From 9e6e0242cccaaa4b683049599cd769ae649a4669 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 14 Nov 2022 22:34:17 -0500
Subject: added an inPlace property for setting up in place content menu
 patterns.

---
 src/client/views/PropertiesButtons.tsx | 44 +++++++++++++++++++++++++++++++---
 1 file changed, 41 insertions(+), 3 deletions(-)

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

diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 80c2c7705..aefc91751 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -2,7 +2,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
-import { Doc, Opt } from '../../fields/Doc';
+import { Doc, DocListCast, Opt } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
 import { InkField } from '../../fields/InkField';
 import { RichTextField } from '../../fields/RichTextField';
@@ -93,6 +93,14 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             on => 'lock'
         );
     }
+    @computed get forceActiveButton() {
+        return this.propertyToggleBtn(
+            'Active',
+            '_forceActive',
+            on => `${on ? 'Select to activate' : 'Contents always active'} `,
+            on => 'eye'
+        );
+    }
     @computed get fitContentButton() {
         return this.propertyToggleBtn(
             'View All',
@@ -101,6 +109,34 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             on => 'eye'
         );
     }
+    // this implments a container pattern by marking the targetDoc (collection) as an inPlace container,
+    // and then making the contained collection be a "menu" such that when any of its contents are clicked,
+    // they will open their targets in the outer container.  To get back to the "menu", you click on the main container.
+    @computed get inPlaceContainerButton() {
+        return this.propertyToggleBtn(
+            'In Place',
+            'isInPlaceContainer',
+            on => `${on ? 'Make' : 'Remove'} in place container flag`,
+            on => 'window',
+            onClick => {
+                SelectionManager.Views().forEach(dv => {
+                    const containerDoc = dv.rootDoc;
+                    containerDoc._isLinkButton = containerDoc._fitContentsToBox = containerDoc._forceActive = containerDoc._isInPlaceContainer = !containerDoc._isInPlaceContainer;
+                    containerDoc.followLinkLocation = containerDoc._isInPlaceContainer ? 'inPlace' : undefined;
+                    containerDoc._xPadding = containerDoc._yPadding = containerDoc._isInPlaceContainer ? 10 : undefined;
+                    const menuDoc = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]).lastElement();
+                    if (menuDoc) {
+                        menuDoc._forceActive = menuDoc._fitContentsToBox = menuDoc._isLinkButton = true;
+                        DocUtils.MakeLink({ doc: dv.rootDoc }, { doc: menuDoc }, 'back link to container');
+                        DocListCast(menuDoc[Doc.LayoutFieldKey(menuDoc)]).forEach(menuItem => {
+                            menuItem._isLinkButton = true;
+                            menuItem._followLinkLocation = 'inPlace';
+                        });
+                    }
+                });
+            }
+        );
+    }
     @computed get fitWidthButton() {
         return this.propertyToggleBtn(
             'Fit\xA0Width',
@@ -256,7 +292,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                         docView.setToggleDetail();
                         break;
                     case 'linkInPlace':
-                        docView.toggleFollowLink('inPlace', true, false);
+                        docView.toggleFollowLink('inPlace', false, false);
                         break;
                     case 'linkOnRight':
                         docView.toggleFollowLink('add:right', false, false);
@@ -277,7 +313,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             ['nothing', 'Select Document'],
             ['enterPortal', 'Enter Portal'],
             ['toggleDetail', 'Toggle Detail'],
-            ['linkInPlace', 'Follow Link'],
+            ['linkInPlace', 'Open in Place'],
             ['linkOnRight', 'Open Link on Right'],
         ];
         const currentSelection = this.selectedDoc.onClickBehavior;
@@ -360,7 +396,9 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                 {toggle(this.onClickButton)}
                 {toggle(this.fitWidthButton)}
                 {toggle(this.freezeThumb)}
+                {toggle(this.forceActiveButton, { display: !isFreeForm && !isMap ? 'none' : '' })}
                 {toggle(this.fitContentButton, { display: !isFreeForm && !isMap ? 'none' : '' })}
+                {toggle(this.inPlaceContainerButton, { display: !isFreeForm && !isMap ? 'none' : '' })}
                 {toggle(this.autoHeightButton, { display: !isText && !isStacking && !isTree ? 'none' : '' })}
                 {toggle(this.maskButton, { display: !isInk ? 'none' : '' })}
                 {toggle(this.chromeButton, { display: !isCollection || isNovice ? 'none' : '' })}
-- 
cgit v1.2.3-70-g09d2


From 6a6408b120c60a717efb90576e1f14d1ac2cde68 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 14 Nov 2022 22:54:33 -0500
Subject: add noHighlight and noShadow flags

---
 src/client/views/PropertiesButtons.tsx | 4 ++--
 src/client/views/StyleProvider.tsx     | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index aefc91751..5992b3eb9 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -121,12 +121,12 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             onClick => {
                 SelectionManager.Views().forEach(dv => {
                     const containerDoc = dv.rootDoc;
-                    containerDoc._isLinkButton = containerDoc._fitContentsToBox = containerDoc._forceActive = containerDoc._isInPlaceContainer = !containerDoc._isInPlaceContainer;
+                    containerDoc.noShadow = containerDoc.noHighlighting = containerDoc._isLinkButton = containerDoc._fitContentsToBox = containerDoc._forceActive = containerDoc._isInPlaceContainer = !containerDoc._isInPlaceContainer;
                     containerDoc.followLinkLocation = containerDoc._isInPlaceContainer ? 'inPlace' : undefined;
                     containerDoc._xPadding = containerDoc._yPadding = containerDoc._isInPlaceContainer ? 10 : undefined;
                     const menuDoc = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]).lastElement();
                     if (menuDoc) {
-                        menuDoc._forceActive = menuDoc._fitContentsToBox = menuDoc._isLinkButton = true;
+                        menuDoc._forceActive = menuDoc._fitContentsToBox = menuDoc._isLinkButton = menuDoc._noShadow = menuDoc.noHighlighting = containerDoc._isInPlaceContainer;
                         DocUtils.MakeLink({ doc: dv.rootDoc }, { doc: menuDoc }, 'back link to container');
                         DocListCast(menuDoc[Doc.LayoutFieldKey(menuDoc)]).forEach(menuItem => {
                             menuItem._isLinkButton = true;
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index caee19e14..a04f4a4f4 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -109,7 +109,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             allSorts[TreeSort.None] = { color: 'darkgray', label: '\u00A0\u00A0\u00A0' };
             return allSorts;
         case StyleProp.Highlighting:
-            if (doc) {
+            if (doc && !doc.noHighlighting) {
                 const highlightIndex = Doc.isBrushedHighlightedDegree(doc);
                 const highlightColor = ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
                 const highlightStyle = ['solid', 'dashed', 'solid', 'solid', 'solid'][highlightIndex];
@@ -250,7 +250,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             return docColor;
         }
         case StyleProp.BoxShadow: {
-            if (!doc || opacity() === 0) return undefined; // if it's not visible, then no shadow)
+            if (!doc || opacity() === 0 || doc.noShadow) return undefined; // if it's not visible, then no shadow)
             if (doc.boxShadow === 'standard') return Shadows.STANDARD_SHADOW;
             if (doc?.isLinkButton && ![DocumentType.LINK, DocumentType.INK].includes(doc.type as any)) return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
             switch (doc?.type) {
-- 
cgit v1.2.3-70-g09d2


From 19f317bd43a7cc8df0daf1c0642011cc8754e14b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 15 Nov 2022 12:49:53 -0500
Subject: made InPlace container tool button apply more settings to be more
 like a template. added followAllLInks flag.  added image anchors to save pan
 zoom.  added follow link button bar option for follow all links.  added
 hideDecorations flag and property

---
 src/client/documents/Documents.ts                  |  5 ++
 src/client/util/LinkFollower.ts                    | 11 ++--
 src/client/views/DocumentButtonBar.scss            | 31 ++++++++++-
 src/client/views/DocumentButtonBar.tsx             | 60 +++++++++++++++-------
 src/client/views/DocumentDecorations.tsx           | 19 +++----
 src/client/views/PropertiesButtons.tsx             | 17 ++++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/linking/LinkEditor.tsx            |  1 -
 src/client/views/nodes/DocumentView.tsx            |  3 +-
 src/client/views/nodes/ImageBox.tsx                | 52 ++++++++++++++++---
 src/client/views/nodes/trails/PresBox.tsx          |  3 +-
 11 files changed, 154 insertions(+), 50 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index e44004f45..8f45802fe 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -257,6 +257,7 @@ export class DocumentOptions {
     lastFrame?: number; // the last frame of a frame-based collection (e.g., progressive slide)
     activeFrame?: number; // the active frame of a document in a frame base collection
     appearFrame?: number; // the frame in which the document appears
+    viewTransitionTime?: number; // transition duration for view parameters
     presTransition?: number; //the time taken for the transition TO a document
     presDuration?: number; //the duration of the slide in presentation view
     borderRounding?: string;
@@ -1047,6 +1048,10 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.MARKER), options?.data, options, id);
         }
 
+        export function ImageanchorDocument(options: DocumentOptions = {}, id?: string) {
+            return InstanceFromProto(Prototypes.get(DocumentType.MARKER), options?.data, options, id);
+        }
+
         export function HTMLAnchorDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
             return InstanceFromProto(Prototypes.get(DocumentType.MARKER), new List(documents), options, id);
         }
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index c09c9d1c5..c6fc7b372 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -1,6 +1,7 @@
 import { action, runInAction } from 'mobx';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
-import { BoolCast, Cast, StrCast } from '../../fields/Types';
+import { BoolCast, Cast, DocCast, StrCast } from '../../fields/Types';
+import { DocumentType } from '../documents/DocumentTypes';
 import { DocumentDecorations } from '../views/DocumentDecorations';
 import { LightboxView } from '../views/LightboxView';
 import { DocumentViewSharedProps, ViewAdjustment } from '../views/nodes/DocumentView';
@@ -76,11 +77,11 @@ export class LinkFollower {
         const linkDocs = link ? [link] : DocListCast(sourceDoc.links);
         const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor1
         const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor2
-        const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0);
-        const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0);
+        const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews((d.anchor2 as Doc).type === DocumentType.MARKER ? DocCast((d.anchor2 as Doc).annotationOn) : (d.anchor2 as Doc)).length === 0);
+        const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews((d.anchor1 as Doc).type === DocumentType.MARKER ? DocCast((d.anchor1 as Doc).annotationOn) : (d.anchor1 as Doc)).length === 0);
         const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView;
-        const linkDocList = linkWithoutTargetDoc ? [linkWithoutTargetDoc] : traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs;
-        const followLinks = sourceDoc.isPushpin ? linkDocList : linkDocList.slice(0, 1);
+        const linkDocList = linkWithoutTargetDoc && !sourceDoc.followAllLinks ? [linkWithoutTargetDoc] : traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs;
+        const followLinks = sourceDoc.isPushpin || sourceDoc.followAllLinks ? linkDocList : linkDocList.slice(0, 1);
         var count = 0;
         const allFinished = () => ++count === followLinks.length && finished?.();
         followLinks.forEach(async linkDoc => {
diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss
index 1e93ba5e2..f9c988fdd 100644
--- a/src/client/views/DocumentButtonBar.scss
+++ b/src/client/views/DocumentButtonBar.scss
@@ -18,15 +18,44 @@ $linkGap: 3px;
     cursor: pointer;
 }
 
+.documentButtonBar-followTypes,
 .documentButtonBar-pinTypes {
     position: absolute;
-    display: flex;
+    display: none;
     width: 60px;
     top: -14px;
     background: black;
     height: 20px;
     align-items: center;
 }
+.documentButtonBar-followTypes {
+    width: 20px;
+    display: none;
+}
+.documentButtonBar-followIcon {
+    align-items: center;
+    display: flex;
+    height: 100%;
+    &:hover {
+        background-color: lightblue;
+    }
+}
+.documentButtonBar-follow {
+    &:hover {
+        .documentButtonBar-followTypes {
+            display: flex;
+        }
+    }
+}
+.documentButtonBar-pin {
+    color: white;
+    &:hover {
+        .documentButtonBar-pinTypes {
+            display: flex;
+        }
+    }
+}
+
 .documentButtonBar-pinIcon {
     &:hover {
         background-color: lightblue;
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 36875290e..794b51cc5 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -5,7 +5,7 @@ import { action, computed, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc } from '../../fields/Doc';
 import { RichTextField } from '../../fields/RichTextField';
-import { Cast, NumCast } from '../../fields/Types';
+import { BoolCast, Cast, NumCast } from '../../fields/Types';
 import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
@@ -209,21 +209,52 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
             </Tooltip>
         );
     }
+    @observable subFollow = '';
     @computed
     get followLinkButton() {
         const targetDoc = this.view0?.props.Document;
+        const followBtn = (allDocs: boolean, click: (doc: Doc) => void, isSet: (doc?: Doc) => boolean, icon: IconProp) => {
+            const tooltip = `Follow ${this.subPin}documents`;
+            return !tooltip ? null : (
+                <Tooltip title={<div className="dash-tooltip">{tooltip}</div>}>
+                    <div className="documentButtonBar-followIcon" style={{ backgroundColor: isSet(targetDoc) ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: isSet(targetDoc) ? Colors.BLACK : Colors.WHITE }}>
+                        <FontAwesomeIcon
+                            className="documentdecorations-icon"
+                            style={{ width: 20 }}
+                            key={icon.toString()}
+                            size="sm"
+                            icon={icon}
+                            onPointerEnter={action(e => (this.subPin = allDocs ? 'All ' : ''))}
+                            onPointerLeave={action(e => (this.subPin = ''))}
+                            onClick={e => {
+                                this.props.views().forEach(dv => click(dv!.rootDoc));
+                                e.stopPropagation();
+                            }}
+                        />
+                    </div>
+                </Tooltip>
+            );
+        };
         return !targetDoc ? null : (
-            <Tooltip title={<div className="dash-tooltip">{'Set onClick to follow primary link'}</div>}>
+            <Tooltip title={<div className="dash-tooltip">Set onClick to follow primary link</div>}>
                 <div
-                    className="documentButtonBar-icon"
+                    className="documentButtonBar-icon documentButtonBar-follow"
                     style={{ backgroundColor: targetDoc.isLinkButton ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: targetDoc.isLinkButton ? Colors.BLACK : Colors.WHITE }}
                     onClick={undoBatch(e => this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, undefined, false)))}>
+                    <div className="documentButtonBar-followTypes">
+                        {followBtn(
+                            true,
+                            (doc: Doc) => (doc.followAllLinks = !doc.followAllLinks),
+                            (doc?: Doc) => (doc?.followAllLinks ? true : false),
+                            'window-maximize'
+                        )}
+                    </div>
                     <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="hand-point-right" />
                 </div>
             </Tooltip>
         );
     }
-    @observable expandPin = false;
+
     @observable subPin = '';
     @computed
     get pinButton() {
@@ -264,10 +295,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         return !targetDoc ? null : (
             <Tooltip title={<div className="dash-tooltip">{`Pin Document ${SelectionManager.Views().length > 1 ? 'multiple documents' : ''} to Trail`}</div>}>
                 <div
-                    className="documentButtonBar-icon"
-                    style={{ color: 'white' }}
-                    onPointerEnter={action(e => (this.expandPin = true))}
-                    onPointerLeave={action(e => (this.expandPin = false))}
+                    className="documentButtonBar-icon documentButtonBar-pin"
                     onClick={e => {
                         const docs = this.props
                             .views()
@@ -276,13 +304,11 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                         TabDocView.PinDoc(docs, { pinDocLayout: e.shiftKey, pinDocContent: e.altKey, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) });
                         e.stopPropagation();
                     }}>
-                    {this.expandPin ? (
-                        <div className="documentButtonBar-pinTypes">
-                            {pinBtn(true, false, 'window-maximize')}
-                            {pinBtn(false, true, 'address-card')}
-                            {pinBtn(true, true, 'id-card')}
-                        </div>
-                    ) : null}
+                    <div className="documentButtonBar-pinTypes">
+                        {pinBtn(true, false, 'window-maximize')}
+                        {pinBtn(false, true, 'address-card')}
+                        {pinBtn(true, true, 'id-card')}
+                    </div>
                     <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
                 </div>
             </Tooltip>
@@ -460,9 +486,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                 ) : null}
                 {
                     Doc.noviceMode ? null : <div className="documentButtonBar-button">{this.templateButton}</div>
-                    /*<div className="documentButtonBar-button">
-                        {this.metadataButton}
-                    </div> */
+                    /*<div className="documentButtonBar-button"> {this.metadataButton} </div> */
                 }
                 {Doc.noviceMode || !SelectionManager.Views()?.some(v => v.allLinks.length) ? null : <div className="documentButtonBar-button">{this.followLinkButton}</div>}
                 <div className="documentButtonBar-button">{this.pinButton}</div>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3fac137fe..3efb5fb37 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -708,24 +708,25 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     }
 
     render() {
-        const { b, c, r, x, y } = this.Bounds;
-        const bounds = { b, c, r, x, y };
+        const { b, r, x, y } = this.Bounds;
+        const bounds = { b, r, x, y };
         const seldocview = SelectionManager.Views().slice(-1)[0];
         if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldocview || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
             return null;
         }
         // hide the decorations if the parent chooses to hide it or if the document itself hides it
-        const hideResizers = seldocview.props.hideResizeHandles || seldocview.rootDoc.hideResizeHandles || seldocview.rootDoc._isGroup || this._isRounding || this._isRotating;
-        const hideTitle = seldocview.props.hideDecorationTitle || seldocview.rootDoc.hideDecorationTitle || this._isRounding || this._isRotating;
-        const hideDocumentButtonBar = seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.hideDocumentButtonBar || this._isRounding || this._isRotating;
+        const hideDecorations = seldocview.props.hideDecorations || seldocview.rootDoc.hideDecorations;
+        const hideResizers = hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.hideResizeHandles || seldocview.rootDoc._isGroup || this._isRounding || this._isRotating;
+        const hideTitle = hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.hideDecorationTitle || this._isRounding || this._isRotating;
+        const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.hideDocumentButtonBar || this._isRounding || this._isRotating;
         // if multiple documents have been opened at the same time, then don't show open button
-        const hideOpenButton =
+        const hideOpenButton =hideDecorations || 
             seldocview.props.hideOpenButton ||
             seldocview.rootDoc.hideOpenButton ||
             SelectionManager.Views().some(docView => docView.props.Document._stayInCollection || docView.props.Document.isGroup || docView.props.Document.hideOpenButton) ||
             this._isRounding ||
             this._isRotating;
-        const hideDeleteButton =
+        const hideDeleteButton =hideDecorations || 
             this._isRounding ||
             this._isRotating ||
             seldocview.props.hideDeleteButton ||
@@ -767,7 +768,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight));
 
         const useLock = bounds.r - bounds.x > 135 && seldocview.props.CollectionFreeFormDocumentView;
-        const useRotation = seldocview.rootDoc.type !== DocumentType.EQUATION && seldocview.props.CollectionFreeFormDocumentView; // when do we want an object to not rotate?
+        const useRotation = !hideResizers && seldocview.rootDoc.type !== DocumentType.EQUATION && seldocview.props.CollectionFreeFormDocumentView; // when do we want an object to not rotate?
         const rotation = NumCast(seldocview.rootDoc._rotation);
 
         const resizerScheme = colorScheme ? 'documentDecorations-resizer' + colorScheme : '';
@@ -837,7 +838,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                             <div className="documentDecorations-topbar" onPointerDown={this.onContainerDown}>
                                 {hideDeleteButton ? <div /> : topBtn('close', 'times', undefined, e => this.onCloseClick(true), 'Close')}
                                 {hideResizers || hideDeleteButton ? <div /> : topBtn('minimize', 'window-maximize', undefined, e => this.onCloseClick(undefined), 'Minimize')}
-                                {titleArea}
+                                {hideTitle ? null : titleArea}
                                 {hideOpenButton ? <div /> : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as alias, shift: in new collection)')}
                             </div>
                             {hideResizers ? null : (
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 5992b3eb9..d27848a90 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -121,15 +121,24 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             onClick => {
                 SelectionManager.Views().forEach(dv => {
                     const containerDoc = dv.rootDoc;
-                    containerDoc.noShadow = containerDoc.noHighlighting = containerDoc._isLinkButton = containerDoc._fitContentsToBox = containerDoc._forceActive = containerDoc._isInPlaceContainer = !containerDoc._isInPlaceContainer;
+                    containerDoc.followAllLinks =
+                        containerDoc.noShadow =
+                        containerDoc.noHighlighting =
+                        containerDoc._isLinkButton =
+                        containerDoc._fitContentsToBox =
+                        containerDoc._forceActive =
+                        containerDoc._isInPlaceContainer =
+                            !containerDoc._isInPlaceContainer;
                     containerDoc.followLinkLocation = containerDoc._isInPlaceContainer ? 'inPlace' : undefined;
                     containerDoc._xPadding = containerDoc._yPadding = containerDoc._isInPlaceContainer ? 10 : undefined;
                     const menuDoc = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]).lastElement();
                     if (menuDoc) {
-                        menuDoc._forceActive = menuDoc._fitContentsToBox = menuDoc._isLinkButton = menuDoc._noShadow = menuDoc.noHighlighting = containerDoc._isInPlaceContainer;
-                        DocUtils.MakeLink({ doc: dv.rootDoc }, { doc: menuDoc }, 'back link to container');
+                        menuDoc.hideDecorations = menuDoc._forceActive = menuDoc._fitContentsToBox = menuDoc._isLinkButton = menuDoc._noShadow = menuDoc.noHighlighting = containerDoc._isInPlaceContainer;
+                        if (!dv.allLinks.find(link => link.anchor1 === menuDoc || link.anchor2 === menuDoc)) {
+                            DocUtils.MakeLink({ doc: dv.rootDoc }, { doc: menuDoc }, 'back link to container');
+                        }
                         DocListCast(menuDoc[Doc.LayoutFieldKey(menuDoc)]).forEach(menuItem => {
-                            menuItem._isLinkButton = true;
+                            menuItem.followAllLinks = menuItem._isLinkButton = true;
                             menuItem._followLinkLocation = 'inPlace';
                         });
                     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index e24b116d0..8fe5ad63f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1157,7 +1157,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         // if (SelectionManager.Views().length !== 1 || SelectionManager.Views()[0].Document !== doc) {
         //     SelectionManager.DeselectAll();
         // }
-        if (this.props.Document.scrollHeight || this.props.Document.scrollTop !== undefined || this.props.Document.currentTimecode !== undefined) {
+        if (this.props.Document.scrollHeight || this.props.Document.scrollTop !== undefined || this.props.Document.currentTimecode !== undefined || doc.type === DocumentType.MARKER) {
             this.props.focus(doc, options);
         } else {
             const xfToCollection = options?.docTransform ?? Transform.Identity();
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 2fb8e3d4c..3e8867c50 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -206,7 +206,6 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
     @computed
     get editAudioFollow() {
         //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS
-        console.log('AudioFollow:' + this.audioFollow);
         return (
             <div className="linkEditor-zoomFollow">
                 <div className="linkEditor-zoomFollow-label">Play Target Audio:</div>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7accd49a4..76d6d3532 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -166,7 +166,8 @@ export interface DocumentViewSharedProps {
 // these props are specific to DocuentViews
 export interface DocumentViewProps extends DocumentViewSharedProps {
     // properties specific to DocumentViews but not to FieldView
-    hideResizeHandles?: boolean; // whether to suppress DocumentDecorations when this document is selected
+    hideDecorations?: boolean; // whether to suppress all DocumentDecorations when doc is selected
+    hideResizeHandles?: boolean; // whether to suppress resized handles on doc decorations when this document is selected
     hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
     hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
     hideDocumentButtonBar?: boolean;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index d0df41023..d67481b22 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -14,7 +14,7 @@ import { TraceMobx } from '../../../fields/util';
 import { emptyFunction, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
-import { DocUtils } from '../../documents/Documents';
+import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { Networking } from '../../Network';
 import { DragManager } from '../../util/DragManager';
@@ -49,21 +49,58 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         return FieldView.LayoutString(ImageBox, fieldKey);
     }
     private _dropDisposer?: DragManager.DragDropDisposer;
+    private _transitioning: any; // timer for setting view spec properties
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _getAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined;
     @observable _curSuffix = '';
     @observable _uploadIcon = uploadIcons.idle;
+    @observable _focusViewScale: number | undefined = 1;
+    @observable _focusPanX: number | undefined = 0;
+    @observable _focusPanY: number | undefined = 0;
+    get viewScale() {
+        return this._focusViewScale || StrCast(this.layoutDoc._viewScale);
+    }
+    get panX() {
+        return this._focusPanX || StrCast(this.layoutDoc._panX);
+    }
+    get panY() {
+        return this._focusPanY || StrCast(this.layoutDoc._panY);
+    }
 
     protected createDropTarget = (ele: HTMLDivElement) => {
         this._dropDisposer?.();
         ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document));
     };
-    setViewSpec = (anchor: Doc, preview: boolean) => {}; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
+
+    @action
+    setViewSpec = (anchor: Doc, preview: boolean) => {
+        if (preview && anchor._viewScale !== undefined) {
+            this._focusViewScale = Cast(anchor._viewScale, 'number', null);
+            this._focusPanX = Cast(anchor._panX, 'number', null);
+            this._focusPanY = Cast(anchor._panX, 'number', null);
+        } else if (anchor._viewScale !== undefined) {
+            const smoothTime = NumCast(anchor.viewTransitionTime);
+            this.layoutDoc.viewTransition = `all ${smoothTime}ms`;
+            this.layoutDoc._panX = NumCast(anchor._panX, NumCast(this.layoutDoc._panY));
+            this.layoutDoc._panY = NumCast(anchor._panY, NumCast(this.layoutDoc._panX));
+            this.layoutDoc._viewScale = NumCast(anchor._viewScale, NumCast(this.layoutDoc._viewScale));
+            if (anchor.type === DocumentType.MARKER) {
+                this.dataDoc[this.annotationKey] = new List<Doc>(DocListCast(anchor._annotations));
+            }
+            clearTimeout(this._transitioning);
+            this._transitioning = setTimeout(() => (this.layoutDoc.viewTransition = undefined), smoothTime);
+        }
+    }; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
 
     getAnchor = () => {
-        const anchor = this._getAnchor?.(this._savedAnnotations);
-        anchor && this.addDocument(anchor);
-        return anchor ?? this.rootDoc;
+        const zoomedAnchor = () => Docs.Create.ImageanchorDocument({ viewTransitionTime: 1000, _viewScale: NumCast(this.layoutDoc._viewScale), _panX: NumCast(this.layoutDoc._panX), _panY: NumCast(this.layoutDoc._panY) });
+        const anchor = this._getAnchor?.(this._savedAnnotations) ?? (this.layoutDoc.viewScale ? zoomedAnchor() : undefined);
+        if (anchor) {
+            anchor._annotations = new List<Doc>(DocListCast(this.dataDoc[this.annotationKey]));
+            this.addDocument(anchor);
+            return anchor;
+        }
+        return this.rootDoc;
     };
 
     componentDidMount() {
@@ -90,6 +127,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     }
 
     componentWillUnmount() {
+        clearTimeout(this._transitioning);
         Object.values(this._disposers).forEach(disposer => disposer?.());
     }
 
@@ -283,9 +321,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     setTimeout(
                         action(() => {
                             this._uploadIcon = idle;
-                            if (data) {
-                                dataDoc[this.fieldKey] = data;
-                            }
+                            data && (dataDoc[this.fieldKey] = data);
                         }),
                         2000
                     );
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index b495a9399..8d805c663 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -509,8 +509,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     static NavigateToTarget(targetDoc: Doc, activeItem: Doc, openInTab: any, srcContext: Doc, finished?: () => void) {
         if ((activeItem.presPinLayout || activeItem.presPinView) && DocCast(targetDoc.context)?._currentFrame === undefined) {
             const transTime = NumCast(activeItem.presTransition, 500);
-            const presTransitionTime = `all ${transTime}ms`;
-            targetDoc._dataTransition = presTransitionTime;
+            targetDoc._dataTransition = `all ${transTime}ms`;
             targetDoc.x = NumCast(activeItem.presX, NumCast(targetDoc.x));
             targetDoc.y = NumCast(activeItem.presY, NumCast(targetDoc.y));
             targetDoc.rotation = NumCast(activeItem.presRot, NumCast(targetDoc.rotation));
-- 
cgit v1.2.3-70-g09d2


From 51a85bae74c3d8ccddb57f6063e93b237cdf1ebd Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 15 Nov 2022 14:24:11 -0500
Subject: added playback of audio annotation on inPlace container.  fixed image
 anchors to be marked un-rendered so link lines show up.

---
 src/client/util/DocumentManager.ts       |  2 +-
 src/client/views/PropertiesButtons.tsx   |  2 +-
 src/client/views/nodes/DocumentView.scss |  6 +++---
 src/client/views/nodes/ImageBox.tsx      | 11 +++++++----
 4 files changed, 12 insertions(+), 9 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index ef0a339ce..519f75dbd 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -252,7 +252,7 @@ export class DocumentManager {
         } else {
             if (!targetDocContext) {
                 // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default
-                createViewFunc(Doc.BrushDoc(targetDoc), finished); // bcz: should we use this?: Doc.MakeAlias(targetDoc)));
+                createViewFunc(Doc.BrushDoc(targetDoc), () => focusAndFinish(true)); // bcz: should we use this?: Doc.MakeAlias(targetDoc)));
             } else {
                 // otherwise try to get a view of the context of the target
                 if (targetDocContextView) {
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index d27848a90..656a56a15 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -138,7 +138,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                             DocUtils.MakeLink({ doc: dv.rootDoc }, { doc: menuDoc }, 'back link to container');
                         }
                         DocListCast(menuDoc[Doc.LayoutFieldKey(menuDoc)]).forEach(menuItem => {
-                            menuItem.followAllLinks = menuItem._isLinkButton = true;
+                            menuItem.followLinkAudio = menuItem.followAllLinks = menuItem._isLinkButton = true;
                             menuItem._followLinkLocation = 'inPlace';
                         });
                     }
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index e437d295e..01188d3fa 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -198,9 +198,9 @@
         > .documentView-titleWrapper-hover {
             display: inline-block;
         }
-        > .documentView-contentsView {
-            opacity: 0.5;
-        }
+        // > .documentView-contentsView {
+        //     opacity: 0.5;
+        // }
         > .documentView-captionWrapper {
             opacity: 1;
         }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index d67481b22..8ecd8104a 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -84,6 +84,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             this.layoutDoc._panX = NumCast(anchor._panX, NumCast(this.layoutDoc._panY));
             this.layoutDoc._panY = NumCast(anchor._panY, NumCast(this.layoutDoc._panX));
             this.layoutDoc._viewScale = NumCast(anchor._viewScale, NumCast(this.layoutDoc._viewScale));
+            this.layoutDoc[this.fieldKey + '-useAlt'] = Cast(anchor._useAlt, 'boolean', null);
             if (anchor.type === DocumentType.MARKER) {
                 this.dataDoc[this.annotationKey] = new List<Doc>(DocListCast(anchor._annotations));
             }
@@ -93,9 +94,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     }; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
 
     getAnchor = () => {
-        const zoomedAnchor = () => Docs.Create.ImageanchorDocument({ viewTransitionTime: 1000, _viewScale: NumCast(this.layoutDoc._viewScale), _panX: NumCast(this.layoutDoc._panX), _panY: NumCast(this.layoutDoc._panY) });
-        const anchor = this._getAnchor?.(this._savedAnnotations) ?? (this.layoutDoc.viewScale ? zoomedAnchor() : undefined);
+        const anchor =
+            this._getAnchor?.(this._savedAnnotations) ?? // use marquee anchor, otherwise, save zoom/pan as anchor
+            Docs.Create.ImageanchorDocument({ viewTransitionTime: 1000, unrendered: true, annotationOn: this.rootDoc, _viewScale: NumCast(this.layoutDoc._viewScale, 1), _panX: NumCast(this.layoutDoc._panX), _panY: NumCast(this.layoutDoc._panY) });
         if (anchor) {
+            anchor._useAlt = Cast(this.layoutDoc[this.fieldKey + '-useAlt'], 'boolean', null);
             anchor._annotations = new List<Doc>(DocListCast(this.dataDoc[this.annotationKey]));
             this.addDocument(anchor);
             return anchor;
@@ -222,7 +225,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             const funcs: ContextMenuProps[] = [];
             funcs.push({ description: 'Rotate Clockwise 90', event: this.rotate, icon: 'expand-arrows-alt' });
             funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? 'Dynamic Res' : 'Full Res'}`, event: this.resolution, icon: 'expand-arrows-alt' });
-            funcs.push({ description: `${this.layoutDoc[this.fieldKey + '-useAlt'] ? 'Show Primary' : 'Show Alternate'}`, event: this.setUseAlt, icon: 'expand-arrows-alt' });
+            funcs.push({ description: `${this.layoutDoc[this.fieldKey + '-useAlt'] ? 'Show Alternate' : 'Show Primary'}`, event: this.setUseAlt, icon: 'expand-arrows-alt' });
             funcs.push({ description: 'Copy path', event: () => Utils.CopyText(this.choosePath(field.url)), icon: 'expand-arrows-alt' });
             if (!Doc.noviceMode) {
                 funcs.push({ description: 'Export to Google Photos', event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: 'caret-square-right' });
@@ -353,7 +356,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         TraceMobx();
 
         const srcpath = this.paths[0];
-        const fadepath = this.paths[Math.min(1, this.paths.length - 1)];
+        const fadepath = this.paths.lastElement();
         const { nativeWidth, nativeHeight, nativeOrientation } = this.nativeSize;
         const rotation = NumCast(this.dataDoc[this.fieldKey + '-rotation']);
         const aspect = rotation % 180 ? nativeHeight / nativeWidth : 1;
-- 
cgit v1.2.3-70-g09d2


From 613daac016c367205ff1afddd81b7b9111c52d33 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 5 Dec 2022 22:45:22 -0500
Subject: cleaning up following links and pres item following so that view
 transitions don't interfere when clicking quickly (eg through animation
 frames).  changed animations to animate multi-level zooming parallel.

---
 src/client/util/CurrentUserUtils.ts                |   2 +-
 src/client/util/DocumentManager.ts                 | 145 ++++++------
 src/client/util/LinkFollower.ts                    |   2 +-
 src/client/views/LightboxView.tsx                  |   8 +-
 src/client/views/PropertiesButtons.tsx             |   8 +-
 src/client/views/PropertiesView.tsx                |   5 +-
 .../views/collections/CollectionDockingView.tsx    |   3 +-
 src/client/views/collections/CollectionMenu.tsx    |  11 +-
 src/client/views/collections/TabDocView.tsx        |  16 +-
 .../CollectionFreeFormLinkView.tsx                 |   6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  39 ++--
 .../collections/collectionFreeForm/MarqueeView.tsx |  14 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  57 ++---
 src/client/views/nodes/DocumentView.tsx            |  54 ++++-
 src/client/views/nodes/ImageBox.tsx                |   2 +-
 src/client/views/nodes/PDFBox.tsx                  |   2 +-
 src/client/views/nodes/VideoBox.tsx                |   2 +-
 src/client/views/nodes/button/FontIconBox.tsx      |   8 +-
 src/client/views/nodes/trails/PresBox.tsx          | 253 ++++++---------------
 19 files changed, 280 insertions(+), 357 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index d13209c4f..9dfc91e3f 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -667,7 +667,7 @@ export class CurrentUserUtils {
               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_, altKey)'}},
             { 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()'}, width: 20, buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()', scripts: { script: 'return curKeyFrame(_readOnly_)'}},
             { 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/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 1b63b615b..d2368f4f6 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,19 +1,19 @@
 import { action, observable, runInAction } from 'mobx';
-import { Doc, DocListCast, DocListCastAsync, Opt } from '../../fields/Doc';
+import { Doc, Opt } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
-import { Cast, DocCast } from '../../fields/Types';
+import { listSpec } from '../../fields/Schema';
+import { Cast } from '../../fields/Types';
+import { AudioField } from '../../fields/URLField';
 import { returnFalse } from '../../Utils';
 import { DocumentType } from '../documents/DocumentTypes';
-import { LightboxView } from '../views/LightboxView';
-import { DocFocusOptions, DocumentView, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView';
-import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
 import { CollectionFreeFormView } from '../views/collections/collectionFreeForm';
 import { CollectionView } from '../views/collections/CollectionView';
+import { LightboxView } from '../views/LightboxView';
+import { DocFocusOptions, DocumentView, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView';
+import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
 import { ScriptingGlobals } from './ScriptingGlobals';
 import { SelectionManager } from './SelectionManager';
-import { listSpec } from '../../fields/Schema';
-import { AudioField } from '../../fields/URLField';
 const { Howl } = require('howler');
 
 export class DocumentManager {
@@ -31,6 +31,30 @@ export class DocumentManager {
     //private constructor so no other class can create a nodemanager
     private constructor() {}
 
+    private _viewRenderedCbs: { doc: Doc; func: (dv: DocumentView) => any }[] = [];
+    public AddViewRenderedCb = (doc: Doc, func: (dv: DocumentView) => any) => {
+        const dv = this.getDocumentViewById(doc[Id]);
+        this._viewRenderedCbs.push({ doc, func });
+        if (dv) {
+            this.callAddViewFuncs(dv);
+        }
+    };
+    callAddViewFuncs = (view: DocumentView) => {
+        const callFuncs = this._viewRenderedCbs.filter(vc => vc.doc === view.rootDoc);
+        if (callFuncs.length) {
+            this._viewRenderedCbs = this._viewRenderedCbs.filter(vc => callFuncs.includes(vc));
+            const intTimer = setInterval(
+                () => {
+                    if (!view.ComponentView?.incrementalRendering?.()) {
+                        callFuncs.forEach(cf => cf.func(view));
+                        clearInterval(intTimer);
+                    }
+                },
+                view.ComponentView?.incrementalRendering?.() ? 0 : 100
+            );
+        }
+    };
+
     @action
     public AddView = (view: DocumentView) => {
         //console.log("MOUNT " + view.props.Document.title + "/" + view.props.LayoutTemplateString);
@@ -50,6 +74,7 @@ export class DocumentManager {
         } else {
             this.DocumentViews.add(view);
         }
+        this.callAddViewFuncs(view);
     };
     public RemoveView = action((view: DocumentView) => {
         this.LinkedDocumentViews.slice().forEach(
@@ -173,7 +198,7 @@ export class DocumentManager {
         targetDoc: Doc, // document to display
         options: DocFocusOptions, // options for how to navigate to target
         createViewFunc = DocumentManager.addView, // how to create a view of the doc if it doesn't exist
-        docContext: Doc[], // context to load that should contain the target
+        docContextPath: Doc[], // context to load that should contain the target
         finished?: () => void
     ): void => {
         const originalTarget = options.originalTarget ?? targetDoc;
@@ -201,29 +226,20 @@ export class DocumentManager {
             finished?.();
         };
         const annoContainerView = (!wasHidden || resolvedTarget !== annotatedDoc) && annotatedDoc && this.getFirstDocumentView(annotatedDoc);
-        const contextDocs = docContext.length ? DocListCast(docContext[0].data) : undefined;
-        const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext.lastElement() : undefined;
-        const targetDocContext = contextDoc || annotatedDoc;
-        const targetDocContextView = (targetDocContext && this.getFirstDocumentView(targetDocContext)) || (wasHidden && annoContainerView); // if we have an annotation container and the target was hidden, then try again because we just un-hid the document above
-        const focusView = !docView && targetDoc.type === DocumentType.MARKER && annoContainerView ? annoContainerView : docView;
         if (annoContainerView) {
             if (annoContainerView.props.Document.layoutKey === 'layout_icon') {
-                annoContainerView.iconify(() =>
-                    annoContainerView.focus(targetDoc, {
-                        ...options,
-                        originalTarget,
-                        afterFocus: (didFocus: boolean) =>
-                            new Promise<ViewAdjustment>(res => {
-                                focusAndFinish(true);
-                                res(ViewAdjustment.doNothing);
-                            }),
-                    })
-                );
-                return;
-            } else if (!docView && targetDoc.type !== DocumentType.MARKER) {
+                return annoContainerView.iconify(() => DocumentManager.Instance.AddViewRenderedCb(targetDoc, () => this.jumpToDocument(resolvedTarget ?? targetDoc, { ...options, toggleTarget: false }, createViewFunc, docContextPath, finished)), 30);
+            }
+            if (!docView && targetDoc.type !== DocumentType.MARKER) {
                 annoContainerView.focus(targetDoc, {}); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below
             }
         }
+
+        const contextDoc = docContextPath.length ? docContextPath[0] : undefined;
+        const remainingDocContext = docContextPath.length ? docContextPath.slice(1) : [];
+        const targetDocContext = contextDoc || annotatedDoc;
+        const targetDocContextView = (targetDocContext && this.getFirstDocumentView(targetDocContext)) || (wasHidden && annoContainerView); // if we have an annotation container and the target was hidden, then try again because we just un-hid the document above
+        const focusView = !docView && targetDoc.type === DocumentType.MARKER && annoContainerView ? annoContainerView : docView;
         if (focusView) {
             !options.noSelect && Doc.linkFollowHighlight(focusView.rootDoc, undefined, targetDoc); //TODO:glr make this a setting in PresBox
             if (options.playAudio) DocumentManager.playAudioAnno(focusView.rootDoc);
@@ -252,56 +268,53 @@ export class DocumentManager {
                     // we found a context view and aren't forced to create a new one ... focus on the context first..
                     wasHidden = wasHidden || targetDocContextView.rootDoc.hidden;
                     targetDocContextView.rootDoc.hidden = false; // make sure context isn't hidden
-                    targetDocContext._viewTransition = 'transform 500ms';
+
+                    if (targetDocContext.layoutKey === 'layout_icon') {
+                        return targetDocContextView.iconify(
+                            () => DocumentManager.Instance.AddViewRenderedCb(targetDoc, () => this.jumpToDocument(resolvedTarget ?? targetDoc, { ...options /* originalTarget - needed? */ }, createViewFunc, docContextPath, finished)),
+                            30
+                        );
+                    }
+
+                    const contextFocusTime = options.zoomTime ? options.zoomTime / 2 : 500;
+                    const remainingFocustime = options.zoomTime ? options.zoomTime - contextFocusTime : undefined;
+                    targetDocContextView.setViewTransition('transform', contextFocusTime);
+                    // this makes focusing on contexts run in parallel -- jutmp to document below makes them run sequentially
+                    this.AddViewRenderedCb(targetDoc, () => this.jumpToDocument(targetDoc, { ...options, zoomTime: remainingFocustime }, createViewFunc, remainingDocContext, finished));
                     targetDocContextView.props.focus(targetDocContextView.rootDoc, {
                         ...options,
+                        zoomTime: contextFocusTime,
                         // originalTarget, // needed?
                         afterFocus: async () => {
-                            targetDocContext._viewTransition = undefined;
-                            if (targetDocContext.layoutKey === 'layout_icon') {
-                                targetDocContextView.iconify(() => this.jumpToDocument(resolvedTarget ?? targetDoc, { ...options /* originalTarget - needed?*/ }, createViewFunc, docContext, finished));
+                            // now find the target document within the context
+                            if (targetDoc._timecodeToShow) {
+                                // if the target has a timecode, it should show up once the (presumed) video context scrubs to the display timecode;
+                                targetDocContext._currentTimecode = targetDoc.anchorTimecodeToShow;
+                                finished?.();
+                            } else {
+                                // otherwise, just look for the target document in this context view now that we've focused the context view
+                                if (this.getFirstDocumentView(resolvedTarget)) {
+                                    // test again for the target view snce we presumably created the context above by focusing on it
+                                    this.jumpToDocument(targetDoc, { ...options, zoomTime: remainingFocustime }, createViewFunc, remainingDocContext, finished);
+                                } else if (targetDoc.layout) {
+                                    // there will no layout for a TEXTANCHOR type document
+                                    createViewFunc(Doc.BrushDoc(targetDoc), finished); //  create a new view of the target
+                                }
                             }
                             return ViewAdjustment.doNothing;
                         },
                     });
-
-                    // now find the target document within the context
-                    if (targetDoc._timecodeToShow) {
-                        // if the target has a timecode, it should show up once the (presumed) video context scrubs to the display timecode;
-                        targetDocContext._currentTimecode = targetDoc.anchorTimecodeToShow;
-                        finished?.();
-                    } else {
-                        // no timecode means we need to find the context view and focus on our target
-                        const retryDocView = this.getFirstDocumentView(resolvedTarget); // test again for the target view snce we presumably created the context above by focusing on it
-                        if (retryDocView) {
-                            // we found the target in the context.
-                            Doc.linkFollowHighlight(retryDocView.rootDoc);
-                            retryDocView.focus(targetDoc, {
-                                ...options,
-                                // originalTarget -- needed?
-                                afterFocus: (didFocus: boolean) =>
-                                    new Promise<ViewAdjustment>(res => {
-                                        !options.noSelect && focusAndFinish(true);
-                                        res(ViewAdjustment.doNothing);
-                                    }),
-                            }); // focus on the target in the context
-                        } else if (targetDoc.layout) {
-                            // there will no layout for a TEXTANCHOR type document
-                            createViewFunc(Doc.BrushDoc(targetDoc), finished); //  create a new view of the target
-                        }
-                    }
                 } else {
-                    if (docContext.length && docContext[0]?.layoutKey === 'layout_icon') {
-                        const docContextView = this.getFirstDocumentView(docContext[0]);
-                        if (docContextView) {
-                            return docContextView.iconify(() => this.jumpToDocument(targetDoc, { ...options, originalTarget }, createViewFunc, docContext.slice(1, docContext.length), finished));
-                        }
+                    if (docContextPath.length && docContextPath[0]?.layoutKey === 'layout_icon') {
+                        Doc.deiconifyView(docContextPath[0]);
+                        this.jumpToDocument(targetDoc, options, createViewFunc, docContextPath, finished);
+                    } else {
+                        // there's no context view so we need to create one first and try again when that finishes
+                        createViewFunc(
+                            targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target
+                            () => this.jumpToDocument(targetDoc, { ...options }, (doc: Doc, finished?: () => void) => doc !== targetDocContext && createViewFunc(doc, finished), remainingDocContext, finished)
+                        );
                     }
-                    // there's no context view so we need to create one first and try again when that finishes
-                    createViewFunc(
-                        targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target
-                        () => this.jumpToDocument(targetDoc, { ...options, originalTarget }, (doc: Doc, finished?: () => void) => doc !== targetDocContext && createViewFunc(doc, finished), docContext, finished)
-                    );
                 }
             }
         }
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 4f742817a..aec4db1df 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -48,7 +48,7 @@ export class LinkFollower {
                             });
                         } else {
                             finished?.();
-                            res(where !== 'inPlace' || BoolCast(sourceDoc.followLinkZoom) ? ViewAdjustment.resetView : ViewAdjustment.doNothing); // for 'inPlace'  resetting the initial focus&zoom would negate the zoom into the target
+                            res(where !== OpenWhere.inPlace || BoolCast(sourceDoc.followLinkZoom) ? ViewAdjustment.resetView : ViewAdjustment.doNothing); // for 'inPlace'  resetting the initial focus&zoom would negate the zoom into the target
                         }
                     }, 100);
                 });
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 1f58763d1..cf0a2fcfb 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -50,7 +50,6 @@ export class LightboxView extends React.Component<LightboxViewProps> {
             this.LightboxDoc._panY = this._savedState.panY;
             this.LightboxDoc._scrollTop = this._savedState.scrollTop;
             this.LightboxDoc._viewScale = this._savedState.scale;
-            this.LightboxDoc._viewTransition = undefined;
         }
         if (!doc) {
             this._docFilters && (this._docFilters.length = 0);
@@ -167,7 +166,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
         if (targetDocView && target) {
             const l = DocUtils.MakeLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.() || target).lastElement();
             l && (Cast(l.anchor2, Doc, null).backgroundColor = 'lightgreen');
-            targetDocView.focus(target, { originalTarget: target, willZoom: true, scale: 0.9 });
+            targetDocView.focus(target, { originalTarget: target, willZoom: true, zoomScale: 0.9 });
             if (LightboxView._history?.lastElement().target !== target) LightboxView._history?.push({ doc, target });
         } else {
             if (!target && LightboxView.path.length) {
@@ -177,7 +176,6 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                     LightboxView.LightboxDoc._panY = saved.panY;
                     LightboxView.LightboxDoc._viewScale = saved.scale;
                     LightboxView.LightboxDoc._scrollTop = saved.scrollTop;
-                    LightboxView.LightboxDoc._viewTransition = undefined;
                 }
                 const pop = LightboxView.path.pop();
                 if (pop) {
@@ -211,7 +209,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
         if (docView) {
             LightboxView._docTarget = target;
             if (!target) docView.ComponentView?.shrinkWrap?.();
-            else docView.focus(target, { willZoom: true, scale: 0.9 });
+            else docView.focus(target, { willZoom: true, zoomScale: 0.9 });
         } else {
             LightboxView.SetLightboxDoc(doc, target);
         }
@@ -290,7 +288,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                                             const target = LightboxView._docTarget;
                                             const doc = LightboxView._doc;
                                             //const targetView = target && DocumentManager.Instance.getLightboxDocumentView(target);
-                                            if (doc === r.props.Document && (!target || target === doc)) r.ComponentView?.shrinkWrap?.();
+                                            //if (doc === r.props.Document && (!target || target === doc)) r.ComponentView?.shrinkWrap?.();
                                             //else target?.focus(target, { willZoom: true, scale: 0.9, instant: true }); // bcz: why was this here?  it breaks smooth navigation in lightbox using 'next' button
                                         })
                                     );
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 656a56a15..66c3ed439 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -14,7 +14,7 @@ import { SelectionManager } from '../util/SelectionManager';
 import { undoBatch } from '../util/UndoManager';
 import { Colors } from './global/globalEnums';
 import { InkingStroke } from './InkingStroke';
-import { DocumentView } from './nodes/DocumentView';
+import { DocumentView, OpenWhere } from './nodes/DocumentView';
 import { VideoBox } from './nodes/VideoBox';
 import { pasteImageBitmap } from './nodes/WebBoxRenderer';
 import './PropertiesButtons.scss';
@@ -117,7 +117,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             'In Place',
             'isInPlaceContainer',
             on => `${on ? 'Make' : 'Remove'} in place container flag`,
-            on => 'window',
+            on => 'window-restore',
             onClick => {
                 SelectionManager.Views().forEach(dv => {
                     const containerDoc = dv.rootDoc;
@@ -129,7 +129,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                         containerDoc._forceActive =
                         containerDoc._isInPlaceContainer =
                             !containerDoc._isInPlaceContainer;
-                    containerDoc.followLinkLocation = containerDoc._isInPlaceContainer ? 'inPlace' : undefined;
+                    containerDoc.followLinkLocation = containerDoc._isInPlaceContainer ? OpenWhere.inPlace : undefined;
                     containerDoc._xPadding = containerDoc._yPadding = containerDoc._isInPlaceContainer ? 10 : undefined;
                     const menuDoc = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]).lastElement();
                     if (menuDoc) {
@@ -139,7 +139,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                         }
                         DocListCast(menuDoc[Doc.LayoutFieldKey(menuDoc)]).forEach(menuItem => {
                             menuItem.followLinkAudio = menuItem.followAllLinks = menuItem._isLinkButton = true;
-                            menuItem._followLinkLocation = 'inPlace';
+                            menuItem._followLinkLocation = OpenWhere.inPlace;
                         });
                     }
                 });
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index f7cc32cff..bc08e920a 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -1409,7 +1409,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     });
 
     @undoBatch
-    changeFollowBehavior = action((follow: string) => this.selectedDoc && (this.selectedDoc.followLinkLocation = follow));
+    changeFollowBehavior = action((follow: string) => this.sourceAnchor && (this.sourceAnchor.followLinkLocation = follow));
 
     @undoBatch
     changeAnimationBehavior = action((behavior: string) => this.sourceAnchor && (this.sourceAnchor.linkAnimEffect = behavior));
@@ -1612,7 +1612,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                                                 <option value="add:right">Opening in new right pane</option>
                                                 <option value="replace:left">Replacing left tab</option>
                                                 <option value="replace:right">Replacing right tab</option>
-                                                <option value="fullScreen">Opening full screen</option>
+                                                <option value="fullScreen">Overlaying current tab</option>
+                                                <option value="lightbox">Opening in lightbox</option>
                                                 <option value="add">Opening in new tab</option>
                                                 <option value="replace">Replacing current tab</option>
                                                 <option value="inPlace">Opening in place</option>
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 8cbe548c7..ffc004df6 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -191,7 +191,7 @@ export class CollectionDockingView extends CollectionSubView() {
 
         if (!pullSide && stack) {
             stack.addChild(docContentConfig, undefined);
-            stack.setActiveContentItem(stack.contentItems[stack.contentItems.length - 1]);
+            setTimeout(() => stack.setActiveContentItem(stack.contentItems[stack.contentItems.length - 1]));
         } else {
             const newContentItem = () => {
                 const newItem = glayRoot.layoutManager.createContentItem({ type: 'stack', content: [docContentConfig] }, instance._goldenLayout);
@@ -389,6 +389,7 @@ export class CollectionDockingView extends CollectionSubView() {
                 const className = typeof htmlTarget.className === 'string' ? htmlTarget.className : '';
                 if (!className.includes('lm_close') && !className.includes('lm_maximise')) {
                     this._flush = UndoManager.StartBatch('golden layout edit');
+                    DocServer.UPDATE_SERVER_CACHE();
                 }
             }
         }
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index db81f28f6..e2594b6ae 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -31,7 +31,7 @@ import { Colors } from '../global/globalEnums';
 import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from '../InkingStroke';
 import { LightboxView } from '../LightboxView';
 import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
-import { DocumentView } from '../nodes/DocumentView';
+import { DocumentView, OpenWhereMod } from '../nodes/DocumentView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
 import { DefaultStyleProvider } from '../StyleProvider';
@@ -569,7 +569,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
     startRecording = () => {
         const doc = Docs.Create.ScreenshotDocument({ title: 'screen recording', _fitWidth: true, _width: 400, _height: 200, mediaState: 'pendingRecording' });
         //Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc);
-        CollectionDockingView.AddSplit(doc, 'right');
+        CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
     };
 
     @computed
@@ -733,11 +733,12 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
                 doc._currentFrame = 0;
                 CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
             }
-            CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0);
+            CollectionFreeFormDocumentView.updateKeyframe(undefined, childDocs, currentFrame || 0);
             doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame);
         }
     }
 
+    _keyTimer: NodeJS.Timeout | undefined;
     @undoBatch
     @action
     nextKeyframe = (): void => {
@@ -746,7 +747,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
             this.document._currentFrame = 0;
             CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
         }
-        CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0);
+        this._keyTimer = CollectionFreeFormDocumentView.updateKeyframe(this._keyTimer, this.childDocs, currentFrame || 0);
         this.document._currentFrame = Math.max(0, (currentFrame || 0) + 1);
         this.document.lastFrame = Math.max(NumCast(this.document._currentFrame), NumCast(this.document.lastFrame));
     };
@@ -758,7 +759,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
             this.document._currentFrame = 0;
             CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
         }
-        CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
+        this._keyTimer = CollectionFreeFormDocumentView.gotoKeyframe(this._keyTimer, this.childDocs.slice());
         this.document._currentFrame = Math.max(0, (currentFrame || 0) - 1);
     };
 
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 13984171c..af1fb175a 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -196,11 +196,12 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 () => SelectionManager.Views().some(v => v.topMost && v.props.Document === doc),
                 action(selected => {
                     if (selected) this._activated = true;
-                    const toggle = tab.element[0].children[1].children[0] as HTMLInputElement;
+                    const toggle = tab.element[0].children[2].children[0] as HTMLInputElement;
                     selected && tab.contentItem !== tab.header.parent.getActiveContentItem() && UndoManager.RunInBatch(() => tab.header.parent.setActiveContentItem(tab.contentItem), 'tab switch');
-                    // toggle.style.fontWeight = selected ? "bold" : "";
+                    toggle.style.fontWeight = selected ? 'bold' : '';
                     // toggle.style.textTransform = selected ? "uppercase" : "";
-                })
+                }),
+                { fireImmediately: true }
             );
 
             // highlight the tab when the tab document is brushed in any part of the UI
@@ -378,14 +379,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
         if (options?.willZoom !== false && shrinkwrap && this._document) {
             const focusSpeed = options.zoomTime ?? 500;
             shrinkwrap();
-            this._document._viewTransition = `transform ${focusSpeed}ms`;
-            setTimeout(
-                action(() => {
-                    this._document!._viewTransition = undefined;
-                    options?.afterFocus?.(false);
-                }),
-                focusSpeed
-            );
+            this._view?.setViewTransition('transform', focusSpeed, () => options?.afterFocus?.(false));
         } else {
             options?.afterFocus?.(false);
         }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index be20bf207..c57810a98 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -270,12 +270,12 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
                     </marker>
                     <filter id="outline">
                         <feMorphology in="SourceAlpha" result="expanded" operator="dilate" radius="1" />
-                        <feFlood flood-color={`${Colors.DARK_GRAY}`} />
+                        <feFlood floodColor={`${Colors.DARK_GRAY}`} />
                         <feComposite in2="expanded" operator="in" />
                         <feComposite in="SourceGraphic" />
                     </filter>
                     <filter x="0" y="0" width="1" height="1" id={`${link[Id] + 'background'}`}>
-                        <feFlood flood-color={`${StrCast(link._backgroundColor, 'white')}`} result="bg" />
+                        <feFlood floodColor={`${StrCast(link._backgroundColor, 'white')}`} result="bg" />
                         <feMerge>
                             <feMergeNode in="bg" />
                             <feMergeNode in="SourceGraphic" />
@@ -286,7 +286,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
                     filter={LinkManager.currentLink === link ? 'url(#outline)' : ''}
                     fill="pink"
                     stroke="antiquewhite"
-                    stroke-width="4"
+                    strokeWidth="4"
                     className="collectionfreeformlinkview-linkLine"
                     style={{ pointerEvents: 'all', opacity: this._opacity, stroke, strokeWidth }}
                     onClick={this.onClickLine}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5fb3c1ac6..b8ff59a14 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,4 +1,5 @@
 import { Bezier } from 'bezier-js';
+import { Colors } from 'browndash-components';
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
@@ -47,7 +48,7 @@ import { StyleProp } from '../../StyleProvider';
 import { CollectionSubView } from '../CollectionSubView';
 import { TreeViewType } from '../CollectionTreeView';
 import { TabDocView } from '../TabDocView';
-import { computePivotLayout, computePassLayout as computePassLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines';
+import { computePassLayout, computePivotLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines';
 import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors';
 import './CollectionFreeFormView.scss';
 import { MarqueeView } from './MarqueeView';
@@ -108,7 +109,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
-    @observable _viewTransition: number = 0; // sets the pan/zoom transform ease time- used by nudge(), focus() etc to smoothly zoom/pan.  set to 0 to use document's transition time or default of 0
+    @observable _panZoomTransition: number = 0; // sets the pan/zoom transform ease time- used by nudge(), focus() etc to smoothly zoom/pan.  set to 0 to use document's transition time or default of 0
     @observable _hLines: number[] | undefined;
     @observable _vLines: number[] | undefined;
     @observable _firstRender = true; // this turns off rendering of the collection's content so that there's instant feedback when a tab is switched of what content will be shown.
@@ -176,6 +177,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return this.getContainerTransform().translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform);
     }
 
+    _keyTimer: NodeJS.Timeout | undefined;
     changeKeyFrame = (back = false) => {
         const currentFrame = Cast(this.Document._currentFrame, 'number', null);
         if (currentFrame === undefined) {
@@ -183,14 +185,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
         }
         if (back) {
-            CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
+            this._keyTimer = CollectionFreeFormDocumentView.gotoKeyframe(this._keyTimer, this.childDocs.slice());
             this.Document._currentFrame = Math.max(0, (currentFrame || 0) - 1);
         } else {
-            CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0);
+            this._keyTimer = CollectionFreeFormDocumentView.updateKeyframe(this._keyTimer, this.childDocs, currentFrame || 0);
             this.Document._currentFrame = Math.max(0, (currentFrame || 0) + 1);
             this.Document.lastFrame = Math.max(NumCast(this.Document._currentFrame), NumCast(this.Document.lastFrame));
         }
     };
+    @observable _keyframeEditing = false;
+    @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);
@@ -712,6 +717,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @action
     pan = (e: PointerEvent | React.Touch | { clientX: number; clientY: number }): void => {
+        this.props.DocumentView?.().clearViewTransition();
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
         this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true);
         this._lastX = e.clientX;
@@ -1079,7 +1085,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             }
         }
         if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc || DocListCast(Doc.MyOverlayDocs?.data).includes(this.Document)) {
-            this._viewTransition = panTime;
+            this._panZoomTransition = panTime;
             const scale = this.getLocalTransform().inverse().Scale;
             const minScale = NumCast(this.rootDoc._viewScaleMin, 1);
             const minPanX = NumCast(this.rootDoc._panXMin, 0);
@@ -1098,7 +1104,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             this.setPan(NumCast(this.layoutDoc._panX) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), NumCast(this.layoutDoc._panY) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(), nudgeTime, true);
             this._lastNudge && clearTimeout(this._lastNudge);
             this._lastNudge = setTimeout(
-                action(() => (this._viewTransition = 0)),
+                action(() => (this._panZoomTransition = 0)),
                 nudgeTime
             );
             return true;
@@ -1127,7 +1133,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @action
     zoomSmoothlyAboutPt(docpt: number[], scale: number, transitionTime = 500) {
         if (this.Document._isGroup) return;
-        this._viewTransition = transitionTime;
+        this._panZoomTransition = transitionTime;
         const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
         this.layoutDoc[this.scaleFieldKey] = scale;
         const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
@@ -1135,7 +1141,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const newpan = this.getTransform().transformDirection(scrDelta.x, scrDelta.y);
         this.layoutDoc._panX = NumCast(this.layoutDoc._panX) - newpan[0];
         this.layoutDoc._panY = NumCast(this.layoutDoc._panY) - newpan[1];
-        return new Promise<number>(res => setTimeout(() => res(runInAction(() => (this._viewTransition = 0))), this._viewTransition)); // set transition to be smooth, then reset
+        return new Promise<number>(res => setTimeout(() => res(runInAction(() => (this._panZoomTransition = 0))), this._panZoomTransition)); // set transition to be smooth, then reset
     }
 
     focusDocument = (doc: Doc, options: DocFocusOptions) => {
@@ -1185,14 +1191,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 doc.hidden && Doc.UnHighlightDoc(doc);
                 const resetView = options?.afterFocus ? await options?.afterFocus(moved) : ViewAdjustment.doNothing;
                 if (resetView) {
-                    const restoreState = (!LightboxView.LightboxDoc || LightboxView.LightboxDoc === this.props.Document) && savedState;
+                    const restoreState = savedState;
                     if (typeof restoreState !== 'boolean') {
                         this.Document._panX = restoreState.panX;
                         this.Document._panY = restoreState.panY;
                         this.Document[this.scaleFieldKey] = restoreState.scale;
                     }
                 }
-                runInAction(() => (this._viewTransition = 0));
+                runInAction(() => (this._panZoomTransition = 0));
                 return resetView;
             };
             const xf = !cantTransform
@@ -1364,7 +1370,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             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),
+            opacity: this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity),
             zIndex: Cast(zIndex, 'number'),
             width: Cast(childDocLayout._width, 'number'),
             height: Cast(childDocLayout._height, 'number'),
@@ -1541,7 +1547,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     scrollFocus = (anchor: Doc, options: DocFocusOptions) => {
         const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500;
         return PresBox.restoreTargetDocView(
-            this.rootDoc, //
+            this.props.DocumentView?.(), //
             { pinDocLayout: BoolCast(anchor.presPinDocLayout) },
             anchor,
             focusSpeed,
@@ -1869,6 +1875,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         DragManager.SetSnapLines(horizLines, vertLines);
     };
 
+    incrementalRendering = () => this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])).length !== 0;
+
     incrementalRender = action(() => {
         if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) {
             const unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
@@ -1944,7 +1952,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         zoomScaling={this.zoomScaling}
                         presPaths={this.showPresPaths}
                         presPinView={BoolCast(this.Document.presPinView)}
-                        transition={this._viewTransition ? `transform ${this._viewTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', null)}
+                        transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', null)}
                         viewDefDivClick={this.props.viewDefDivClick}>
                         {this.children}
                     </CollectionFreeFormViewPannableContents>
@@ -2265,6 +2273,11 @@ ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) {
 ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) {
     !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true);
 });
+ScriptingGlobals.add(function curKeyFrame(readOnly: boolean) {
+    const selView = SelectionManager.Views();
+    if (readOnly) return selView[0].ComponentView?.getKeyFrameEditing?.() ? Colors.MEDIUM_BLUE : undefined;
+    runInAction(() => selView[0].ComponentView?.setKeyFrameEditing?.(!selView[0].ComponentView?.getKeyFrameEditing?.()));
+});
 ScriptingGlobals.add(function pinWithView(readOnly: boolean, pinDocContent: boolean) {
     !readOnly && SelectionManager.Views().forEach(view => TabDocView.PinDoc(view.rootDoc, { pinDocContent, pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()) }));
 });
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 9df3e195f..7c1137292 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -19,7 +19,6 @@ import { undoBatch, UndoManager } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
 import { OpenWhere } from '../../nodes/DocumentView';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
-import { PresBox } from '../../nodes/trails/PresBox';
 import { VideoBox } from '../../nodes/VideoBox';
 import { pasteImageBitmap } from '../../nodes/WebBoxRenderer';
 import { PreviewCursor } from '../../PreviewCursor';
@@ -244,7 +243,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         if (!(e.nativeEvent as any).marqueeHit) {
             (e.nativeEvent as any).marqueeHit = true;
             // allow marquee if right click OR alt+left click OR in adding presentation slide & left key drag mode
-            if (e.button === 2 || (e.button === 0 && e.altKey) || (PresBox.startMarquee && e.button === 0)) {
+            if (e.button === 2 || (e.button === 0 && e.altKey)) {
                 // if (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))) {
                 this.setPreviewCursor(e.clientX, e.clientY, true, false);
                 // (!e.altKey) && e.stopPropagation(); // bcz: removed so that you can alt-click on button in a collection to switch link following behaviors.
@@ -273,9 +272,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             this.cleanupInteractions(true); // stop listening for events if another lower-level handle (e.g. another Marquee) has stopPropagated this
         }
         e.altKey && e.preventDefault();
-        if (PresBox.startMarquee) {
-            e.stopPropagation();
-        }
     };
 
     @action
@@ -296,11 +292,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             document.removeEventListener('pointerdown', hideMarquee, true);
             document.removeEventListener('wheel', hideMarquee, true);
         };
-        if (PresBox.startMarquee) {
-            this.pinWithView();
-            PresBox.startMarquee = false;
-        }
-        if (!this._commandExecuted && Math.abs(this.Bounds.height * this.Bounds.width) > 100 && !PresBox.startMarquee) {
+        if (!this._commandExecuted && Math.abs(this.Bounds.height * this.Bounds.width) > 100) {
             MarqueeOptionsMenu.Instance.createCollection = this.collection;
             MarqueeOptionsMenu.Instance.delete = this.delete;
             MarqueeOptionsMenu.Instance.summarize = this.summary;
@@ -686,7 +678,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                 className="marqueeView"
                 style={{
                     overflow: StrCast(this.props.Document._overflow),
-                    cursor: [InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool) || this._visible || PresBox.startMarquee ? 'crosshair' : 'pointer',
+                    cursor: [InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool) || this._visible ? 'crosshair' : 'pointer',
                 }}
                 onDragOver={e => e.preventDefault()}
                 onScroll={e => (e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0)}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 868822fbf..bf1f13a06 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -115,45 +115,30 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         });
     }
 
-    public static updateKeyframe(docs: Doc[], time: number, targetDoc?: Doc) {
+    public static updateKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], time: number, targetDoc?: Doc) {
+        if (timer) clearTimeout(timer);
+        const newTimer = DocumentView.SetViewTransition(docs, 'all', 1000, undefined, true);
         const timecode = Math.round(time);
-        docs.forEach(
-            action(doc => {
-                doc._viewTransition = doc.dataTransition = 'all 1s';
-                CollectionFreeFormDocumentView.animFields.forEach(val => {
-                    const findexed = Cast(doc[`${val.key}-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);
-                });
-                CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => {
-                    const findexed = Cast(doc[`${val}-indexed`], listSpec(InkField), null);
-                    findexed?.length <= timecode + 1 && findexed.push(undefined as any);
-                });
-            })
-        );
-        setTimeout(
-            () =>
-                docs.forEach(doc => {
-                    doc._viewTransition = undefined;
-                    doc.dataTransition = 'inherit';
-                }),
-            1010
-        );
+        docs.forEach(doc => {
+            CollectionFreeFormDocumentView.animFields.forEach(val => {
+                const findexed = Cast(doc[`${val.key}-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);
+            });
+            CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => {
+                const findexed = Cast(doc[`${val}-indexed`], listSpec(InkField), null);
+                findexed?.length <= timecode + 1 && findexed.push(undefined as any);
+            });
+        });
+        return newTimer;
     }
 
-    public static gotoKeyframe(docs: Doc[], duration = 1000) {
-        docs.forEach(doc => (doc._viewTransition = doc.dataTransition = `all ${duration}ms`));
-        setTimeout(
-            () =>
-                docs.forEach(doc => {
-                    doc._viewTransition = undefined;
-                    doc.dataTransition = 'inherit';
-                }),
-            1010
-        );
+    public static gotoKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], duration = 1000) {
+        if (timer) clearTimeout(timer);
+        return DocumentView.SetViewTransition(docs, 'all', duration, undefined, true);
     }
 
     public static setupZoom(doc: Doc, targDoc: Doc) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index fb20887cb..4abfef563 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -127,6 +127,7 @@ export interface DocComponentView {
     Pause?: () => void;
     setFocus?: () => void;
     componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
+    incrementalRendering?: () => void;
     fieldKey?: string;
     annotationKey?: string;
     getTitle?: () => string;
@@ -238,7 +239,7 @@ export interface DocumentViewInternalProps extends DocumentViewProps {
 @observer
 export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps>() {
     public static SelectAfterContextMenu = true; //  whether a document should be selected after it's contextmenu is triggered.
-    _animateScaleTime = 300; // milliseconds;
+    @observable _animateScaleTime: Opt<number>; // milliseconds for animating between views.  defaults to 300 if not uset
     @observable _animateScalingTo = 0;
     @observable _pendingDoubleClick = false;
     private _disposers: { [name: string]: IReactionDisposer } = {};
@@ -259,6 +260,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     private get topMost() {
         return this.props.renderDepth === 0 && !LightboxView.LightboxDoc;
     }
+    public get animateScaleTime() {
+        return this._animateScaleTime ?? 300;
+    }
     public get displayName() {
         return 'DocumentView(' + this.props.Document.title + ')';
     } // this makes mobx trace() statements more descriptive
@@ -1396,7 +1400,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         fontFamily: StrCast(this.Document._fontFamily, 'inherit'),
                         fontSize: Cast(this.Document._fontSize, 'string', null),
                         transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined,
-                        transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform ${this._animateScaleTime / 1000}s ease-${this._animateScalingTo < 1 ? 'in' : 'out'}`,
+                        transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform ${this.animateScaleTime / 1000}s ease-${this._animateScalingTo < 1 ? 'in' : 'out'}`,
                     }}>
                     {this.innards}
                     {this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <div className="documentView-contentBlocker" /> : null}
@@ -1476,7 +1480,37 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return 'DocumentView(' + this.props.Document?.title + ')';
     } // this makes mobx trace() statements more descriptive
     public ContentRef = React.createRef<HTMLDivElement>();
+    public ViewTimer: NodeJS.Timeout | undefined; // timer for res
     private _disposers: { [name: string]: IReactionDisposer } = {};
+    public clearViewTransition = () => {
+        this.ViewTimer && clearTimeout(this.ViewTimer);
+        this.rootDoc._viewTransition = undefined;
+    };
+    public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => {
+        this.rootDoc._viewTransition = `${transProp} ${timeInMs}ms`;
+        if (dataTrans) this.rootDoc._dataTransition = `${transProp} ${timeInMs}ms`;
+        this.ViewTimer && clearTimeout(this.ViewTimer);
+        return (this.ViewTimer = setTimeout(() => {
+            this.rootDoc._viewTransition = undefined;
+            this.rootDoc._dataTransition = 'inherit';
+            afterTrans?.();
+        }, timeInMs + 10));
+    };
+    public static SetViewTransition(docs: Doc[], transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) {
+        docs.forEach(doc => {
+            doc._viewTransition = `${transProp} ${timeInMs}ms`;
+            dataTrans && (doc.dataTransition = `${transProp} ${timeInMs}ms`);
+        });
+        return setTimeout(
+            () =>
+                docs.forEach(doc => {
+                    doc._viewTransition = undefined;
+                    dataTrans && (doc.dataTransition = 'inherit');
+                    afterTrans?.();
+                }),
+            timeInMs + 10
+        );
+    }
 
     public static showBackLinks(linkSource: Doc) {
         const docid = Doc.CurrentUserEmail + Doc.GetProto(linkSource)[Id] + '-pivotish';
@@ -1617,15 +1651,21 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return { left, top, right, bottom, center: this.ComponentView?.getCenter?.(xf) };
     };
 
-    public iconify(finished?: () => void) {
+    public iconify(finished?: () => void, animateTime?: number) {
         this.ComponentView?.updateIcon?.();
+        const animTime = this.docView?._animateScaleTime;
+        runInAction(() => this.docView && animateTime !== undefined && (this.docView._animateScaleTime = animateTime));
+        const finalFinished = action(() => {
+            finished?.();
+            this.docView && (this.docView._animateScaleTime = animTime);
+        });
         const layoutKey = Cast(this.Document.layoutKey, 'string', null);
         if (layoutKey !== 'layout_icon') {
-            this.switchViews(true, 'icon', finished);
+            this.switchViews(true, 'icon', finalFinished);
             if (layoutKey && layoutKey !== 'layout' && layoutKey !== 'layout_icon') this.Document.deiconifyLayout = layoutKey.replace('layout_', '');
         } else {
             const deiconifyLayout = Cast(this.Document.deiconifyLayout, 'string', null);
-            this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finished);
+            this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finalFinished);
             this.Document.deiconifyLayout = undefined;
             this.props.bringToFront(this.rootDoc);
         }
@@ -1651,10 +1691,10 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                         this.docView && (this.docView._animateScalingTo = 0);
                         finished?.();
                     }),
-                    this.docView!._animateScaleTime - 10
+                    this.docView ? Math.max(0, this.docView.animateScaleTime - 10) : 0
                 );
             }),
-            this.docView!._animateScaleTime - 10
+            this.docView ? Math.max(0, this.docView?.animateScaleTime - 10) : 0
         );
     });
 
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 416107859..19f5e9e29 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -65,7 +65,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     scrollFocus = (anchor: Doc, options: DocFocusOptions) => {
         const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500;
         return PresBox.restoreTargetDocView(
-            this.rootDoc, //
+            this.props.DocumentView?.(), //
             { pinDocLayout: BoolCast(anchor.presPinDocLayout) },
             anchor,
             focusSpeed,
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index b19c4a9e2..88aac67a7 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -209,7 +209,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1;
         this._initialScrollTarget = doc;
-        PresBox.restoreTargetDocView(this.rootDoc, {}, doc, options.zoomTime ?? 500, { pannable: doc.presPinData ? true : false });
+        PresBox.restoreTargetDocView(this.props.DocumentView?.(), {}, doc, options.zoomTime ?? 500, { pannable: doc.presPinData ? true : false });
         return this._pdfViewer?.scrollFocus(doc, NumCast(doc.presPinViewScroll, NumCast(doc.y)), options) ?? (didToggle ? 1 : undefined);
     };
     getAnchor = () => {
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 607cd6187..a8f78edd5 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -961,7 +961,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     // renders CollectionStackedTimeline
     @computed get renderTimeline() {
         return (
-            <div className="videoBox-stackPanel" style={{ transition: this.transition, height: `${100 - this.heightPercent}%` }}>
+            <div className="videoBox-stackPanel" style={{ transition: this.transition, height: `${100 - this.heightPercent}%`, display: this.heightPercent === 100 ? 'none' : '' }}>
                 <CollectionStackedTimeline
                     ref={action((r: any) => (this._stackedTimeline = r))}
                     {...this.props}
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index fe8c85e5e..e477d7ae2 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -510,8 +510,12 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
             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` }}>
+            case ButtonType.TextButton: 
+                    // Script for checking the outcome of the toggle
+                    const script = ScriptCast(this.rootDoc.script);
+                    const checkResult = script?.script.run({ _readOnly_: true }).result;
+                    button = (
+                    <div className={`menuButton ${this.type}`} style={{ color, backgroundColor:checkResult ?? 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()}
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 4073677f3..169f51dac 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -5,7 +5,7 @@ import { action, computed, IReactionDisposer, observable, ObservableSet, reactio
 import { observer } from 'mobx-react';
 import { ColorState, SketchPicker } from 'react-color';
 import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
-import { Doc, DocListCast, FieldResult, StrListCast } from '../../../../fields/Doc';
+import { Doc, DocListCast, FieldResult, Opt, StrListCast } from '../../../../fields/Doc';
 import { Copy, Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
@@ -13,9 +13,9 @@ import { ObjectField } from '../../../../fields/ObjectField';
 import { listSpec } from '../../../../fields/Schema';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { AudioField } from '../../../../fields/URLField';
-import { emptyFunction, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../Utils';
+import { returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
-import { Docs, DocumentOptions } from '../../../documents/Documents';
+import { Docs } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
@@ -30,12 +30,11 @@ import { ViewBoxBaseComponent } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
 import { LightboxView } from '../../LightboxView';
 import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentView';
+import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { ScriptingBox } from '../ScriptingBox';
 import './PresBox.scss';
 import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
-import { map } from 'bluebird';
-import { DocFocusOptions, OpenWhere, OpenWhereMod } from '../DocumentView';
 const { Howl } = require('howler');
 
 export interface PinProps {
@@ -97,7 +96,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public selectedArray = new ObservableSet<Doc>();
 
     @observable public static Instance: PresBox;
-    @observable static startMarquee: boolean = false; // onclick "+ new slide" in presentation mode, set as true, then when marquee selection finish, onPointerUp automatically triggers PinWithView
 
     @observable _isChildActive = false;
     @observable _moveOnFromAudio: boolean = true;
@@ -140,7 +138,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return Cast(this.activeItem?.presentationTargetDoc, Doc, null);
     }
     @computed get scrollable() {
-        if (this.targetDoc.type === DocumentType.PDF || this.targetDoc.type === DocumentType.WEB || this.targetDoc.type === DocumentType.RTF || this.targetDoc._viewType === CollectionViewType.Stacking) return true;
+        if ([DocumentType.PDF, DocumentType.WEB, DocumentType.RTF].includes(this.targetDoc.type as DocumentType) || this.targetDoc._viewType === CollectionViewType.Stacking) return true;
         return false;
     }
     @computed get panable() {
@@ -214,16 +212,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         PresBox.Instance = this;
     };
 
-    // There are still other internal frames and should go through all frames before going to next slide
-    nextInternalFrame = (targetDoc: Doc, activeItem: Doc) => {
-        const currentFrame = Cast(targetDoc?._currentFrame, 'number', null);
-        const childDocs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]);
-        targetDoc._viewTransition = 'all 1s';
-        setTimeout(() => (targetDoc._viewTransition = undefined), 1010);
-        this.nextKeyframe(targetDoc, activeItem);
-        targetDoc.keyFrameEditing = true;
-    };
-
     _mediaTimer!: [NodeJS.Timeout, Doc];
     // 'Play on next' for audio or video therefore first navigate to the audio/video before it should be played
     startTempMedia = (targetDoc: Doc, activeItem: Doc) => {
@@ -279,23 +267,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const targetDoc: Doc = this.targetDoc;
         const prevItem = Cast(this.childDocs[Math.max(0, this.itemIndex - 1)], Doc, null);
         const prevTargetDoc = Cast(prevItem.presentationTargetDoc, Doc, null);
-        const lastFrame = Cast(targetDoc.lastFrame, 'number', null);
-        const curFrame = NumCast(targetDoc._currentFrame);
         let prevSelected = this.itemIndex;
         // Functionality for group with up
         let didZoom = activeItem.presMovement;
         for (; prevSelected > 0 && this.childDocs[Math.max(0, prevSelected - 1)].groupWithUp; prevSelected--) {
             didZoom = didZoom === 'none' ? this.childDocs[prevSelected].presMovement : didZoom;
         }
-        if (lastFrame !== undefined && curFrame >= 1) {
-            // Case 1: There are still other frames and should go through all frames before going to previous slide
-            this.prevKeyframe(targetDoc, activeItem);
-        } else if (activeItem && this.childDocs[this.itemIndex - 1] !== undefined) {
+        if (activeItem && this.childDocs[this.itemIndex - 1] !== undefined) {
             // Case 2: There are no other frames so it should go to the previous slide
             prevSelected = Math.max(0, prevSelected - 1);
             this.nextSlide(prevSelected);
             this.rootDoc._itemIndex = prevSelected;
-            if (NumCast(prevTargetDoc.lastFrame) > 0) prevTargetDoc._currentFrame = NumCast(prevTargetDoc.lastFrame);
         } else if (this.childDocs[this.itemIndex - 1] === undefined && this.layoutDoc.presLoop) {
             // Case 3: Pres loop is on so it should go to the last slide
             this.gotoDocument(this.childDocs.length - 1, activeItem);
@@ -313,14 +295,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             this.rootDoc._itemIndex = index;
             const activeItem: Doc = this.activeItem;
             const targetDoc: Doc = this.targetDoc;
-            let focusSpeed = 500;
             if (activeItem.presActiveFrame !== undefined) {
                 const transTime = NumCast(activeItem.presTransition, 500);
                 const context = DocCast(DocCast(activeItem.presentationTargetDoc).context);
                 if (context) {
-                    const contextView = DocumentManager.Instance.getFirstDocumentView(context);
-                    if (contextView?.ComponentView) {
-                        CollectionFreeFormDocumentView.gotoKeyframe((contextView.ComponentView as CollectionFreeFormView).childDocs.slice(), transTime);
+                    const ffview = DocumentManager.Instance.getFirstDocumentView(context)?.ComponentView as CollectionFreeFormView;
+                    if (ffview) {
+                        this._keyTimer = CollectionFreeFormDocumentView.gotoKeyframe(this._keyTimer, ffview.childDocs.slice(), transTime);
                         context._currentFrame = NumCast(activeItem.presActiveFrame);
                     }
                 }
@@ -335,9 +316,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             if (this.layoutDoc.presStatus !== PresStatus.Edit && (targetDoc.type === DocumentType.AUDIO || targetDoc.type === DocumentType.VID) && activeItem.mediaStart === 'auto') {
                 this.startTempMedia(targetDoc, activeItem);
             }
-            if (targetDoc?.lastFrame !== undefined) {
-                targetDoc._currentFrame = 0;
-            }
             if (!group) this.clearSelectedArray();
             this.childDocs[index] && this.addToSelectedArray(this.childDocs[index]); //Update selected array
             this.turnOffEdit();
@@ -345,14 +323,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             this.onHideDocument(); //Handles hide after/before
         }
     });
-    static pinDataTypes(target: Doc): { scrollable?: boolean; pannable?: boolean; temporal?: boolean; clippable?: boolean; dataview?: boolean; textview?: boolean; poslayoutview?: boolean; dataannos?: boolean } {
-        const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(target.type as any) || target._viewType === CollectionViewType.Stacking;
-        const pannable = [DocumentType.IMG, DocumentType.PDF].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);
-        const dataview = [DocumentType.INK, DocumentType.COL, DocumentType.IMG].includes(target.type as any) && target.activeFrame === undefined;
-        const poslayoutview = [DocumentType.COL].includes(target.type as any) && target.activeFrame === undefined;
-        const textview = [DocumentType.RTF].includes(target.type as any) && target.activeFrame === undefined;
+    static pinDataTypes(target?: Doc): { scrollable?: boolean; pannable?: boolean; temporal?: boolean; clippable?: boolean; dataview?: boolean; textview?: boolean; poslayoutview?: boolean; dataannos?: boolean } {
+        const targetType = target?.type as any;
+        const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetType) || target?._viewType === CollectionViewType.Stacking;
+        const pannable = [DocumentType.IMG, DocumentType.PDF].includes(targetType) || (targetType === DocumentType.COL && target?._viewType === CollectionViewType.Freeform);
+        const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(targetType);
+        const clippable = [DocumentType.COMPARISON].includes(targetType);
+        const dataview = [DocumentType.INK, DocumentType.COL, DocumentType.IMG].includes(targetType) && target?.activeFrame === undefined;
+        const poslayoutview = [DocumentType.COL].includes(targetType) && target?.activeFrame === undefined;
+        const textview = [DocumentType.RTF].includes(targetType) && target?.activeFrame === undefined;
         const dataannos = false;
         return { scrollable, pannable, temporal, clippable, dataview, textview, poslayoutview, dataannos };
     }
@@ -360,8 +339,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @action
     playAnnotation = (anno: AudioField) => {};
     @action
-    static restoreTargetDocView(bestTarget: Doc, pinProps: PinProps | undefined, activeItem: Doc, transTime: number, pinDataTypes = this.pinDataTypes(bestTarget)) {
-        const presTransitionTime = `all ${transTime}ms`;
+    static restoreTargetDocView(bestTargetView: Opt<DocumentView>, pinProps: PinProps | undefined, activeItem: Doc, transTime: number, pinDataTypes = this.pinDataTypes(bestTargetView?.rootDoc)) {
+        if (!bestTargetView) return;
+        const bestTarget = bestTargetView.rootDoc;
         let changed = false;
         if (pinProps?.pinDocLayout) {
             if (
@@ -420,7 +400,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 .map(str => JSON.parse(str) as { id: string; x: number; y: number; w: number; h: number })
                 .forEach(data => {
                     const doc = DocServer.GetCachedRefField(data.id) as Doc;
-                    doc._dataTransition = presTransitionTime;
+                    doc._dataTransition = `all ${transTime}ms`;
                     doc.x = data.x;
                     doc.y = data.y;
                     doc._width = data.w;
@@ -456,8 +436,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             }
         }
         if (changed) {
-            bestTarget._viewTransition = presTransitionTime;
-            return setTimeout(() => (bestTarget._viewTransition = undefined), transTime + 10);
+            return bestTargetView.setViewTransition('all', transTime);
         }
     }
 
@@ -519,8 +498,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             pinDoc.presPinViewBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]);
         }
     }
-
-    static _navTimer: NodeJS.Timeout | undefined;
     /**
      * This method makes sure that cursor navigates to the element that
      * has the option open and last in the group.
@@ -549,55 +526,59 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const selViewCache = Array.from(this.selectedArray);
         const dragViewCache = Array.from(this._dragArray);
         const eleViewCache = Array.from(this._eleArray);
-        const self = this;
         const resetSelection = action(() => {
-            const presDocView = DocumentManager.Instance.getDocumentView(self.rootDoc);
+            const presDocView = DocumentManager.Instance.getDocumentView(this.rootDoc);
             if (presDocView) SelectionManager.SelectView(presDocView, false);
-            self.rootDoc.presStatus = presStatus;
-            self.clearSelectedArray();
-            selViewCache.forEach(doc => self.addToSelectedArray(doc));
-            self._dragArray.splice(0, self._dragArray.length, ...dragViewCache);
-            self._eleArray.splice(0, self._eleArray.length, ...eleViewCache);
+            this.rootDoc.presStatus = presStatus;
+            this.clearSelectedArray();
+            selViewCache.forEach(doc => this.addToSelectedArray(doc));
+            this._eleArray.splice(0, this._eleArray.length, ...eleViewCache);
         });
-        const openInTab = (doc: Doc, finished?: () => void) => {
-            (collectionDocView ?? this).props.addDocTab(doc, OpenWhere.add);
+        const createDocView = (doc: Doc, finished?: () => void) => {
+            DocumentManager.Instance.AddViewRenderedCb(doc, () => finished?.());
+            (collectionDocView ?? this).props.addDocTab(doc, OpenWhere.lightbox);
             this.layoutDoc.presCollection = targetDoc;
-            // this still needs some fixing
-            setTimeout(resetSelection, 500);
-            if (doc !== targetDoc) {
-                setTimeout(finished ?? emptyFunction, 100); /// give it some time to create the targetDoc if we're opening up its context
-            } else {
-                finished?.();
-            }
         };
-        PresBox.NavigateToTarget(targetDoc, activeItem, openInTab, srcContext, includesDoc || tab ? undefined : resetSelection);
+        PresBox.NavigateToTarget(targetDoc, activeItem, createDocView, srcContext, includesDoc || tab ? undefined : resetSelection);
     };
 
-    static NavigateToTarget(targetDoc: Doc, activeItem: Doc, openInTab: any, srcContext: Doc, finished?: () => void) {
+    static NavigateToTarget(targetDoc: Doc, activeItem: Doc, createDocView: any, srcContext: Doc, finished?: () => void) {
+        if (activeItem.presMovement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) {
+            (DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.();
+            return;
+        }
+        const restoreLayout = () => {
+            // After navigating to the document, if it is added as a presPinView then it will
+            // adjust the pan and scale to that of the pinView when it was added.
+            const pinDocLayout = (BoolCast(activeItem.presPinLayout) || BoolCast(activeItem.presPinView)) && DocCast(targetDoc.context)?._currentFrame === undefined;
+            if (activeItem.presPinData || activeItem.presPinView || pinDocLayout) {
+                // targetDoc may or may not be displayed.  so get the first available document (or alias) view that matches targetDoc and use it
+                PresBox.restoreTargetDocView(DocumentManager.Instance.getFirstDocumentView(targetDoc), { pinDocLayout }, activeItem, NumCast(activeItem.presTransition, 500));
+            }
+        };
         // If openDocument is selected then it should open the document for the user
         if (activeItem.openDocument) {
             LightboxView.SetLightboxDoc(targetDoc); // openInTab(targetDoc);
-        } else if (targetDoc && activeItem.presMovement !== PresMovement.None) {
-            LightboxView.SetLightboxDoc(undefined);
-            const options: DocFocusOptions = {
-                willZoom: activeItem.presMovement !== PresMovement.Pan,
-                zoomScale: NumCast(activeItem.presZoom, 1),
-                zoomTime: activeItem.presMovement === PresMovement.Jump ? 0 : NumCast(activeItem.presTransition, 500),
-                noSelect: true,
-                originatingDoc: activeItem,
-            };
-            DocumentManager.Instance.jumpToDocument(targetDoc, options, openInTab, srcContext ? [srcContext] : [], finished);
-        } else if (activeItem.presMovement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) {
-            (DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.();
-        }
-        // After navigating to the document, if it is added as a presPinView then it will
-        // adjust the pan and scale to that of the pinView when it was added.
-        const pinDocLayout = (BoolCast(activeItem.presPinLayout) || BoolCast(activeItem.presPinView)) && DocCast(targetDoc.context)?._currentFrame === undefined;
-        if (activeItem.presPinData || activeItem.presPinView || pinDocLayout) {
-            PresBox._navTimer && clearTimeout(PresBox._navTimer);
-            // targetDoc may or may not be displayed.  this gets the first available document (or alias) view that matches targetDoc
-            const bestTargetView = DocumentManager.Instance.getFirstDocumentView(targetDoc);
-            if (bestTargetView?.props.Document) PresBox._navTimer = PresBox.restoreTargetDocView(bestTargetView?.props.Document, { pinDocLayout }, activeItem, NumCast(activeItem.presTransition, 500));
+            setTimeout(restoreLayout);
+        } else {
+            if (targetDoc && activeItem.presMovement !== PresMovement.None) {
+                LightboxView.SetLightboxDoc(undefined);
+                const options: DocFocusOptions = {
+                    willZoom: activeItem.presMovement !== PresMovement.Pan,
+                    zoomScale: NumCast(activeItem.presZoom, 1),
+                    zoomTime: activeItem.presMovement === PresMovement.Jump ? 0 : NumCast(activeItem.presTransition, 500),
+                    noSelect: true,
+                    originatingDoc: activeItem,
+                };
+
+                var containerDocContext = srcContext ? [srcContext] : [];
+                while (containerDocContext.length && !DocumentManager.Instance.getDocumentView(containerDocContext[0]) && containerDocContext[0].context) {
+                    containerDocContext = [Cast(containerDocContext[0].context, Doc, null), ...containerDocContext];
+                }
+
+                DocumentManager.Instance.jumpToDocument(targetDoc, options, createDocView, containerDocContext, finished);
+            }
+            restoreLayout();
         }
     }
 
@@ -1824,32 +1805,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return undefined;
     };
 
-    // Case in which the document has keyframes to navigate to next key frame
-    @action
-    nextKeyframe = (tagDoc: Doc, curDoc: Doc): void => {
-        const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]);
-        const currentFrame = Cast(tagDoc._currentFrame, 'number', null);
-        if (currentFrame === undefined) {
-            tagDoc._currentFrame = 0;
-            // CollectionFreeFormDocumentView.setupScroll(tagDoc, 0);
-            // CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
-        }
-        CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, tagDoc);
-        tagDoc._currentFrame = Math.max(0, (currentFrame || 0) + 1);
-        tagDoc.lastFrame = Math.max(NumCast(tagDoc._currentFrame), NumCast(tagDoc.lastFrame));
-    };
-
-    @action
-    prevKeyframe = (tagDoc: Doc, actItem: Doc): void => {
-        const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]);
-        const currentFrame = Cast(tagDoc._currentFrame, 'number', null);
-        if (currentFrame === undefined) {
-            tagDoc._currentFrame = 0;
-            // CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
-        }
-        CollectionFreeFormDocumentView.gotoKeyframe(childDocs.slice());
-        tagDoc._currentFrame = Math.max(0, (currentFrame || 0) - 1);
-    };
+    _keyTimer: NodeJS.Timeout | undefined;
 
     /**
      * Returns the collection type as a string for headers
@@ -2022,76 +1978,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         );
     }
 
-    @action
-    getList = (list: any): List<number> => list;
-
-    @action
-    updateList = (list: any): List<number> => {
-        const targetDoc: Doc = this.targetDoc;
-        const x: List<number> = list;
-        x[x.length - 1] = NumCast(targetDoc._scrollY);
-        return x;
-    };
-
-    @action
-    newFrame = () => {
-        const activeItem: Doc = this.activeItem;
-        const type: string = StrCast(this.targetDoc.type);
-        if (!activeItem.frameList) activeItem.frameList = new List<number>();
-        switch (type) {
-            case DocumentType.PDF || DocumentType.RTF || DocumentType.WEB:
-                this.updateList(activeItem.frameList);
-                break;
-        }
-    };
-
-    @computed get frameListHeader() {
-        return (
-            <div className="frameList-header">
-                &nbsp; Frames {this.panable ? <i>Panable</i> : this.scrollable ? <i>Scrollable</i> : null}
-                <div className={'frameList-headerButtons'}>
-                    <Tooltip title={<div className="dash-tooltip">{'Add frame by example'}</div>}>
-                        <div
-                            className={'headerButton'}
-                            onClick={e => {
-                                e.stopPropagation();
-                                this.newFrame();
-                            }}>
-                            <FontAwesomeIcon icon={'plus'} onPointerDown={e => e.stopPropagation()} />
-                        </div>
-                    </Tooltip>
-                    <Tooltip title={<div className="dash-tooltip">{'Edit in collection'}</div>}>
-                        <div className={'headerButton'} onClick={e => e.stopPropagation()}>
-                            <FontAwesomeIcon icon={'edit'} onPointerDown={e => e.stopPropagation()} />
-                        </div>
-                    </Tooltip>
-                </div>
-            </div>
-        );
-    }
-
-    @computed get frameList() {
-        const frameList: List<number> = this.getList(this.activeItem.frameList);
-        return !frameList ? null : (
-            <div className="frameList-container">
-                {frameList.map(value => (
-                    <div className="framList-item" />
-                ))}
-            </div>
-        );
-    }
-
-    @computed get playButtonFrames() {
-        const targetDoc = this.targetDoc;
-        return !this.targetDoc ? null : (
-            <div className="presPanel-button-frame" style={{ display: targetDoc.lastFrame !== undefined && targetDoc.lastFrame >= 0 ? 'inline-flex' : 'none' }}>
-                <div>{NumCast(targetDoc._currentFrame)}</div>
-                <div className="presPanel-divider" style={{ border: 'solid 0.5px white', height: '60%' }}></div>
-                <div>{NumCast(targetDoc.lastFrame)}</div>
-            </div>
-        );
-    }
-
     @computed get playButtons() {
         const presEnd: boolean = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1;
         const presStart: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0;
@@ -2143,7 +2029,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 </Tooltip>
                 <div className="presPanel-button-text" onClick={() => this.gotoDocument(0, this.activeItem)} style={{ display: this.props.PanelWidth() > 250 ? 'inline-flex' : 'none' }}>
                     Slide {this.itemIndex + 1} / {this.childDocs.length}
-                    {this.playButtonFrames}
                 </div>
                 <div className="presPanel-divider"></div>
                 {this.props.PanelWidth() > 250 ? (
@@ -2202,9 +2087,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         clearTimeout(this._presTimer);
     };
 
-    @action
-    startMarqueeCreateSlide = () => (PresBox.startMarquee = true);
-
     AddToMap = (treeViewDoc: Doc, index: number[]): Doc[] => {
         var indexNum = 0;
         for (let i = 0; i < index.length; i++) {
@@ -2271,7 +2153,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     </Tooltip>
                     <div className="presPanel-button-text">
                         Slide {this.itemIndex + 1} / {this.childDocs.length}
-                        {this.playButtonFrames}
                     </div>
                     <div className="presPanel-divider" />
                     <div className="presPanel-button-text" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.exitClicked, false, false)}>
-- 
cgit v1.2.3-70-g09d2

