From 4a9d6d1409327fd99c5f554caebd917a316db32b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 12 Feb 2021 11:00:29 -0500
Subject: changed lightbox to navigate within frame if next target is alreay
 there, otherwise create it.  changed focus default to call afterFocus(). 
 fixed bug of zooming on target doc to compute scale correctly.

---
 src/client/views/LightboxView.tsx | 52 ++++++++++++++++++++++++++++-----------
 1 file changed, 37 insertions(+), 15 deletions(-)

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

diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 4e9491ec6..fbfc9672f 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -9,6 +9,8 @@ import { Transform } from '../util/Transform';
 import "./LightboxView.scss";
 import { DocumentView } from './nodes/DocumentView';
 import { DefaultStyleProvider } from './StyleProvider';
+import { DocUtils } from '../documents/Documents';
+import { DocumentManager } from '../util/DocumentManager';
 
 interface LightboxViewProps {
     PanelWidth: number;
@@ -18,7 +20,16 @@ interface LightboxViewProps {
 
 @observer
 export class LightboxView extends React.Component<LightboxViewProps> {
-    @observable public static LightboxDoc: Opt<Doc>;
+    @observable static LightboxDoc: Opt<Doc>;
+    @action public static SetLightboxDoc(doc: Opt<Doc>, future?: Doc[]) {
+        LightboxView.LightboxDoc = doc;
+        if (!doc) {
+            LightboxView.LightboxFuture = LightboxView.LightboxHistory = [];
+        } else if (future) {
+            LightboxView.LightboxFuture = future;
+        }
+        return true;
+    }
     public static IsLightboxDocView(path: DocumentView[]) { return path.includes(LightboxView.LightboxDocView.current!); }
     public static LightboxHistory: (Opt<Doc>)[] = [];
     public static LightboxFuture: (Opt<Doc>)[] = [];
@@ -47,13 +58,11 @@ export class LightboxView extends React.Component<LightboxViewProps> {
         return !LightboxView.LightboxDoc ? (null) :
             <div className="lightboxView-frame"
                 onPointerDown={e => { downx = e.clientX; downy = e.clientY; }}
-                onClick={action(e => {
+                onClick={e => {
                     if (Math.abs(downx - e.clientX) < 4 && Math.abs(downy - e.clientY) < 4) {
-                        LightboxView.LightboxHistory = [];
-                        LightboxView.LightboxFuture = [];
-                        LightboxView.LightboxDoc = undefined;
+                        LightboxView.SetLightboxDoc(undefined);
                     }
-                })}  >
+                }}  >
                 <div className="lightboxView-contents" style={{
                     left: this.leftBorder,
                     top: this.topBorder,
@@ -74,7 +83,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                         ScreenToLocalTransform={this.lightboxScreenToLocal}
                         PanelWidth={this.lightboxWidth}
                         PanelHeight={this.lightboxHeight}
-                        focus={emptyFunction}
+                        focus={DocUtils.DefaultFocus}
                         parentActive={returnTrue}
                         whenActiveChanged={emptyFunction}
                         bringToFront={emptyFunction}
@@ -87,18 +96,31 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                 </div>
                 {this.navBtn(undefined, "chevron-left",
                     () => LightboxView.LightboxDoc && LightboxView.LightboxHistory.length ? "" : "none",
-                    action(e => {
+                    e => {
                         e.stopPropagation();
-                        const popped = LightboxView.LightboxHistory.pop();
-                        if (LightboxView.LightboxHistory.lastElement() !== LightboxView.LightboxFuture.lastElement()) LightboxView.LightboxFuture.push(popped);
-                        LightboxView.LightboxDoc = LightboxView.LightboxHistory.lastElement();
-                    }))}
+                        const previous = LightboxView.LightboxHistory.pop();
+                        const target = LightboxView.LightboxHistory.lastElement();
+                        const docView = target && DocumentManager.Instance.getLightboxDocumentView(target);
+                        if (docView && target) {
+                            if (LightboxView.LightboxFuture.lastElement() !== previous) LightboxView.LightboxFuture.push(previous);
+                            docView.focus(target, true, 0.9);
+                        } else {
+                            LightboxView.SetLightboxDoc(target);
+                        }
+                    })}
                 {this.navBtn(this.props.PanelWidth - Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]), "chevron-right",
                     () => LightboxView.LightboxDoc && LightboxView.LightboxFuture.length ? "" : "none",
-                    action(e => {
+                    e => {
                         e.stopPropagation();
-                        LightboxView.LightboxDoc = LightboxView.LightboxFuture.pop();
-                    }))}
+                        const target = LightboxView.LightboxFuture.pop();
+                        const docView = target && DocumentManager.Instance.getLightboxDocumentView(target);
+                        if (docView && target) {
+                            docView.focus(target, true, 0.9);
+                            if (LightboxView.LightboxHistory.lastElement() !== target) LightboxView.LightboxHistory.push(target);
+                        } else {
+                            LightboxView.SetLightboxDoc(target);
+                        }
+                    })}
 
             </div>;
     }
-- 
cgit v1.2.3-70-g09d2


From d0607264d955d90eca8178d5bfbc6a72f47fa872 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 12 Feb 2021 12:17:44 -0500
Subject: fixed subtle bug with lightbox inadvertently clearing out childDoc
 list of a collection. added shift-double-cllick to open collection in
 lightbox.

---
 src/client/documents/Documents.ts                        |  1 +
 src/client/util/LinkManager.ts                           |  8 ++------
 src/client/views/LightboxView.tsx                        |  2 +-
 src/client/views/collections/CollectionStackingView.tsx  |  2 +-
 src/client/views/collections/CollectionSubView.tsx       |  4 +++-
 .../collectionFreeForm/CollectionFreeFormView.tsx        | 16 ++++++++++------
 src/client/views/nodes/DocumentView.tsx                  |  9 +++------
 src/client/views/nodes/PDFBox.tsx                        |  2 +-
 8 files changed, 22 insertions(+), 22 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2d370ca8d..aafa58028 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -56,6 +56,7 @@ import { SearchBox } from "../views/search/SearchBox";
 import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
 import { DocumentType } from "./DocumentTypes";
 import { DocAfterFocusFunc } from "../views/nodes/DocumentView";
+import { Transform } from "../util/Transform";
 const path = require('path');
 
 const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", ""));
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index d433605f1..c39a4abda 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -106,12 +106,8 @@ export class LinkManager {
         // open up target if it's not already in view ...
         const createViewFunc = (doc: Doc, followLoc: string, finished?: Opt<() => void>) => {
             const createTabForTarget = (didFocus: boolean) => new Promise<boolean>(res => {
-                const where = StrCast(sourceDoc.followLinkLocation) || followLoc;
-                if (LightboxView.LightboxDoc) {
-                    LightboxView.SetLightboxDoc(doc);
-                } else {
-                    docViewProps.addDocTab(doc, where);
-                }
+                const where = LightboxView.LightboxDoc ? "lightbox" : StrCast(sourceDoc.followLinkLocation) || followLoc;
+                docViewProps.addDocTab(doc, where);
                 setTimeout(() => {
                     const targDocView = DocumentManager.Instance.getFirstDocumentView(doc);
                     if (targDocView) {
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index fbfc9672f..529eecacf 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -26,7 +26,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
         if (!doc) {
             LightboxView.LightboxFuture = LightboxView.LightboxHistory = [];
         } else if (future) {
-            LightboxView.LightboxFuture = future;
+            LightboxView.LightboxFuture = future.slice();
         }
         return true;
     }
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index f100247c9..6425e1625 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -181,7 +181,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
                 smoothScroll(focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500, this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
             }
         }
-        const endFocus = async (moved: boolean) => afterFocus ? await afterFocus(moved) : false;
+        const endFocus = async (moved: boolean) => afterFocus ? afterFocus(moved) : false;
         this.props.focus(this.rootDoc, willZoom, scale, (didFocus: boolean) =>
             new Promise<boolean>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)));
 
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 287bc56c2..3cbda37e2 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -112,7 +112,9 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
             const docFilters = this.docFilters();
             const docRangeFilters = this.docRangeFilters();
             const searchDocs = this.searchFilterDocs();
-            if (this.props.Document.dontRegisterView || (!docFilters.length && !docRangeFilters.length && !searchDocs.length)) return childDocs;
+            if (this.props.Document.dontRegisterView || (!docFilters.length && !docRangeFilters.length && !searchDocs.length)) {
+                return childDocs;
+            }
 
             const docsforFilter: Doc[] = [];
             childDocs.forEach((d) => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 74175c0b2..1a6c6bf0c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -50,6 +50,7 @@ import { StyleProp, StyleLayers } from "../../StyleProvider";
 import { DocumentDecorations } from "../../DocumentDecorations";
 import { FieldViewProps } from "../../nodes/FieldView";
 import { reset } from "colors";
+import { LightboxView } from "../../LightboxView";
 
 export const panZoomSchema = createSchema({
     _panX: "number",
@@ -441,7 +442,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             return;
         }
         this._hitCluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY));
-        if (e.button === 0 && (!e.shiftKey || this._hitCluster !== -1) && !e.altKey && !e.ctrlKey && this.props.active(true)) {
+        if (e.button === 0 && !e.altKey && !e.ctrlKey && this.props.active(true)) {
             document.removeEventListener("pointermove", this.onPointerMove);
             document.removeEventListener("pointerup", this.onPointerUp);
             document.addEventListener("pointermove", this.onPointerMove);
@@ -608,10 +609,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     onClick = (e: React.MouseEvent) => {
         if (this.layoutDoc.targetScale && (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) {
             if (Date.now() - this._lastTap < 300) { // reset zoom of freeform view to 1-to-1 on a double click
-                runInAction(() => DocumentLinksButton.StartLink = DocumentLinksButton.StartLinkView = undefined);
-                this.scaleAtPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1);
-                e.stopPropagation();
-                e.preventDefault();
+                if (e.shiftKey) {
+                    LightboxView.SetLightboxDoc(this.rootDoc, this.childDocs);
+                } else {
+                    this.scaleAtPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1);
+                    e.stopPropagation();
+                    e.preventDefault();
+                }
             }
             this._lastTap = Date.now();
         }
@@ -918,7 +922,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             const layoutdoc = Doc.Layout(doc);
             const savedState = { px: NumCast(this.Document._panX), py: NumCast(this.Document._panY), s: this.Document[this.scaleFieldKey], pt: this.Document._viewTransition };
             const newState = HistoryUtil.getState();
-            const cantTransform = this.props.isAnnotationOverlay || this.rootDoc._isGroup;
+            const cantTransform = this.props.isAnnotationOverlay || this.rootDoc._isGroup || this.layoutDoc._lockedTransform;
             const { px, py } = cantTransform ? savedState : this.setPanIntoView(layoutdoc, xfToCollection, willZoom ? scale || .75 : undefined);
             if (!cantTransform) {   // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection
                 newState.initializers![this.Document[Id]] = { panX: px, panY: py };
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 40dfd1643..66436fa75 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -377,7 +377,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => {
         const focusSpeed = this._componentView?.scrollFocus?.(doc, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here      
-        const endFocus = focusSpeed === undefined ? afterFocus : async (moved: boolean) => afterFocus ? await afterFocus(true) : false;
+        const endFocus = focusSpeed === undefined ? afterFocus : async (moved: boolean) => afterFocus ? afterFocus(true) : false;
         this.props.focus(docTransform ? doc : this.rootDoc, willZoom, scale, (didFocus: boolean) =>
             new Promise<boolean>(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : false), focusSpeed ?? 0)), docTransform);
 
@@ -408,10 +408,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         UndoManager.RunInBatch(() => func().result?.select === true ? this.props.select(false) : "", "on double click");
                     } else if (!Doc.IsSystem(this.props.Document)) {
                         if (this.props.Document.type !== DocumentType.LABEL) {
-                            UndoManager.RunInBatch(() => {
-                                const fullScreenDoc = Cast(this.props.Document._fullScreenView, Doc, null) || this.props.Document;
-                                this.props.addDocTab(fullScreenDoc, "lightbox");
-                            }, "double tap");
+                            UndoManager.RunInBatch(() => this.props.addDocTab((this.rootDoc._fullScreenView as Doc) || this.rootDoc, "lightbox"), "double tap");
                             SelectionManager.DeselectAll();
                         }
                         Doc.UnBrushDoc(this.props.Document);
@@ -963,7 +960,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     docViewPathFunc = () => this.docViewPath;
     isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction);
-    select = (ctrlPressed: boolean) => SelectionManager.SelectView(this, ctrlPressed);
+    select = (extendSelection: boolean) => SelectionManager.SelectView(this, !SelectionManager.Views().some(v => v.props.Document === this.props.ContainingCollectionDoc) && extendSelection);
     NativeWidth = () => this.nativeWidth;
     NativeHeight = () => this.nativeHeight;
     PanelWidth = () => this.panelWidth;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index f8d222798..c20d958ff 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -80,7 +80,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
         }
     }
 
-    scrollFocus = (doc: Doc, smooth: boolean, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => this._pdfViewer?.scrollFocus(doc, smooth, willZoom, scale, afterFocus);
+    scrollFocus = (doc: Doc, smooth: boolean) => this._pdfViewer?.scrollFocus(doc, smooth);
     getAnchor = () => this.rootDoc;
     componentWillUnmount() { this._selectReactionDisposer?.(); }
     componentDidMount() {
-- 
cgit v1.2.3-70-g09d2


From ffeada4e258e1786536b579b17798932bf4b8a8a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 12 Feb 2021 14:53:37 -0500
Subject: fixed bejhavior of Annotations when clicked to use FollowLink so that
 behavior is consistent.  previously in a lightbox links within a pdf woud
 unnecessarily open the the PDF in its context

---
 src/client/views/LightboxView.tsx   | 10 ++++++++--
 src/client/views/nodes/WebBox.tsx   |  2 +-
 src/client/views/pdf/Annotation.tsx | 31 +++++++------------------------
 src/client/views/pdf/PDFViewer.tsx  |  3 +--
 4 files changed, 17 insertions(+), 29 deletions(-)

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

diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 529eecacf..4c6592e58 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -11,6 +11,8 @@ import { DocumentView } from './nodes/DocumentView';
 import { DefaultStyleProvider } from './StyleProvider';
 import { DocUtils } from '../documents/Documents';
 import { DocumentManager } from '../util/DocumentManager';
+import { SelectionManager } from '../util/SelectionManager';
+import { TabDocView } from './collections/TabDocView';
 
 interface LightboxViewProps {
     PanelWidth: number;
@@ -51,6 +53,10 @@ export class LightboxView extends React.Component<LightboxViewProps> {
             </div>
         </div>;
     }
+    addDocTab = (doc: Doc, location: string) => {
+        SelectionManager.DeselectAll();
+        return LightboxView.SetLightboxDoc(doc);
+    }
 
     render() {
         if (LightboxView.LightboxHistory.lastElement() !== LightboxView.LightboxDoc) LightboxView.LightboxHistory.push(LightboxView.LightboxDoc);
@@ -73,8 +79,8 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                         Document={LightboxView.LightboxDoc}
                         DataDoc={undefined}
                         addDocument={undefined}
-                        addDocTab={returnFalse}
-                        pinToPres={emptyFunction}
+                        addDocTab={this.addDocTab}
+                        pinToPres={TabDocView.PinDoc}
                         rootSelected={returnTrue}
                         docViewPath={returnEmptyDoclist}
                         removeDocument={undefined}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 662477c05..abda0ed3a 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -425,7 +425,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         TraceMobx();
         return <div className="webBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}>
             {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
-                <Annotation {...this.props} showInfo={emptyFunction} focus={this.props.focus} dataDoc={this.dataDoc} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />)
+                <Annotation {...this.props} showInfo={emptyFunction} dataDoc={this.dataDoc} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />)
             }
         </div>;
     }
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index cd32c2c3a..4e3721e2b 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -9,42 +9,30 @@ import { LinkManager } from "../../util/LinkManager";
 import { undoBatch } from "../../util/UndoManager";
 import "./Annotation.scss";
 import { AnchorMenu } from "./AnchorMenu";
+import { FieldViewProps, FieldView } from "../nodes/FieldView";
 
-interface IAnnotationProps {
+interface IAnnotationProps extends FieldViewProps {
     anno: Doc;
-    addDocTab: (document: Doc, where: string) => boolean;
-    pinToPres: (document: Doc, unpin?: boolean) => void;
-    focus: (doc: Doc) => void;
-    select: (isCtrlPressed: boolean) => void;
     dataDoc: Doc;
     fieldKey: string;
     showInfo: (anno: Opt<Doc>) => void;
 }
-
 @observer
 export
     class Annotation extends React.Component<IAnnotationProps> {
     render() {
         return DocListCast(this.props.anno.annotations).map(a =>
-            <RegionAnnotation {...this.props} showInfo={this.props.showInfo} select={this.props.select} pinToPres={this.props.pinToPres} document={a} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />);
+            <RegionAnnotation {...this.props} showInfo={this.props.showInfo} document={a} x={NumCast(a.x)} y={NumCast(a.y)} width={a[WidthSym]()} height={a[HeightSym]()} key={a[Id]} />);
     }
 }
 
-interface IRegionAnnotationProps {
-    anno: Doc;
+interface IRegionAnnotationProps extends IAnnotationProps {
     x: number;
     y: number;
     width: number;
     height: number;
-    addDocTab: (document: Doc, where: string) => boolean;
-    pinToPres: (document: Doc, unpin: boolean) => void;
-    select: (isCtrlPressed: boolean) => void;
     document: Doc;
-    dataDoc: Doc;
-    fieldKey: string;
-    showInfo: (anno: Opt<Doc>) => void;
 }
-
 @observer
 class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
     private _reactionDisposer?: IReactionDisposer;
@@ -90,8 +78,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
     @undoBatch
     pinToPres = () => {
         const group = FieldValue(Cast(this.props.document.group, Doc));
-        const isPinned = group && Doc.isDocPinned(group) ? true : false;
-        group && this.props.pinToPres(group, isPinned);
+        group && this.props.pinToPres(group);
     }
 
     @undoBatch
@@ -115,13 +102,9 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
             AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true);
             e.stopPropagation();
         }
-        else if (e.button === 0) {
-            e.persist();
+        else if (e.button === 0 && this.props.document.group instanceof Doc) {
             e.stopPropagation();
-            PromiseValue(this.props.document.group).then(annoGroup => annoGroup instanceof Doc &&
-                LinkManager.traverseLink(undefined, annoGroup, (doc, followLinkLocation) => this.props.addDocTab(doc, e.ctrlKey ? "add" : followLinkLocation), false, undefined,
-                    () => this.props.select(false))
-            );
+            LinkManager.FollowLink(undefined, this.props.document.group, this.props, false);
         }
     }
 
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 0ba08685a..17936847e 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -28,7 +28,6 @@ import { AnchorMenu } from "./AnchorMenu";
 import "./PDFViewer.scss";
 const pdfjs = require('pdfjs-dist/es5/build/pdf.js');
 import React = require("react");
-import { DocAfterFocusFunc } from "../nodes/DocumentView";
 const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
 const pdfjsLib = require("pdfjs-dist");
 const _global = (window /* browser */ || global /* node */) as any;
@@ -496,7 +495,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         TraceMobx();
         return <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.Document), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}>
             {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
-                <Annotation {...this.props} showInfo={this.showInfo} select={this.props.select} focus={this.props.focus} dataDoc={this.dataDoc} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />)
+                <Annotation {...this.props} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />)
             }
         </div>;
     }
-- 
cgit v1.2.3-70-g09d2


From d9ec489c2f549c4aef0b3f458abfda5bb11e7188 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 12 Feb 2021 22:25:40 -0500
Subject: changed audio video annotation to use only
 timecodeToShow/timecodeToHide.  distinguish timeline annotation from video
 frame annotations with _timelineLabel field.  Lightbox improvements to
 restore original camera values and to use fitContentsToDoc for collections

---
 src/client/documents/Documents.ts                  |  1 +
 src/client/views/LightboxView.tsx                  | 36 +++++++++++++++++-----
 .../views/collections/CollectionSchemaView.tsx     |  4 +--
 .../collections/CollectionStackedTimeline.tsx      |  7 ++---
 .../views/collections/CollectionTimeView.tsx       |  4 +--
 src/client/views/collections/TabDocView.tsx        |  4 +--
 src/client/views/collections/TreeView.tsx          |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 32 ++++++++++---------
 src/client/views/nodes/AudioBox.tsx                |  6 ++--
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/ImageBox.tsx                |  5 ++-
 src/client/views/nodes/PresBox.tsx                 |  4 +--
 src/client/views/nodes/VideoBox.tsx                | 14 ++++-----
 .../views/nodes/formattedText/FormattedTextBox.tsx |  6 ++--
 src/fields/documentSchemas.ts                      |  1 +
 15 files changed, 77 insertions(+), 51 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index aafa58028..d72efb1b4 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -155,6 +155,7 @@ export interface DocumentOptions {
     _currentFrame?: number; // the current frame of a frame-based collection (e.g., progressive slide)
     _timecodeToShow?: number; // the time that a document should be displayed (e.g., when an annotation shows up as a video plays)
     _timecodeToHide?: number; // the time that a document should be hidden
+    _timelineLabel?: boolean; // whether the document exists on a timeline
     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
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 4c6592e58..0957fc94d 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -13,6 +13,7 @@ import { DocUtils } from '../documents/Documents';
 import { DocumentManager } from '../util/DocumentManager';
 import { SelectionManager } from '../util/SelectionManager';
 import { TabDocView } from './collections/TabDocView';
+import { Cast } from '../../fields/Types';
 
 interface LightboxViewProps {
     PanelWidth: number;
@@ -22,14 +23,31 @@ interface LightboxViewProps {
 
 @observer
 export class LightboxView extends React.Component<LightboxViewProps> {
+    public static SavedState: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number>, transition: Opt<string> }>;
     @observable static LightboxDoc: Opt<Doc>;
+    @observable static LightboxDocTarget: Opt<Doc>;
     @action public static SetLightboxDoc(doc: Opt<Doc>, future?: Doc[]) {
-        LightboxView.LightboxDoc = doc;
         if (!doc) {
+            if (this.LightboxDoc) {
+                this.LightboxDoc._panX = this.SavedState?.panX;
+                this.LightboxDoc._panY = this.SavedState?.panY;
+                this.LightboxDoc._viewScale = this.SavedState?.scale;
+                this.LightboxDoc._viewTransition = this.SavedState?.transition;
+            }
             LightboxView.LightboxFuture = LightboxView.LightboxHistory = [];
-        } else if (future) {
+        } else {
+            LightboxView.SavedState = {
+                panX: Cast(doc._panX, "number", null),
+                panY: Cast(doc._panY, "number", null),
+                scale: Cast(doc._viewScale, "number", null),
+                transition: Cast(doc._viewTransition, "string", null)
+            };
+        }
+        if (future) {
             LightboxView.LightboxFuture = future.slice();
         }
+        LightboxView.LightboxDoc = LightboxView.LightboxDocTarget = doc;
+
         return true;
     }
     public static IsLightboxDocView(path: DocumentView[]) { return path.includes(LightboxView.LightboxDocView.current!); }
@@ -58,6 +76,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
         return LightboxView.SetLightboxDoc(doc);
     }
 
+    fitToBox = () => LightboxView.LightboxDocTarget === LightboxView.LightboxDoc;
     render() {
         if (LightboxView.LightboxHistory.lastElement() !== LightboxView.LightboxDoc) LightboxView.LightboxHistory.push(LightboxView.LightboxDoc);
         let downx = 0, downy = 0;
@@ -79,6 +98,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                         Document={LightboxView.LightboxDoc}
                         DataDoc={undefined}
                         addDocument={undefined}
+                        fitContentsToDoc={this.fitToBox}
                         addDocTab={this.addDocTab}
                         pinToPres={TabDocView.PinDoc}
                         rootSelected={returnTrue}
@@ -102,10 +122,10 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                 </div>
                 {this.navBtn(undefined, "chevron-left",
                     () => LightboxView.LightboxDoc && LightboxView.LightboxHistory.length ? "" : "none",
-                    e => {
+                    action(e => {
                         e.stopPropagation();
                         const previous = LightboxView.LightboxHistory.pop();
-                        const target = LightboxView.LightboxHistory.lastElement();
+                        const target = LightboxView.LightboxDocTarget = LightboxView.LightboxHistory.lastElement();
                         const docView = target && DocumentManager.Instance.getLightboxDocumentView(target);
                         if (docView && target) {
                             if (LightboxView.LightboxFuture.lastElement() !== previous) LightboxView.LightboxFuture.push(previous);
@@ -113,12 +133,12 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                         } else {
                             LightboxView.SetLightboxDoc(target);
                         }
-                    })}
+                    }))}
                 {this.navBtn(this.props.PanelWidth - Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]), "chevron-right",
                     () => LightboxView.LightboxDoc && LightboxView.LightboxFuture.length ? "" : "none",
-                    e => {
+                    action(e => {
                         e.stopPropagation();
-                        const target = LightboxView.LightboxFuture.pop();
+                        const target = LightboxView.LightboxDocTarget = LightboxView.LightboxFuture.pop();
                         const docView = target && DocumentManager.Instance.getLightboxDocumentView(target);
                         if (docView && target) {
                             docView.focus(target, true, 0.9);
@@ -126,7 +146,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                         } else {
                             LightboxView.SetLightboxDoc(target);
                         }
-                    })}
+                    }))}
 
             </div>;
     }
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index d2ed5427b..528cdc8b7 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -11,7 +11,7 @@ import { listSpec } from "../../../fields/Schema";
 import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField";
 import { Cast, NumCast } from "../../../fields/Types";
 import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, emptyPath, returnFalse, setupMoveUpEvents, returnEmptyDoclist } from "../../../Utils";
+import { emptyFunction, emptyPath, returnFalse, setupMoveUpEvents, returnEmptyDoclist, returnTrue } from "../../../Utils";
 import { SelectionManager } from "../../util/SelectionManager";
 import { SnappingManager } from "../../util/SnappingManager";
 import { Transform } from "../../util/Transform";
@@ -402,7 +402,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
                 <DocumentView
                     Document={this.previewDocument}
                     DataDoc={undefined}
-                    fitContentsToDoc={true}
+                    fitContentsToDoc={returnTrue}
                     freezeDimensions={true}
                     dontCenter={"y"}
                     focus={DocUtils.DefaultFocus}
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index d4eb66fcc..968ff3cd8 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -31,7 +31,6 @@ export type CollectionStackedTimelineProps = {
     isChildActive: () => boolean;
     startTag: string;
     endTag: string;
-    fieldKeySuffix?: string;
 };
 
 @observer
@@ -47,7 +46,6 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
     @observable _markerEnd: number = 0;
 
     get duration() { return this.props.duration; }
-    @computed get anchorDocs() { return this.props.fieldKeySuffix ? this.childDocs.concat(...DocListCast(this.rootDoc[this.props.fieldKey + this.props.fieldKeySuffix])) : this.childDocs; }
     @computed get currentTime() { return NumCast(this.layoutDoc._currentTimecode); }
     @computed get selectionContainer() {
         return CollectionStackedTimeline.SelectingRegion !== this ? (null) : <div className="collectionStackedTimeline-selector" style={{
@@ -159,7 +157,8 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
             title: ComputedField.MakeFunction(`"#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"])`) as any,
             useLinkSmallAnchor: true,
             hideLinkButton: true,
-            annotationOn: rootDoc
+            annotationOn: rootDoc,
+            _timelineLabel: true
         });
         Doc.GetProto(anchor)[startTag] = anchorStartTime;
         Doc.GetProto(anchor)[endTag] = anchorEndTime;
@@ -295,7 +294,7 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
         const timelineContentWidth = this.props.PanelWidth();
         const timelineContentHeight = this.props.PanelHeight();
         const overlaps: { anchorStartTime: number, anchorEndTime: number, level: number }[] = [];
-        const drawAnchors = this.anchorDocs.map(anchor => ({ level: this.getLevel(anchor, overlaps), anchor }));
+        const drawAnchors = this.childDocs.map(anchor => ({ level: this.getLevel(anchor, overlaps), anchor }));
         const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2;
         const isActive = this.props.isChildActive() || this.props.isSelected(false);
         return <div className="collectionStackedTimeline" ref={(timeline: HTMLDivElement | null) => this._timeline = timeline}
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index cc625e12e..869e01fea 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -6,7 +6,7 @@ import { ObjectField } from "../../../fields/ObjectField";
 import { RichTextField } from "../../../fields/RichTextField";
 import { ComputedField, ScriptField } from "../../../fields/ScriptField";
 import { NumCast, StrCast, BoolCast, Cast } from "../../../fields/Types";
-import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils";
+import { emptyFunction, returnFalse, setupMoveUpEvents, returnTrue } from "../../../Utils";
 import { Scripting } from "../../util/Scripting";
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
@@ -86,7 +86,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
     @computed get contents() {
         return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%", pointerEvents: this.props.active() ? undefined : "none" }} onPointerDown={this.contentsDown}>
             <CollectionFreeFormView {...this.props}
-                fitContentsToDoc={true}
+                fitContentsToDoc={returnTrue}
                 childClickScript={this._childClickedScript}
                 viewDefDivClick={this._viewDefDivClick}
                 childFreezeDimensions={true}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 0fb140231..9048a5f01 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -279,7 +279,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             case "close": return CollectionDockingView.CloseSplit(doc, locationParams);
             case "fullScreen": return CollectionDockingView.OpenFullScreen(doc);
             case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack);
-            case "lightbox": return LightboxView.SetLightboxDoc(doc);
+            case "lightbox": return LightboxView.SetLightboxDoc(doc, DocListCast(doc[Doc.LayoutFieldKey(doc) + "-annotations"]).sort((a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)));
             case "inPlace":
             case "add":
             default:
@@ -342,7 +342,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     docFilters={CollectionDockingView.Instance.docFilters}
                     docRangeFilters={CollectionDockingView.Instance.docRangeFilters}
                     searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
-                    fitContentsToDoc={true}
+                    fitContentsToDoc={returnTrue}
                 />
                 <div className="miniOverlay" onPointerDown={this.miniDown} >
                     <div className="miniThumb" style={{ width: `${miniWidth}% `, height: `${miniHeight}% `, left: `${miniLeft}% `, top: `${miniTop}% `, }} />
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 14075db1f..d7198ee7c 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -632,7 +632,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             PanelHeight={panelHeight}
             NativeWidth={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfWidth : undefined}
             NativeHeight={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfHeight : undefined}
-            fitContentsToDoc={true}
+            fitContentsToDoc={returnTrue}
             hideTitle={asText}
             LayoutTemplateString={asText ? FormattedTextBox.LayoutString("text") : undefined}
             focus={asText ? this.refocus : returnFalse}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index eb99702a1..1f65cf1ea 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -118,7 +118,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && (this.props.ContainingCollectionView?.active() || this.props.active()); }
     @computed get fitToContentScaling() { return this.fitToContent ? NumCast(this.layoutDoc.fitToContentScaling, 1) : 1; }
-    @computed get fitToContent() { return (this.props.fitContentsToDoc || this.Document._fitToBox) && !this.isAnnotationOverlay; }
+    @computed get fitToContent() { return (this.props.fitContentsToDoc?.() || this.Document._fitToBox) && !this.isAnnotationOverlay; }
     @computed get parentScaling() { return 1; }
     @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); }
     @computed get nativeWidth() { return this.fitToContent ? 0 : Doc.NativeWidth(this.Document); }
@@ -864,8 +864,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             const scale = this.getLocalTransform().inverse().Scale;
             const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
             const newPanY = Math.min((this.props.Document.scrollHeight !== undefined ? NumCast(this.Document.scrollHeight) : (1 - 1 / scale) * this.nativeHeight), Math.max(0, panY));
-            this.Document._panX = this.isAnnotationOverlay ? newPanX : panX;
-            this.Document._panY = this.isAnnotationOverlay ? newPanY : panY;
+            !this.Document._verticalScroll && (this.Document._panX = this.isAnnotationOverlay ? newPanX : panX);
+            !this.Document._horizontalScroll && (this.Document._panY = this.isAnnotationOverlay ? newPanY : panY);
         }
     }
 
@@ -914,25 +914,26 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 HistoryUtil.pushState(state);
             }
         }
+        const LightboxState = LightboxView.LightboxDoc === this.props.Document && LightboxView.SavedState ? LightboxView.SavedState : undefined;
         SelectionManager.DeselectAll();
         if (this.props.Document.scrollHeight) {
             this.props.focus(doc, willZoom, scale, afterFocus);
         } else {
             const xfToCollection = docTransform ?? Transform.Identity();
             const layoutdoc = Doc.Layout(doc);
-            const savedState = { px: NumCast(this.Document._panX), py: NumCast(this.Document._panY), s: this.Document[this.scaleFieldKey], pt: this.Document._viewTransition };
+            const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: this.Document[this.scaleFieldKey], transition: this.Document._viewTransition };
             const newState = HistoryUtil.getState();
             const cantTransform = this.props.isAnnotationOverlay || this.rootDoc._isGroup || this.layoutDoc._lockedTransform;
-            const { px, py } = cantTransform ? savedState : this.setPanIntoView(layoutdoc, xfToCollection, willZoom ? scale || .75 : undefined);
+            const { panX, panY } = cantTransform ? savedState : this.setPanIntoView(layoutdoc, xfToCollection, willZoom ? scale || .75 : undefined);
             if (!cantTransform) {   // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection
-                newState.initializers![this.Document[Id]] = { panX: px, panY: py };
+                newState.initializers![this.Document[Id]] = { panX: panX, panY: panY };
                 HistoryUtil.pushState(newState);
             }
             // focus on the document in the collection
-            const didMove = !doc.z && (px !== savedState.px || py !== savedState.py);
+            const didMove = !doc.z && (panX !== savedState.panX || panY !== savedState.panY);
             const focusSpeed = didMove ? (doc.focusSpeed !== undefined ? Number(doc.focusSpeed) : 500) : 0;
             // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
-            if (didMove) this.setPan(px, py, `transform ${focusSpeed}ms`, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
+            if (didMove) this.setPan(panX, panY, `transform ${focusSpeed}ms`, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
             Doc.BrushDoc(this.rootDoc);
             !doc.hidden && Doc.linkFollowHighlight(doc);
 
@@ -941,10 +942,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 doc.hidden && Doc.UnHighlightDoc(doc);
                 const resetView = afterFocus ? await afterFocus(moved) : false;
                 if (resetView) {
-                    this.Document._panX = savedState.px;
-                    this.Document._panY = savedState.py;
-                    this.Document[this.scaleFieldKey] = savedState.s;
-                    this.Document._viewTransition = savedState.pt;
+                    const restoreState = LightboxView.LightboxDoc !== this.props.Document && LightboxState ? LightboxState : savedState;
+                    this.Document._panX = restoreState.panX;
+                    this.Document._panY = restoreState.panY;
+                    this.Document[this.scaleFieldKey] = restoreState.scale;
+                    this.Document._viewTransition = restoreState.transition;
                 }
                 return resetView;
             };
@@ -969,14 +971,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         if (scale) {
             const maxZoom = 2; // sets the limit for how far we will zoom. this is useful for preventing small text boxes from filling the screen. So probably needs to be more sophisticated to consider more about the target and context
             this.Document[this.scaleFieldKey] = Math.min(maxZoom, scale * Math.min(this.props.PanelWidth() / Math.abs(pt2[0] - pt[0]), this.props.PanelHeight() / Math.abs(pt2[1] - pt[1])));
-            return { px: (bounds.left + bounds.right) / 2, py: (bounds.top + bounds.bot) / 2 };
+            return { panX: (bounds.left + bounds.right) / 2, panY: (bounds.top + bounds.bot) / 2 };
         } else {
             const cx = NumCast(this.layoutDoc._panX);
             const cy = NumCast(this.layoutDoc._panY);
             const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 };
             return {
-                px: cx + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
-                py: cy + Math.min(0, bounds.top - ph / 10 - screen.top) + Math.max(0, bounds.bot + ph / 10 - screen.bot)
+                panX: cx + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
+                panY: cy + Math.min(0, bounds.top - ph / 10 - screen.top) + Math.max(0, bounds.bot + ph / 10 - screen.bot),
             };
         }
     }
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index b681054fc..ecc93ce67 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -89,7 +89,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
     }
 
     getAnchor = () => {
-        return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "audioStart", "audioEnd", this._ele?.currentTime || Cast(this.props.Document._currentTimecode, "number", null) || (this.audioState === "recording" ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined)) || this.rootDoc;
+        return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "_timecodeToShow" /* audioStart */, "_timecodeToHide" /* audioEnd */, this._ele?.currentTime || Cast(this.props.Document._currentTimecode, "number", null) || (this.audioState === "recording" ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined)) || this.rootDoc;
     }
 
     componentWillUnmount() {
@@ -354,8 +354,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
         return <CollectionStackedTimeline ref={this._stackedTimeline} {...this.props}
             fieldKey={this.annotationKey}
             renderDepth={this.props.renderDepth + 1}
-            startTag={"audioStart"}
-            endTag={"audioEnd"}
+            startTag={"_timecodeToShow" /* audioStart */}
+            endTag={"_timecodeToHide" /* audioEnd */}
             focus={DocUtils.DefaultFocus}
             bringToFront={emptyFunction}
             CollectionView={undefined}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index bcf12c93c..d593b75eb 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -57,7 +57,7 @@ export interface DocumentViewSharedProps {
     renderDepth: number;
     Document: Doc;
     DataDoc?: Doc;
-    fitContentsToDoc?: boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitToBox property on a Document
+    fitContentsToDoc?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitToBox property on a Document
     ContainingCollectionView: Opt<CollectionView>;
     ContainingCollectionDoc: Opt<Doc>;
     setContentView?: (view: DocComponentView) => any;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 67397dc51..441d6232a 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -151,6 +151,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
             }, 5000);
         });
     }
+    @undoBatch
+    resolution = () => this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes;
 
     @undoBatch
     rotate = action(() => {
@@ -170,6 +172,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
         if (field) {
             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" });
             if (!Doc.UserDoc().noviceMode) {
                 funcs.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" });
                 funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" });
@@ -223,7 +226,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
 
         const ext = path.extname(url.href);
         const scrSize = this.props.ScreenToLocalTransform().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight);
-        this._curSuffix = this.props.renderDepth < 1 ? "_o" : scrSize[0] < 100 ? "_s" : scrSize[0] < 400 || !this.props.isSelected() ? "_m" : "_o";
+        this._curSuffix = this.props.renderDepth < 1 || this.layoutDoc._showFullRes ? "_o" : scrSize[0] < 100 ? "_s" : scrSize[0] < 400 || !this.props.isSelected() ? "_m" : "_o";
         return url.href.replace(ext, this._curSuffix + ext);
     }
 
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index d736dc583..77b050abe 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -719,8 +719,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
                 if (audio) {
                     audio.mediaStart = "manual";
                     audio.mediaStop = "manual";
-                    audio.presStartTime = NumCast(doc.audioStart, NumCast(doc.videoStart));
-                    audio.presEndTime = NumCast(doc.audioEnd, NumCast(doc.videoEnd));
+                    audio.presStartTime = NumCast(doc._timecodeToShow /* audioStart */, NumCast(doc._timecodeToShow /* videoStart */));
+                    audio.presEndTime = NumCast(doc._timecodeToHide /* audioEnd */, NumCast(doc._timecodeToHide /* videoEnd */));
                     audio.presDuration = audio.presStartTime - audio.presEndTime;
                     TabDocView.PinDoc(audio, { audioRange: true });
                     setTimeout(() => this.removeDocument(doc), 0);
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index a99853aac..6d0a200cb 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -4,7 +4,7 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction,
 import { observer } from "mobx-react";
 import * as rp from 'request-promise';
 import { Dictionary } from "typescript-collections";
-import { Doc, DocListCast } from "../../../fields/Doc";
+import { Doc, DocListCast, StrListCast } from "../../../fields/Doc";
 import { documentSchema } from "../../../fields/documentSchemas";
 import { InkTool } from "../../../fields/InkField";
 import { makeInterface } from "../../../fields/Schema";
@@ -71,7 +71,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
 
     getAnchor = () => {
         const timecode = Cast(this.layoutDoc._currentTimecode, "number", null);
-        return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey + "-timeline", "videoStart", "videoEnd", timecode ? timecode : undefined) || this.rootDoc;
+        const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "_timecodeToShow"/* videoStart */, "_timecodeToHide" /* videoEnd */, timecode ? timecode : undefined) || this.rootDoc;
+        return anchor;
     }
 
     choosePath(url: string) {
@@ -202,7 +203,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
 
     componentDidMount() {
         this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
-
         this._disposers.selection = reaction(() => this.props.isSelected(),
             selected => {
                 if (!selected) {
@@ -494,9 +494,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
             <CollectionStackedTimeline ref={this._stackedTimeline} {...this.props}
                 fieldKey={this.annotationKey}
                 renderDepth={this.props.renderDepth + 1}
-                startTag={"videoStart"}
-                endTag={"videoEnd"}
-                fieldKeySuffix={"-timeline"}
+                startTag={"_timecodeToShow" /* videoStart */}
+                endTag={"_timecodeToHide" /* videoEnd */}
                 focus={DocUtils.DefaultFocus}
                 bringToFront={emptyFunction}
                 CollectionView={undefined}
@@ -540,7 +539,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
     }
     marqueeFitScaling = () => (this.props.scaling?.() || 1) * this.heightPercent / 100;
     marqueeOffset = () => [this.panelWidth() / 2 * (1 - this.heightPercent / 100) / (this.heightPercent / 100), 0];
-
+    timelineDocFilter = () => ["_timelineLabel:true:x"];
     render() {
         const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
         const borderRadius = borderRad?.includes("px") ? `${Number(borderRad.split("px")[0]) / this.scaling()}px` : borderRad;
@@ -558,6 +557,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
                         select={emptyFunction}
                         active={this.annotationsActive}
                         scaling={returnOne}
+                        docFilters={this.timelineDocFilter}
                         PanelWidth={this.panelWidth}
                         PanelHeight={this.panelHeight}
                         ScreenToLocalTransform={this.screenToLocalTransform}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index e9aabe8f1..9f9f11ab5 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -362,7 +362,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         DocListCast(this.dataDoc.links).forEach((l, i) => {
             const anchor = (l.anchor1 as Doc).annotationOn ? l.anchor1 as Doc : (l.anchor2 as Doc).annotationOn ? (l.anchor2 as Doc) : undefined;
             if (anchor && (anchor.annotationOn as Doc).audioState === "recording") {
-                linkTime = NumCast(anchor.audioStart);
+                linkTime = NumCast(anchor._timecodeToShow /* audioStart */);
                 linkAnchor = anchor;
             }
         });
@@ -1669,8 +1669,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
 
     sidebarContentScaling = () => (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
+    fitToBox = () => this.props.Document._fitToBox;
     @computed get sidebarCollection() {
-        const fitToBox = this.props.Document._fitToBox;
         const collectionProps: SubCollectionViewProps & collectionFreeformViewProps = {
             ...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit,
             NativeWidth: returnZero,
@@ -1683,7 +1683,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             scaleField: this.SidebarKey + "-scale",
             isAnnotationOverlay: true,
             fieldKey: this.SidebarKey,
-            fitContentsToDoc: fitToBox,
+            fitContentsToDoc: this.fitToBox,
             select: emptyFunction,
             active: this.annotationsActive,
             scaling: this.sidebarContentScaling,
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 056243953..bdc498c97 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -20,6 +20,7 @@ export const documentSchema = createSchema({
     activeFrame: "number",      // the active frame of a frame based animated document 
     _currentTimecode: "number", // current play back time of a temporal document (video / audio)
     _timecodeToShow: "number",  // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
+    _timecodeToHIde: "number",  // the time that a document should be hidden
     isLabel: "boolean",         // whether the document is a label or not (video / audio)
     markers: listSpec(Doc),     // list of markers for audio / video
     x: "number",                // x coordinate when in a freeform view 
-- 
cgit v1.2.3-70-g09d2


From aca573baccc106363d6b61797b58cd60c742b0a0 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 13 Feb 2021 01:13:09 -0500
Subject: fixed double-click to open nested freeform docs to fall back on
 DocumentView to open them in lightbox.   fixed resetting of collectoin view
 pan/scale after coming out of lightbox.

---
 src/client/views/LightboxView.tsx                  | 27 +++++++++++----
 src/client/views/collections/TabDocView.tsx        |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 40 ++++++++++++----------
 src/client/views/nodes/DocumentView.tsx            |  8 +++--
 4 files changed, 48 insertions(+), 29 deletions(-)

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

diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 0957fc94d..7fab88800 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -3,7 +3,7 @@ import { action, observable, computed } from 'mobx';
 import { observer } from 'mobx-react';
 import "normalize.css";
 import * as React from 'react';
-import { Doc, Opt } from '../../fields/Doc';
+import { Doc, Opt, DocListCast } from '../../fields/Doc';
 import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../Utils';
 import { Transform } from '../util/Transform';
 import "./LightboxView.scss";
@@ -13,7 +13,7 @@ import { DocUtils } from '../documents/Documents';
 import { DocumentManager } from '../util/DocumentManager';
 import { SelectionManager } from '../util/SelectionManager';
 import { TabDocView } from './collections/TabDocView';
-import { Cast } from '../../fields/Types';
+import { Cast, NumCast } from '../../fields/Types';
 
 interface LightboxViewProps {
     PanelWidth: number;
@@ -50,10 +50,10 @@ export class LightboxView extends React.Component<LightboxViewProps> {
 
         return true;
     }
-    public static IsLightboxDocView(path: DocumentView[]) { return path.includes(LightboxView.LightboxDocView.current!); }
+    public static IsLightboxDocView(path: DocumentView[]) { return path.includes(LightboxView.LightboxDocView!); }
     public static LightboxHistory: (Opt<Doc>)[] = [];
     public static LightboxFuture: (Opt<Doc>)[] = [];
-    public static LightboxDocView = React.createRef<DocumentView>();
+    public static LightboxDocView: Opt<DocumentView>;
     @computed get leftBorder() { return Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]); }
     @computed get topBorder() { return Math.min(this.props.PanelHeight / 4, this.props.maxBorder[1]); }
     lightboxWidth = () => this.props.PanelWidth - this.leftBorder * 2;
@@ -71,10 +71,12 @@ export class LightboxView extends React.Component<LightboxViewProps> {
             </div>
         </div>;
     }
-    addDocTab = (doc: Doc, location: string) => {
+    public static AddDocTab = (doc: Doc, location: string) => {
         SelectionManager.DeselectAll();
-        return LightboxView.SetLightboxDoc(doc);
+        return LightboxView.SetLightboxDoc(doc, [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + "-annotations"])]
+            .sort((a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)));
     }
+    addDocTab = LightboxView.AddDocTab;
 
     fitToBox = () => LightboxView.LightboxDocTarget === LightboxView.LightboxDoc;
     render() {
@@ -94,7 +96,18 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                     width: this.lightboxWidth(),
                     height: this.lightboxHeight()
                 }}>
-                    <DocumentView ref={LightboxView.LightboxDocView}
+                    <DocumentView ref={action((r: DocumentView | null) => {
+                        LightboxView.LightboxDocView = r !== null ? r : undefined;
+                        setTimeout(action(() => {
+                            const vals = r?.ComponentView?.freeformData?.();
+                            if (vals && r) {
+                                r.layoutDoc._panX = vals.panX;
+                                r.layoutDoc._panY = vals.panY;
+                                r.layoutDoc._viewScale = vals.scale;
+                            }
+                            LightboxView.LightboxDocTarget = undefined;
+                        }));
+                    })}
                         Document={LightboxView.LightboxDoc}
                         DataDoc={undefined}
                         addDocument={undefined}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 9048a5f01..d62b8b6f6 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -279,7 +279,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             case "close": return CollectionDockingView.CloseSplit(doc, locationParams);
             case "fullScreen": return CollectionDockingView.OpenFullScreen(doc);
             case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack);
-            case "lightbox": return LightboxView.SetLightboxDoc(doc, DocListCast(doc[Doc.LayoutFieldKey(doc) + "-annotations"]).sort((a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)));
+            case "lightbox": return LightboxView.AddDocTab(doc, location);
             case "inPlace":
             case "add":
             default:
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 1f65cf1ea..954b6478f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -117,7 +117,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @observable _marqueeRef = React.createRef<HTMLDivElement>();
 
     @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && (this.props.ContainingCollectionView?.active() || this.props.active()); }
-    @computed get fitToContentScaling() { return this.fitToContent ? NumCast(this.layoutDoc.fitToContentScaling, 1) : 1; }
+    @computed get fitToContentVals() {
+        return this.fitToContent &&
+        {
+            panX: (this.contentBounds.x + this.contentBounds.r) / 2,
+            panY: (this.contentBounds.y + this.contentBounds.b) / 2,
+            scale: !this.childDocs.length ? 1 :
+                Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y),
+                    this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x))
+        } || undefined;
+    }
     @computed get fitToContent() { return (this.props.fitContentsToDoc?.() || this.Document._fitToBox) && !this.isAnnotationOverlay; }
     @computed get parentScaling() { return 1; }
     @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); }
@@ -126,17 +135,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
     private get scaleFieldKey() { return this.props.scaleField || "_viewScale"; }
     private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
-    private panX = () => this.fitToContent && !this.props.isAnnotationOverlay ? (this.contentBounds.x + this.contentBounds.r) / 2 : this.Document._panX || 0;
-    private panY = () => this.fitToContent && !this.props.isAnnotationOverlay ? (this.contentBounds.y + this.contentBounds.b) / 2 : this.Document._panY || 0;
-    private zoomScaling = () => {
-        const mult = this.fitToContentScaling / this.parentScaling;
-        if (this.fitToContent) {
-            const zs = !this.childDocs.length ? 1 :
-                Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x));
-            return mult * zs;
-        }
-        return mult * NumCast(this.Document[this.scaleFieldKey], 1);
-    }
+    private panX = () => this.fitToContentVals?.panX ?? NumCast(this.Document._panX);
+    private panY = () => this.fitToContentVals?.panY ?? NumCast(this.Document._panY);
+    private zoomScaling = () => (this.fitToContentVals?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1)) / this.parentScaling;
     @computed get cachedCenteringShiftX(): number {
         const scaling = this.fitToContent || !this.contentScaling ? 1 : this.contentScaling;
         return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / this.parentScaling / scaling;  // shift so pan position is at center of window for non-overlay collections
@@ -607,14 +608,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     }
 
     onClick = (e: React.MouseEvent) => {
-        if (this.layoutDoc.targetScale && (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) {
+        if ((Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) {
             if (Date.now() - this._lastTap < 300) { // reset zoom of freeform view to 1-to-1 on a double click 
                 if (e.shiftKey) {
-                    this.scaleAtPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1);
+                    if (this.layoutDoc.targetScale) {
+                        this.scaleAtPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1);
+                    }
                     e.stopPropagation();
                     e.preventDefault();
-                } else {
-                    LightboxView.SetLightboxDoc(this.rootDoc, this.childDocs);
                 }
             }
             this._lastTap = Date.now();
@@ -937,6 +938,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             Doc.BrushDoc(this.rootDoc);
             !doc.hidden && Doc.linkFollowHighlight(doc);
 
+            const startTime = Date.now();
             // focus on this collection within its parent view.  the parent view after focusing determines whether to reset the view change within the collection
             const endFocus = async (moved: boolean) => {
                 doc.hidden && Doc.UnHighlightDoc(doc);
@@ -956,8 +958,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     :
                     new Transform(NumCast(this.rootDoc.x) + this.rootDoc[WidthSym]() / 2 - NumCast(this.rootDoc._panX),
                         NumCast(this.rootDoc.y) + this.rootDoc[HeightSym]() / 2 - NumCast(this.rootDoc._panY), 1);
+
             this.props.focus(cantTransform ? doc : this.rootDoc, willZoom, scale, (didFocus: boolean) =>
-                new Promise<boolean>(res => setTimeout(async () => res(await endFocus(didMove || didFocus)), focusSpeed)), xf);
+                new Promise<boolean>(res => setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime)))), xf);
         }
     }
 
@@ -1185,10 +1188,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         this.Document._useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true);
         return elements;
     }
-
+    freeformData = () => { return this.fitToContentVals; }
     @action
     componentDidMount() {
         super.componentDidMount?.();
+        this.props.setContentView?.(this);
         this._layoutComputeReaction = reaction(() => this.doLayoutComputation,
             (elements) => this._layoutElements = elements || [],
             { fireImmediately: true, name: "doLayout" });
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d593b75eb..7ba0d7043 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -46,12 +46,13 @@ export type DocAfterFocusFunc = (notFocused: boolean) => Promise<boolean>;
 export type DocFocusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => void;
 export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => any;
 export interface DocComponentView {
-    getAnchor: () => Doc;
+    getAnchor?: () => Doc;
     scrollFocus?: (doc: Doc, smooth: boolean) => Opt<number>; // returns the duration of the focus
     back?: () => boolean;
     forward?: () => boolean;
     url?: () => string;
     submitURL?: (url: string) => boolean;
+    freeformData?: () => Opt<{ panX: number, panY: number, scale: number }>;
 }
 export interface DocumentViewSharedProps {
     renderDepth: number;
@@ -388,7 +389,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             let stopPropagate = true;
             let preventDefault = true;
             !StrListCast(this.props.Document.layers).includes(StyleLayers.Background) && (this.rootDoc._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc);
-            if (this._doubleTap && ((this.props.renderDepth && this.props.Document.type !== DocumentType.FONTICON) || this.onDoubleClickHandler)) {// && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
+            if (this._doubleTap && (this.props.Document.type !== DocumentType.FONTICON || this.onDoubleClickHandler)) {// && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
                 if (this._timeout) {
                     clearTimeout(this._timeout);
                     this._timeout = undefined;
@@ -513,7 +514,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log);
         } else {
             this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
-            if (!this.props.isSelected(true)) this._lastTap = Date.now();// don't want to process the start of a double tap if the doucment is selected
+            // bcz: this is a placeholder.  documents, when selected, should stopPropagation on doubleClicks if they want to keep the DocumentView from getting them
+            if (!this.props.isSelected(true) || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this.rootDoc.type) as any)) this._lastTap = Date.now();// don't want to process the start of a double tap if the doucment is selected
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 4e1df78c6d94fabd949113801261150de8ed686c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 13 Feb 2021 21:14:05 -0500
Subject: fixed bug introduced where stacked documents couldn't be reordered. 
 changed lightbox future a bit.

---
 src/client/util/LinkManager.ts                     |  4 ---
 src/client/views/LightboxView.tsx                  | 30 ++++++++++++----------
 .../views/collections/CollectionStackingView.tsx   | 11 ++++----
 src/client/views/nodes/LinkAnchorBox.tsx           |  3 +--
 src/client/views/nodes/PresBox.tsx                 |  1 +
 5 files changed, 25 insertions(+), 24 deletions(-)

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

diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index c39a4abda..1e6e02e55 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -145,12 +145,8 @@ export class LinkManager {
             if (linkDoc) {
                 const target = (doc === linkDoc.anchor1 ? linkDoc.anchor2 : doc === linkDoc.anchor2 ? linkDoc.anchor1 :
                     (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc;
-                const targetTimecode = (doc === linkDoc.anchor1 ? Cast(linkDoc.anchor2_timecode, "number") :
-                    doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number") :
-                        (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number")));
                 if (target) {
                     const containerDoc = Cast(target.annotationOn, Doc, null) || target;
-                    targetTimecode !== undefined && (containerDoc._currentTimecode = targetTimecode);
                     const targetContext = Cast(containerDoc?.context, Doc, null);
                     const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
                     DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "add:right"), finished), targetNavContext, linkDoc, undefined, doc, finished);
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 7fab88800..d36c3ad4f 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -44,15 +44,15 @@ export class LightboxView extends React.Component<LightboxViewProps> {
             };
         }
         if (future) {
-            LightboxView.LightboxFuture = future.slice();
+            LightboxView.LightboxFuture = future.slice().sort((a, b) => DocListCast(a.links).length - DocListCast(b.links).length);
         }
         LightboxView.LightboxDoc = LightboxView.LightboxDocTarget = doc;
 
         return true;
     }
     public static IsLightboxDocView(path: DocumentView[]) { return path.includes(LightboxView.LightboxDocView!); }
-    public static LightboxHistory: (Opt<Doc>)[] = [];
-    public static LightboxFuture: (Opt<Doc>)[] = [];
+    public static LightboxHistory: Opt<Doc[]> = [];
+    public static LightboxFuture: Opt<Doc[]> = [];
     public static LightboxDocView: Opt<DocumentView>;
     @computed get leftBorder() { return Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]); }
     @computed get topBorder() { return Math.min(this.props.PanelHeight / 4, this.props.maxBorder[1]); }
@@ -73,14 +73,18 @@ export class LightboxView extends React.Component<LightboxViewProps> {
     }
     public static AddDocTab = (doc: Doc, location: string) => {
         SelectionManager.DeselectAll();
-        return LightboxView.SetLightboxDoc(doc, [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + "-annotations"])]
-            .sort((a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)));
+        return LightboxView.SetLightboxDoc(doc,
+            [...DocListCast(doc[Doc.LayoutFieldKey(doc)]),
+            ...DocListCast(doc[Doc.LayoutFieldKey(doc) + "-annotations"]),
+            ...(LightboxView.LightboxFuture ?? [])
+            ]
+                .sort((a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)));
     }
     addDocTab = LightboxView.AddDocTab;
 
     fitToBox = () => LightboxView.LightboxDocTarget === LightboxView.LightboxDoc;
     render() {
-        if (LightboxView.LightboxHistory.lastElement() !== LightboxView.LightboxDoc) LightboxView.LightboxHistory.push(LightboxView.LightboxDoc);
+        if (LightboxView.LightboxHistory?.lastElement() !== LightboxView.LightboxDoc) LightboxView.LightboxHistory?.push(LightboxView.LightboxDoc!);
         let downx = 0, downy = 0;
         return !LightboxView.LightboxDoc ? (null) :
             <div className="lightboxView-frame"
@@ -134,28 +138,28 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                         renderDepth={0} />
                 </div>
                 {this.navBtn(undefined, "chevron-left",
-                    () => LightboxView.LightboxDoc && LightboxView.LightboxHistory.length ? "" : "none",
+                    () => LightboxView.LightboxDoc && LightboxView.LightboxHistory?.length ? "" : "none",
                     action(e => {
                         e.stopPropagation();
-                        const previous = LightboxView.LightboxHistory.pop();
-                        const target = LightboxView.LightboxDocTarget = LightboxView.LightboxHistory.lastElement();
+                        const previous = LightboxView.LightboxHistory?.pop();
+                        const target = LightboxView.LightboxDocTarget = LightboxView.LightboxHistory?.lastElement();
                         const docView = target && DocumentManager.Instance.getLightboxDocumentView(target);
                         if (docView && target) {
-                            if (LightboxView.LightboxFuture.lastElement() !== previous) LightboxView.LightboxFuture.push(previous);
+                            if (LightboxView.LightboxFuture?.lastElement() !== previous) LightboxView.LightboxFuture?.push(previous!);
                             docView.focus(target, true, 0.9);
                         } else {
                             LightboxView.SetLightboxDoc(target);
                         }
                     }))}
                 {this.navBtn(this.props.PanelWidth - Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]), "chevron-right",
-                    () => LightboxView.LightboxDoc && LightboxView.LightboxFuture.length ? "" : "none",
+                    () => LightboxView.LightboxDoc && LightboxView.LightboxFuture?.length ? "" : "none",
                     action(e => {
                         e.stopPropagation();
-                        const target = LightboxView.LightboxDocTarget = LightboxView.LightboxFuture.pop();
+                        const target = LightboxView.LightboxDocTarget = LightboxView.LightboxFuture?.pop();
                         const docView = target && DocumentManager.Instance.getLightboxDocumentView(target);
                         if (docView && target) {
                             docView.focus(target, true, 0.9);
-                            if (LightboxView.LightboxHistory.lastElement() !== target) LightboxView.LightboxHistory.push(target);
+                            if (LightboxView.LightboxHistory?.lastElement() !== target) LightboxView.LightboxHistory?.push(target);
                         } else {
                             LightboxView.SetLightboxDoc(target);
                         }
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 6425e1625..2d03c5279 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -44,7 +44,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
     _draggerRef = React.createRef<HTMLDivElement>();
     _pivotFieldDisposer?: IReactionDisposer;
     _autoHeightDisposer?: IReactionDisposer;
-    _docXfs: any[] = [];
+    _docXfs: { height: () => number, width: () => number, stackedDocTransform: () => Transform }[] = [];
     _columnStart: number = 0;
     @observable _heightMap = new Map<string, number>();
     @observable _cursor: CursorProperty = "grab";
@@ -204,6 +204,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
 
         let dref: Opt<HTMLDivElement>;
         const stackedDocTransform = () => this.getDocTransform(doc, dref);
+        this._docXfs.push({ stackedDocTransform, width, height });
         return <DocumentView ref={r => dref = r?.ContentDiv ? r.ContentDiv : undefined}
             Document={doc}
             DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])}
@@ -295,8 +296,8 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
         let dropAfter = 0;
         if (de.complete.docDragData) {
             this._docXfs.map((cd, i) => {
-                const pos = cd.dxf().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
-                const pos1 = cd.dxf().inverse().transformPoint(cd.width(), cd.height());
+                const pos = cd.stackedDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
+                const pos1 = cd.stackedDocTransform().inverse().transformPoint(cd.width(), cd.height());
                 if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && (i === this._docXfs.length - 1 || where[1] < pos1[1])) {
                     dropInd = i;
                     const axis = this.Document._viewType === CollectionViewType.Masonry ? 0 : 1;
@@ -327,8 +328,8 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
         const where = [e.clientX, e.clientY];
         let targInd = -1;
         this._docXfs.map((cd, i) => {
-            const pos = cd.dxf().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
-            const pos1 = cd.dxf().inverse().transformPoint(cd.width(), cd.height());
+            const pos = cd.stackedDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
+            const pos1 = cd.stackedDocTransform().inverse().transformPoint(cd.width(), cd.height());
             if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && where[1] < pos1[1]) {
                 targInd = i;
             }
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index db5414069..d76b61502 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -124,8 +124,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
         const anchor = this.fieldKey === "anchor1" ? "anchor2" : "anchor1";
         const anchorScale = !this.dataDoc[this.fieldKey + "-useLinkSmallAnchor"] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : .25;
 
-        const timecode = this.dataDoc[anchor + "_timecode"];
-        const targetTitle = StrCast((this.dataDoc[anchor] as Doc)?.title) + (timecode !== undefined ? ":" + timecode : "");
+        const targetTitle = StrCast((this.dataDoc[anchor] as Doc)?.title);
         const flyout = (
             <div className="linkAnchorBoxBox-flyout" title=" " onPointerOver={() => Doc.UnBrushDoc(this.rootDoc)}>
                 <LinkEditor sourceDoc={Cast(this.dataDoc[this.fieldKey], Doc, null)} hideback={true} linkDoc={this.rootDoc} showLinks={action(() => { })} />
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 77b050abe..b9480fa74 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -70,6 +70,7 @@ export class PinProps {
     audioRange?: boolean;
     unpin?: boolean;
     setPosition?: boolean;
+    hidePresBox?: boolean;
 }
 
 type PresBoxSchema = makeInterface<[typeof documentSchema]>;
-- 
cgit v1.2.3-70-g09d2


From 2a4af13c04b05d3bc4a13dc400dd842060ab0339 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 14 Feb 2021 12:11:50 -0500
Subject: simplistic version of branching lightboxview

---
 src/client/views/LightboxView.tsx | 111 +++++++++++++++++++++++++++-----------
 1 file changed, 79 insertions(+), 32 deletions(-)

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

diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index d36c3ad4f..43d65795c 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -14,6 +14,7 @@ import { DocumentManager } from '../util/DocumentManager';
 import { SelectionManager } from '../util/SelectionManager';
 import { TabDocView } from './collections/TabDocView';
 import { Cast, NumCast } from '../../fields/Types';
+import { path } from 'animejs';
 
 interface LightboxViewProps {
     PanelWidth: number;
@@ -26,6 +27,10 @@ export class LightboxView extends React.Component<LightboxViewProps> {
     public static SavedState: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number>, transition: Opt<string> }>;
     @observable static LightboxDoc: Opt<Doc>;
     @observable static LightboxDocTarget: Opt<Doc>;
+    public static LightboxHistory: Opt<Doc[]> = [];
+    public static LightboxFuture: Opt<Doc[]> = [];
+    public static LightboxDocView: Opt<DocumentView>;
+    static path: { doc: Opt<Doc>, target: Opt<Doc>, history: Opt<Doc[]>, future: Opt<Doc[]>, saved: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number>, transition: Opt<string> }> }[] = [];
     @action public static SetLightboxDoc(doc: Opt<Doc>, future?: Doc[]) {
         if (!doc) {
             if (this.LightboxDoc) {
@@ -51,21 +56,19 @@ export class LightboxView extends React.Component<LightboxViewProps> {
         return true;
     }
     public static IsLightboxDocView(path: DocumentView[]) { return path.includes(LightboxView.LightboxDocView!); }
-    public static LightboxHistory: Opt<Doc[]> = [];
-    public static LightboxFuture: Opt<Doc[]> = [];
-    public static LightboxDocView: Opt<DocumentView>;
     @computed get leftBorder() { return Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]); }
     @computed get topBorder() { return Math.min(this.props.PanelHeight / 4, this.props.maxBorder[1]); }
     lightboxWidth = () => this.props.PanelWidth - this.leftBorder * 2;
     lightboxHeight = () => this.props.PanelHeight - this.topBorder * 2;
     lightboxScreenToLocal = () => new Transform(-this.leftBorder, -this.topBorder, 1);
-    navBtn = (left: Opt<number>, icon: string, display: () => string, click: (e: React.MouseEvent) => void) => {
+    navBtn = (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: string, display: () => string, click: (e: React.MouseEvent) => void) => {
         return <div className="lightboxView-navBtn-frame" style={{
             display: display(),
             left,
-            width: Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0])
+            width: bottom !== undefined ? undefined : Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]),
+            bottom
         }}>
-            <div className="lightboxView-navBtn" style={{ top: this.props.PanelHeight / 2 - 12.50 }}
+            <div className="lightboxView-navBtn" style={{ top }}
                 onClick={click}>
                 <FontAwesomeIcon icon={icon as any} size="3x" />
             </div>
@@ -81,6 +84,63 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                 .sort((a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)));
     }
     addDocTab = LightboxView.AddDocTab;
+    @action
+    stepForward = () => {
+        const target = LightboxView.LightboxDocTarget = LightboxView.LightboxFuture?.pop();
+        const docView = target && DocumentManager.Instance.getLightboxDocumentView(target);
+        if (docView && target) {
+            docView.focus(target, true, 0.9);
+            if (LightboxView.LightboxHistory?.lastElement() !== target) LightboxView.LightboxHistory?.push(target);
+        } else {
+            if (!target && LightboxView.path.length) {
+                const saved = LightboxView.SavedState;
+                if (LightboxView.LightboxDoc) {
+                    LightboxView.LightboxDoc._panX = saved?.panX;
+                    LightboxView.LightboxDoc._panY = saved?.panY;
+                    LightboxView.LightboxDoc._viewScale = saved?.scale;
+                    LightboxView.LightboxDoc._viewTransition = saved?.transition;
+                }
+                const pop = LightboxView.path.pop();
+                if (pop) {
+                    LightboxView.LightboxDoc = pop.doc;
+                    LightboxView.LightboxDocTarget = pop.target;
+                    LightboxView.LightboxFuture = pop.future;
+                    LightboxView.LightboxHistory = pop.history;
+                    LightboxView.SavedState = pop.saved;
+                }
+            } else {
+                LightboxView.SetLightboxDoc(target);
+            }
+        }
+    }
+    @action
+    stepBackward = () => {
+        const previous = LightboxView.LightboxHistory?.pop();
+        const target = LightboxView.LightboxDocTarget = LightboxView.LightboxHistory?.lastElement();
+        const docView = target && DocumentManager.Instance.getLightboxDocumentView(target);
+        if (docView && target) {
+            if (LightboxView.LightboxFuture?.lastElement() !== previous) LightboxView.LightboxFuture?.push(previous!);
+            docView.focus(target, true, 0.9);
+        } else {
+            LightboxView.SetLightboxDoc(target);
+        }
+    }
+    @action
+    stepInto = () => {
+        LightboxView.path.push({
+            doc: LightboxView.LightboxDoc,
+            target: LightboxView.LightboxDocTarget,
+            future: LightboxView.LightboxFuture,
+            history: LightboxView.LightboxHistory,
+            saved: LightboxView.SavedState
+        });
+        const coll = LightboxView.LightboxDocTarget;
+        if (coll) {
+            const fieldKey = Doc.LayoutFieldKey(coll);
+            LightboxView.SetLightboxDoc(coll, [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + "-annotations"])]);
+        }
+        setTimeout(() => this.stepForward());
+    }
 
     fitToBox = () => LightboxView.LightboxDocTarget === LightboxView.LightboxDoc;
     render() {
@@ -137,34 +197,21 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                         ContainingCollectionDoc={undefined}
                         renderDepth={0} />
                 </div>
-                {this.navBtn(undefined, "chevron-left",
-                    () => LightboxView.LightboxDoc && LightboxView.LightboxHistory?.length ? "" : "none",
-                    action(e => {
+                {this.navBtn(0, undefined, this.props.PanelHeight / 2 - 12.50, "chevron-left",
+                    () => LightboxView.LightboxDoc && LightboxView.LightboxHistory?.length ? "" : "none", e => {
                         e.stopPropagation();
-                        const previous = LightboxView.LightboxHistory?.pop();
-                        const target = LightboxView.LightboxDocTarget = LightboxView.LightboxHistory?.lastElement();
-                        const docView = target && DocumentManager.Instance.getLightboxDocumentView(target);
-                        if (docView && target) {
-                            if (LightboxView.LightboxFuture?.lastElement() !== previous) LightboxView.LightboxFuture?.push(previous!);
-                            docView.focus(target, true, 0.9);
-                        } else {
-                            LightboxView.SetLightboxDoc(target);
-                        }
-                    }))}
-                {this.navBtn(this.props.PanelWidth - Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]), "chevron-right",
-                    () => LightboxView.LightboxDoc && LightboxView.LightboxFuture?.length ? "" : "none",
-                    action(e => {
+                        this.stepBackward();
+                    })}
+                {this.navBtn(this.props.PanelWidth - Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]), undefined, this.props.PanelHeight / 2 - 12.50, "chevron-right",
+                    () => LightboxView.LightboxDoc && LightboxView.LightboxFuture?.length ? "" : "none", e => {
                         e.stopPropagation();
-                        const target = LightboxView.LightboxDocTarget = LightboxView.LightboxFuture?.pop();
-                        const docView = target && DocumentManager.Instance.getLightboxDocumentView(target);
-                        if (docView && target) {
-                            docView.focus(target, true, 0.9);
-                            if (LightboxView.LightboxHistory?.lastElement() !== target) LightboxView.LightboxHistory?.push(target);
-                        } else {
-                            LightboxView.SetLightboxDoc(target);
-                        }
-                    }))}
-
+                        this.stepForward();
+                    })}
+                {this.navBtn("50%", 0, 0, "chevron-down",
+                    () => LightboxView.LightboxDoc && LightboxView.LightboxFuture?.length ? "" : "none", e => {
+                        e.stopPropagation();
+                        this.stepInto();
+                    })}
             </div>;
     }
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 26bbd3a593e6f51ec3665e124e66ed8030ab98df Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 14 Feb 2021 12:36:30 -0500
Subject: sort by timecode in lightbox

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

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

diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 43d65795c..eb3d62779 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -49,7 +49,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
             };
         }
         if (future) {
-            LightboxView.LightboxFuture = future.slice().sort((a, b) => DocListCast(a.links).length - DocListCast(b.links).length);
+            LightboxView.LightboxFuture = future.slice().sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)).sort((a, b) => DocListCast(a.links).length - DocListCast(b.links).length);
         }
         LightboxView.LightboxDoc = LightboxView.LightboxDocTarget = doc;
 
-- 
cgit v1.2.3-70-g09d2


From 6fcef3d8dfdbe373724d2f11c9df8f350bd8950c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 14 Feb 2021 18:29:00 -0500
Subject: fixes to focus()'s api and extension to allow focusing on a Tab's
 contents.

---
 src/client/documents/Documents.ts                  |  7 +--
 src/client/util/DocumentManager.ts                 | 38 ++++++------
 src/client/util/LinkManager.ts                     | 28 +++++----
 src/client/views/LightboxView.tsx                  |  5 +-
 .../collections/CollectionStackedTimeline.tsx      |  4 +-
 .../views/collections/CollectionStackingView.tsx   | 12 ++--
 src/client/views/collections/TabDocView.tsx        | 29 +++++++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 71 ++++++++++++----------
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 35 +++++++----
 src/client/views/nodes/ImageBox.tsx                |  2 +-
 src/client/views/nodes/PDFBox.tsx                  |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 13 files changed, 139 insertions(+), 98 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index d72efb1b4..fee2679b6 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -35,6 +35,7 @@ import { AudioBox } from "../views/nodes/AudioBox";
 import { ColorBox } from "../views/nodes/ColorBox";
 import { ComparisonBox } from "../views/nodes/ComparisonBox";
 import { DocHolderBox } from "../views/nodes/DocHolderBox";
+import { DocFocusOptions } from "../views/nodes/DocumentView";
 import { FilterBox } from "../views/nodes/FilterBox";
 import { FontIconBox } from "../views/nodes/FontIconBox";
 import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox";
@@ -55,8 +56,6 @@ import { PresElementBox } from "../views/presentationview/PresElementBox";
 import { SearchBox } from "../views/search/SearchBox";
 import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
 import { DocumentType } from "./DocumentTypes";
-import { DocAfterFocusFunc } from "../views/nodes/DocumentView";
-import { Transform } from "../util/Transform";
 const path = require('path');
 
 const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", ""));
@@ -1020,8 +1019,8 @@ export namespace DocUtils {
         });
     }
 
-    export function DefaultFocus(doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) {
-        afterFocus?.(false);
+    export function DefaultFocus(doc: Doc, options?: DocFocusOptions) {
+        options?.afterFocus?.(false);
     }
 
     export let ActiveRecordings: AudioBox[] = [];
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index d2251583c..dd59efa41 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -139,9 +139,11 @@ export class DocumentManager {
         closeContextIfNotFound: boolean = false, // after opening a context where the document should be, this determines whether the context should be closed if the Doc isn't actually there
         originatingDoc: Opt<Doc> = undefined, // doc that initiated the display of the target odoc
         finished?: () => void,
+        originalTarget?: Doc
     ): Promise<void> => {
+        originalTarget = originalTarget ?? targetDoc;
         const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView;
-        let docView = getFirstDocView(targetDoc, originatingDoc);
+        const docView = getFirstDocView(targetDoc, originatingDoc);
         const highlight = () => {
             const finalDocView = getFirstDocView(targetDoc);
             finalDocView && Doc.linkFollowHighlight(finalDocView.rootDoc);
@@ -166,15 +168,16 @@ export class DocumentManager {
         const targetDocContext = contextDoc || annotatedDoc;
         const targetDocContextView = targetDocContext && getFirstDocView(targetDocContext);
         if (!docView && targetDoc.type === DocumentType.TEXTANCHOR && rtfView) {
-            rtfView.focus(targetDoc, false);
+            rtfView.focus(targetDoc);
         }
         else if (docView) {
-            docView.props.focus(targetDoc, willZoom, undefined, (didFocus: boolean) =>
-                new Promise<boolean>(res => {
-                    focusAndFinish(didFocus);
-                    res();
-                })
-            );
+            docView.props.focus(targetDoc, {
+                originalTarget, willZoom, afterFocus: (didFocus: boolean) =>
+                    new Promise<boolean>(res => {
+                        focusAndFinish(didFocus);
+                        res();
+                    })
+            });
         } 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)));
@@ -182,7 +185,7 @@ export class DocumentManager {
             } else {  // otherwise try to get a view of the context of the target
                 if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first..
                     targetDocContext._viewTransition = "transform 500ms";
-                    targetDocContextView.props.focus(targetDocContextView.rootDoc, willZoom);
+                    targetDocContextView.props.focus(targetDocContextView.rootDoc, { willZoom });
 
                     // 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;
@@ -192,12 +195,13 @@ export class DocumentManager {
                         const findView = (delay: number) => {
                             const retryDocView = getFirstDocView(targetDoc);  // 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
-                                retryDocView.props.focus(targetDoc, willZoom, undefined, (didFocus: boolean) =>
-                                    new Promise<boolean>(res => {
-                                        focusAndFinish(didFocus);
-                                        res();
-                                    })
-                                ); // focus on the target in the context
+                                retryDocView.props.focus(targetDoc, {
+                                    willZoom, afterFocus: (didFocus: boolean) =>
+                                        new Promise<boolean>(res => {
+                                            focusAndFinish(didFocus);
+                                            res();
+                                        })
+                                }); // focus on the target in the context
                                 highlight();
                             } else if (delay > 1500) {
                                 // we didn't find the target, so it must have moved out of the context.  Go back to just creating it.
@@ -213,7 +217,7 @@ export class DocumentManager {
                         findView(0);
                     }
                 } else {  // there's no context view so we need to create one first and try again when that finishes
-                    const finishFunc = () => this.jumpToDocument(targetDoc, true, createViewFunc, docContext, linkDoc, true /* if we don't find the target, we want to get rid of the context just created */, undefined, finished);
+                    const finishFunc = () => this.jumpToDocument(targetDoc, true, createViewFunc, docContext, linkDoc, true /* if we don't find the target, we want to get rid of the context just created */, undefined, finished, originalTarget);
                     createViewFunc(targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target
                         finishFunc);
                 }
@@ -222,4 +226,4 @@ export class DocumentManager {
     }
 
 }
-Scripting.addGlobal(function DocFocus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, true)); });
\ No newline at end of file
+Scripting.addGlobal(function DocFocus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, { willZoom: true })); });
\ No newline at end of file
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 1e6e02e55..f741d9c2c 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -111,10 +111,12 @@ export class LinkManager {
                 setTimeout(() => {
                     const targDocView = DocumentManager.Instance.getFirstDocumentView(doc);
                     if (targDocView) {
-                        targDocView.props.focus(doc, BoolCast(sourceDoc.followLinkZoom, false), undefined, (didFocus: boolean) => {
-                            finished?.();
-                            res(true);
-                            return new Promise<boolean>(res2 => res2());
+                        targDocView.props.focus(doc, {
+                            willZoom: BoolCast(sourceDoc.followLinkZoom, false), afterFocus: (didFocus: boolean) => {
+                                finished?.();
+                                res(true);
+                                return new Promise<boolean>(res2 => res2());
+                            }
                         });
                     } else {
                         res(where !== "inPlace"); // return true to reset the initial focus&zoom (return false for 'inPlace' since resetting the initial focus&zoom will negate the zoom into the target)
@@ -126,30 +128,30 @@ export class LinkManager {
                 createTabForTarget(false);
             } else {
                 // first focus & zoom onto this (the clicked document).  Then execute the function to focus on the target
-                docViewProps.focus(sourceDoc, BoolCast(sourceDoc.followLinkZoom, true), 1, createTabForTarget);
+                docViewProps.focus(sourceDoc, { willZoom: BoolCast(sourceDoc.followLinkZoom, true), scale: 1, afterFocus: createTabForTarget });
             }
         };
         LinkManager.traverseLink(linkDoc, sourceDoc, createViewFunc, BoolCast(sourceDoc.followLinkZoom, false), docViewProps.ContainingCollectionDoc, batch.end, altKey ? true : undefined);
     }
 
-    public static traverseLink(link: Opt<Doc>, doc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) {
-        const linkDocs = link ? [link] : DocListCast(doc.links);
-        const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor1
-        const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor2
+    public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) {
+        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 linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView;
         const linkDocList = linkWithoutTargetDoc ? [linkWithoutTargetDoc] : (traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs);
-        const followLinks = linkDocList.length ? (doc.isPushpin ? linkDocList : [linkDocList[0]]) : [];
+        const followLinks = linkDocList.length ? (sourceDoc.isPushpin ? linkDocList : [linkDocList[0]]) : [];
         followLinks.forEach(async linkDoc => {
             if (linkDoc) {
-                const target = (doc === linkDoc.anchor1 ? linkDoc.anchor2 : doc === linkDoc.anchor2 ? linkDoc.anchor1 :
-                    (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc;
+                const target = (sourceDoc === linkDoc.anchor1 ? linkDoc.anchor2 : sourceDoc === linkDoc.anchor2 ? linkDoc.anchor1 :
+                    (Doc.AreProtosEqual(sourceDoc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc;
                 if (target) {
                     const containerDoc = Cast(target.annotationOn, Doc, null) || target;
                     const targetContext = Cast(containerDoc?.context, Doc, null);
                     const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
-                    DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "add:right"), finished), targetNavContext, linkDoc, undefined, doc, finished);
+                    DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "add:right"), finished), targetNavContext, linkDoc, undefined, sourceDoc, finished);
                 } else {
                     finished?.();
                 }
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index eb3d62779..a2088a8fc 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -89,7 +89,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
         const target = LightboxView.LightboxDocTarget = LightboxView.LightboxFuture?.pop();
         const docView = target && DocumentManager.Instance.getLightboxDocumentView(target);
         if (docView && target) {
-            docView.focus(target, true, 0.9);
+            docView.focus(target, { willZoom: true, scale: 0.9 });
             if (LightboxView.LightboxHistory?.lastElement() !== target) LightboxView.LightboxHistory?.push(target);
         } else {
             if (!target && LightboxView.path.length) {
@@ -120,7 +120,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
         const docView = target && DocumentManager.Instance.getLightboxDocumentView(target);
         if (docView && target) {
             if (LightboxView.LightboxFuture?.lastElement() !== previous) LightboxView.LightboxFuture?.push(previous!);
-            docView.focus(target, true, 0.9);
+            docView.focus(target, { willZoom: true, scale: 0.9 });
         } else {
             LightboxView.SetLightboxDoc(target);
         }
@@ -138,6 +138,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
         if (coll) {
             const fieldKey = Doc.LayoutFieldKey(coll);
             LightboxView.SetLightboxDoc(coll, [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + "-annotations"])]);
+            TabDocView.PinDoc(coll, { hidePresBox: true });
         }
         setTimeout(() => this.stepForward());
     }
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index efbab3ede..e3e753902 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -259,8 +259,8 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
         const anchor = observable({ view: undefined as any });
         const focusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: (notFocused: boolean) => Promise<boolean>, docTransform?: Transform) => {
             this.props.playLink(mark);
-            this.props.focus(doc, willZoom, scale, afterFocus, docTransform);
-        }
+            this.props.focus(doc, { willZoom, scale, afterFocus, docTransform });
+        };
         return {
             anchor, view: <DocumentView key="view"  {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
                 ref={action((r: DocumentView | null) => anchor.view = r)}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 2d03c5279..1d6112745 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -21,7 +21,7 @@ import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
 import { EditableView } from "../EditableView";
 import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
-import { DocumentView, DocAfterFocusFunc, DocumentViewProps } from "../nodes/DocumentView";
+import { DocumentView, DocumentViewProps, DocFocusOptions } from "../nodes/DocumentView";
 import { FieldViewProps } from "../nodes/FieldView";
 import { StyleProp } from "../StyleProvider";
 import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow";
@@ -168,7 +168,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
         return this.props.addDocTab(doc, where);
     }
 
-    focusDocument = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => {
+    focusDocument = (doc: Doc, options?: DocFocusOptions) => {
         Doc.BrushDoc(doc);
         Doc.linkFollowHighlight(doc);
 
@@ -181,9 +181,11 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
                 smoothScroll(focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500, this._mainCont!, localTop[1] + this._mainCont!.scrollTop);
             }
         }
-        const endFocus = async (moved: boolean) => afterFocus ? afterFocus(moved) : false;
-        this.props.focus(this.rootDoc, willZoom, scale, (didFocus: boolean) =>
-            new Promise<boolean>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)));
+        const endFocus = async (moved: boolean) => options?.afterFocus ? options?.afterFocus(moved) : false;
+        this.props.focus(this.rootDoc, {
+            willZoom: options?.willZoom, scale: options?.scale, afterFocus: (didFocus: boolean) =>
+                new Promise<boolean>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed))
+        });
 
     }
 
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index d62b8b6f6..4c792186a 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -14,6 +14,7 @@ import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
+import { DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { CurrentUserUtils } from '../../util/CurrentUserUtils';
 import { DocumentManager } from '../../util/DocumentManager';
@@ -23,7 +24,7 @@ import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { LightboxView } from '../LightboxView';
-import { DocAfterFocusFunc, DocumentView, DocumentViewProps } from "../nodes/DocumentView";
+import { DocFocusOptions, DocumentView, DocumentViewProps } from "../nodes/DocumentView";
 import { FieldViewProps } from '../nodes/FieldView';
 import { PinProps, PresBox, PresMovement } from '../nodes/PresBox';
 import { DefaultLayerProvider, DefaultStyleProvider, StyleLayers, StyleProp } from '../StyleProvider';
@@ -34,7 +35,6 @@ import { CollectionViewType } from './CollectionView';
 import "./TabDocView.scss";
 import React = require("react");
 import Color = require('color');
-import { DocUtils } from '../../documents/Documents';
 const _global = (window /* browser */ || global /* node */) as any;
 
 interface TabDocViewProps {
@@ -209,7 +209,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             const tabs = Cast(sublists[0], Doc, null);
             const tabdocs = await DocListCastAsync(tabs.data);
             runInAction(() => {
-                if (!tabdocs?.includes(curPres)) {
+                if (!pinProps?.hidePresBox && !tabdocs?.includes(curPres)) {
                     tabdocs?.push(curPres);  // bcz: Argh! this is annoying.  if multiple documents are pinned, this will get called multiple times before the presentation view is drawn.  Thus it won't be in the tabdocs list and it will get created multple times.  so need to explicilty add the presbox to the list of open tabs
                     CollectionDockingView.AddSplit(curPres, "right");
                 }
@@ -279,7 +279,10 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             case "close": return CollectionDockingView.CloseSplit(doc, locationParams);
             case "fullScreen": return CollectionDockingView.OpenFullScreen(doc);
             case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack);
-            case "lightbox": return LightboxView.AddDocTab(doc, location);
+            case "lightbox": {
+                // TabDocView.PinDoc(doc, { hidePresBox: true });
+                return LightboxView.AddDocTab(doc, location);
+            }
             case "inPlace":
             case "add":
             default:
@@ -357,11 +360,25 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             </Tooltip>
         </>;
     }
-    focusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => {
+    @action
+    focusFunc = (doc: Doc, options?: DocFocusOptions) => {
+        const vals = (!options?.originalTarget || options?.originalTarget === this._document) && this.view?.ComponentView?.freeformData?.(true);
+        if (vals && this._document) {
+            const focusSpeed = 1000;
+            this._document._panX = vals.panX;
+            this._document._panY = vals.panY;
+            this._document._viewScale = vals.scale;
+            this._document._viewTrasition = `transform ${focusSpeed}ms`;
+            setTimeout(action(() => {
+                this._document!._viewTransition = undefined;
+                options?.afterFocus?.(false);
+            }), focusSpeed);
+        } else {
+            options?.afterFocus?.(false);
+        }
         if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) {
             this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
         }
-        afterFocus?.(false);
     }
     active = () => this._isActive;
     ScreenToLocalTransform = () => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 954b6478f..92916b5db 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,22 +1,23 @@
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { computedFn } from "mobx-utils";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, StrListCast } from "../../../../fields/Doc";
+import { Doc, HeightSym, Opt, StrListCast, WidthSym } from "../../../../fields/Doc";
 import { collectionSchema, documentSchema } from "../../../../fields/documentSchemas";
 import { Id } from "../../../../fields/FieldSymbols";
 import { InkData, InkField, InkTool } from "../../../../fields/InkField";
 import { List } from "../../../../fields/List";
 import { RichTextField } from "../../../../fields/RichTextField";
-import { createSchema, makeInterface, listSpec } from "../../../../fields/Schema";
+import { createSchema, listSpec, makeInterface } from "../../../../fields/Schema";
 import { ScriptField } from "../../../../fields/ScriptField";
 import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
 import { TraceMobx } from "../../../../fields/util";
 import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
-import { aggregateBounds, intersectRect, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils, returnVal, returnTrue } from "../../../../Utils";
+import { aggregateBounds, intersectRect, returnFalse, setupMoveUpEvents, Utils } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { DocServer } from "../../../DocServer";
 import { Docs, DocUtils } from "../../../documents/Documents";
 import { DocumentType } from "../../../documents/DocumentTypes";
+import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
 import { DocumentManager } from "../../../util/DocumentManager";
 import { DragManager, dropActionType } from "../../../util/DragManager";
 import { HistoryUtil } from "../../../util/History";
@@ -30,13 +31,16 @@ import { undoBatch } from "../../../util/UndoManager";
 import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";
 import { Timeline } from "../../animationtimeline/Timeline";
 import { ContextMenu } from "../../ContextMenu";
+import { DocumentDecorations } from "../../DocumentDecorations";
 import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth } from "../../InkingStroke";
-import { CollectionFreeFormDocumentView, CollectionFreeFormDocumentViewProps } from "../../nodes/CollectionFreeFormDocumentView";
-import { DocumentLinksButton } from "../../nodes/DocumentLinksButton";
-import { DocumentViewProps, DocAfterFocusFunc, DocumentView } from "../../nodes/DocumentView";
+import { LightboxView } from "../../LightboxView";
+import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
+import { DocFocusOptions, DocumentView, DocumentViewProps } from "../../nodes/DocumentView";
+import { FieldViewProps } from "../../nodes/FieldView";
 import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
 import { pageSchema } from "../../nodes/ImageBox";
 import { PresBox } from "../../nodes/PresBox";
+import { StyleLayers, StyleProp } from "../../StyleProvider";
 import { CollectionDockingView } from "../CollectionDockingView";
 import { CollectionSubView } from "../CollectionSubView";
 import { CollectionViewType } from "../CollectionView";
@@ -45,12 +49,6 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso
 import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
-import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
-import { StyleProp, StyleLayers } from "../../StyleProvider";
-import { DocumentDecorations } from "../../DocumentDecorations";
-import { FieldViewProps } from "../../nodes/FieldView";
-import { reset } from "colors";
-import { LightboxView } from "../../LightboxView";
 
 export const panZoomSchema = createSchema({
     _panX: "number",
@@ -118,8 +116,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && (this.props.ContainingCollectionView?.active() || this.props.active()); }
     @computed get fitToContentVals() {
-        return this.fitToContent &&
-        {
+        return {
             panX: (this.contentBounds.x + this.contentBounds.r) / 2,
             panY: (this.contentBounds.y + this.contentBounds.b) / 2,
             scale: !this.childDocs.length ? 1 :
@@ -135,9 +132,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
     private get scaleFieldKey() { return this.props.scaleField || "_viewScale"; }
     private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
-    private panX = () => this.fitToContentVals?.panX ?? NumCast(this.Document._panX);
-    private panY = () => this.fitToContentVals?.panY ?? NumCast(this.Document._panY);
-    private zoomScaling = () => (this.fitToContentVals?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1)) / this.parentScaling;
+    private panX = () => this.freeformData()?.panX ?? NumCast(this.Document._panX);
+    private panY = () => this.freeformData()?.panY ?? NumCast(this.Document._panY);
+    private zoomScaling = () => (this.freeformData()?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1)) / this.parentScaling;
     @computed get cachedCenteringShiftX(): number {
         const scaling = this.fitToContent || !this.contentScaling ? 1 : this.contentScaling;
         return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / this.parentScaling / scaling;  // shift so pan position is at center of window for non-overlay collections
@@ -860,7 +857,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 else if (ranges.yrange.max <= (panY - panelDim[1] / 2)) panY = ranges.yrange.min - panelDim[1] / 2;
             }
         }
-        if (!this.layoutDoc._lockedTransform || CurrentUserUtils.OverlayDocs.includes(this.Document)) {
+        if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc || CurrentUserUtils.OverlayDocs.includes(this.Document)) {
             this.Document._viewTransition = panType;
             const scale = this.getLocalTransform().inverse().Scale;
             const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
@@ -899,7 +896,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         this.layoutDoc._panY = NumCast(this.layoutDoc._panY) - newpan[1];
     }
 
-    focusDocument = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => {
+    focusDocument = (doc: Doc, options?: DocFocusOptions) => {
         const state = HistoryUtil.getState();
 
         // TODO This technically isn't correct if type !== "doc", as
@@ -918,23 +915,26 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const LightboxState = LightboxView.LightboxDoc === this.props.Document && LightboxView.SavedState ? LightboxView.SavedState : undefined;
         SelectionManager.DeselectAll();
         if (this.props.Document.scrollHeight) {
-            this.props.focus(doc, willZoom, scale, afterFocus);
+            this.props.focus(doc, options);
         } else {
-            const xfToCollection = docTransform ?? Transform.Identity();
+            const xfToCollection = options?.docTransform ?? Transform.Identity();
             const layoutdoc = Doc.Layout(doc);
             const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: this.Document[this.scaleFieldKey], transition: this.Document._viewTransition };
             const newState = HistoryUtil.getState();
-            const cantTransform = this.props.isAnnotationOverlay || this.rootDoc._isGroup || this.layoutDoc._lockedTransform;
-            const { panX, panY } = cantTransform ? savedState : this.setPanIntoView(layoutdoc, xfToCollection, willZoom ? scale || .75 : undefined);
+            const cantTransform = this.props.isAnnotationOverlay || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
+            const { panX, panY, scale } = cantTransform ? savedState : this.setPanIntoView(layoutdoc, xfToCollection, options?.willZoom ? options?.scale || .75 : undefined);
             if (!cantTransform) {   // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection
                 newState.initializers![this.Document[Id]] = { panX: panX, panY: panY };
                 HistoryUtil.pushState(newState);
             }
             // focus on the document in the collection
-            const didMove = !doc.z && (panX !== savedState.panX || panY !== savedState.panY);
+            const didMove = !doc.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== undefined);
             const focusSpeed = didMove ? (doc.focusSpeed !== undefined ? Number(doc.focusSpeed) : 500) : 0;
             // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
-            if (didMove) this.setPan(panX, panY, `transform ${focusSpeed}ms`, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
+            if (didMove) {
+                this.setPan(panX, panY, `transform ${focusSpeed}ms`, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
+                options?.willZoom && (this.Document[this.scaleFieldKey] = scale);
+            }
             Doc.BrushDoc(this.rootDoc);
             !doc.hidden && Doc.linkFollowHighlight(doc);
 
@@ -942,7 +942,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             // focus on this collection within its parent view.  the parent view after focusing determines whether to reset the view change within the collection
             const endFocus = async (moved: boolean) => {
                 doc.hidden && Doc.UnHighlightDoc(doc);
-                const resetView = afterFocus ? await afterFocus(moved) : false;
+                const resetView = options?.afterFocus ? await options?.afterFocus(moved) : false;
                 if (resetView) {
                     const restoreState = LightboxView.LightboxDoc !== this.props.Document && LightboxState ? LightboxState : savedState;
                     this.Document._panX = restoreState.panX;
@@ -959,8 +959,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     new Transform(NumCast(this.rootDoc.x) + this.rootDoc[WidthSym]() / 2 - NumCast(this.rootDoc._panX),
                         NumCast(this.rootDoc.y) + this.rootDoc[HeightSym]() / 2 - NumCast(this.rootDoc._panY), 1);
 
-            this.props.focus(cantTransform ? doc : this.rootDoc, willZoom, scale, (didFocus: boolean) =>
-                new Promise<boolean>(res => setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime)))), xf);
+            this.props.focus(cantTransform ? doc : this.rootDoc, {
+                ...options, docTransform: xf,
+                afterFocus: (didFocus: boolean) => new Promise<boolean>(res =>
+                    setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime))))
+            });
         }
     }
 
@@ -973,8 +976,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
         if (scale) {
             const maxZoom = 2; // sets the limit for how far we will zoom. this is useful for preventing small text boxes from filling the screen. So probably needs to be more sophisticated to consider more about the target and context
-            this.Document[this.scaleFieldKey] = Math.min(maxZoom, scale * Math.min(this.props.PanelWidth() / Math.abs(pt2[0] - pt[0]), this.props.PanelHeight() / Math.abs(pt2[1] - pt[1])));
-            return { panX: (bounds.left + bounds.right) / 2, panY: (bounds.top + bounds.bot) / 2 };
+
+            return {
+                panX: (bounds.left + bounds.right) / 2,
+                panY: (bounds.top + bounds.bot) / 2,
+                scale: Math.min(maxZoom, scale * Math.min(this.props.PanelWidth() / Math.abs(pt2[0] - pt[0]), this.props.PanelHeight() / Math.abs(pt2[1] - pt[1])))
+            };
         } else {
             const cx = NumCast(this.layoutDoc._panX);
             const cy = NumCast(this.layoutDoc._panY);
@@ -1188,7 +1195,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         this.Document._useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true);
         return elements;
     }
-    freeformData = () => { return this.fitToContentVals; }
+    freeformData = (force?: boolean) => this.fitToContent || force ? this.fitToContentVals : undefined;
     @action
     componentDidMount() {
         super.componentDidMount?.();
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 4b0422ed3..e93d7ff72 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -153,7 +153,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     panelWidth = () => (this.sizeProvider?.width || this.props.PanelWidth?.());
     panelHeight = () => (this.sizeProvider?.height || this.props.PanelHeight?.());
     screenToLocalTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.X, -this.Y);
-    focusDoc = (doc: Doc) => this.props.focus(doc, false);
+    focusDoc = (doc: Doc) => this.props.focus(doc);
     returnThis = () => this;
     render() {
         TraceMobx();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 9801de1a2..9d91311f6 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,6 +1,6 @@
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast, HeightSym } from "../../../fields/Doc";
+import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast } from "../../../fields/Doc";
 import { Document } from '../../../fields/documentSchemas';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
@@ -37,13 +37,20 @@ import { DocumentLinksButton } from './DocumentLinksButton';
 import "./DocumentView.scss";
 import { FieldViewProps } from "./FieldView";
 import { LinkAnchorBox } from './LinkAnchorBox';
+import { LinkDocPreview } from "./LinkDocPreview";
 import { PresBox } from './PresBox';
 import { RadialMenu } from './RadialMenu';
 import React = require("react");
-import { LinkDocPreview } from "./LinkDocPreview";
 
+export interface DocFocusOptions {
+    originalTarget?: Doc;
+    willZoom?: boolean;
+    scale?: number;
+    afterFocus?: DocAfterFocusFunc;
+    docTransform?: Transform;
+}
 export type DocAfterFocusFunc = (notFocused: boolean) => Promise<boolean>;
-export type DocFocusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => void;
+export type DocFocusFunc = (doc: Doc, options?: DocFocusOptions) => void;
 export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => any;
 export interface DocComponentView {
     getAnchor?: () => Doc;
@@ -52,7 +59,7 @@ export interface DocComponentView {
     forward?: () => boolean;
     url?: () => string;
     submitURL?: (url: string) => boolean;
-    freeformData?: () => Opt<{ panX: number, panY: number, scale: number }>;
+    freeformData?: (force?: boolean) => Opt<{ panX: number, panY: number, scale: number }>;
 }
 export interface DocumentViewSharedProps {
     renderDepth: number;
@@ -376,11 +383,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
     }
 
-    focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => {
+    focus = (doc: Doc, options?: DocFocusOptions) => {
         const focusSpeed = this._componentView?.scrollFocus?.(doc, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here      
-        const endFocus = focusSpeed === undefined ? afterFocus : async (moved: boolean) => afterFocus ? afterFocus(true) : false;
-        this.props.focus(docTransform ? doc : this.rootDoc, willZoom, scale, (didFocus: boolean) =>
-            new Promise<boolean>(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : false), focusSpeed ?? 0)), docTransform);
+        const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : false;
+        this.props.focus(options?.docTransform ? doc : this.rootDoc, {
+            ...options, afterFocus: (didFocus: boolean) =>
+                new Promise<boolean>(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : false), focusSpeed ?? 0))
+        });
 
     }
     onClick = action((e: React.MouseEvent | React.PointerEvent) => {
@@ -439,7 +448,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 } else clickFunc();
             } else if (this.Document["onClick-rawScript"] && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) {// bcz: hack? don't edit a script if you're clicking on a scripting box itself
                 this.props.addDocTab(DocUtils.makeCustomViewClicked(Doc.MakeAlias(this.props.Document), undefined, "onClick"), "add:right");
-            } else if (this.allLinks && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
+            } else if (this.allLinks && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey && !(e.nativeEvent as any).formattedHandled) {
                 this.allLinks.length && LinkManager.FollowLink(undefined, this.props.Document, this.props, e.altKey);
             } else {
                 if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) {  // onDragStart implies a button doc that we don't want to select when clicking.   RootDocument & isTemplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
@@ -575,7 +584,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         const linkSource = de.complete.annoDragData ? de.complete.annoDragData.annotationDocument : de.complete.linkDragData ? de.complete.linkDragData.linkSourceDocument : undefined;
         if (linkSource && linkSource !== this.props.Document) {
             e.stopPropagation();
-            const dropDoc = this._componentView?.getAnchor() || this.rootDoc;
+            const dropDoc = this._componentView?.getAnchor?.() || this.rootDoc;
             if (de.complete.annoDragData) de.complete.annoDragData.dropDocument = dropDoc;
             de.complete.linkDocument = DocUtils.MakeLink({ doc: linkSource }, { doc: dropDoc }, "link", undefined, undefined, undefined, [de.x, de.y]);
         }
@@ -718,7 +727,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
     contentScaling = () => this.ContentScale;
     onClickFunc = () => this.onClickHandler;
-    setContentView = (view: { getAnchor: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view;
+    setContentView = (view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view;
     @observable contentsActive: () => boolean = returnFalse;
     @action setContentsActive = (setActive: () => boolean) => this.contentsActive = setActive;
     @computed get contents() {
@@ -918,8 +927,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight());
     contentsActive = () => this.docView?.contentsActive();
-    focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => {
-        return this.docView?.focus(doc, willZoom, scale, afterFocus);
+    focus = (doc: Doc, options?: DocFocusOptions) => {
+        return this.docView?.focus(doc, options);
     }
     getBounds = () => {
         if (!this.docView || !this.docView.ContentDiv || this.docView.props.renderDepth === 0 || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 441d6232a..74bff2bfe 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -152,7 +152,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
         });
     }
     @undoBatch
-    resolution = () => this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes;
+    resolution = () => this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes
 
     @undoBatch
     rotate = action(() => {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index b941c07f6..33147e7f3 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -138,7 +138,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
             this.scrollFocus(this.initialScrollTarget, false);
             this.initialScrollTarget = undefined;
         }
-    };
+    }
     searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => this._searchString = e.currentTarget.value;
 
     settingsPanel() {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 9f9f11ab5..a63e0daef 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1456,7 +1456,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             }
         }
         if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
-        (e.nativeEvent as any).formattedHandled = true;
+        this.props.isSelected(true) && ((e.nativeEvent as any).formattedHandled = true);
 
         if (this.props.isSelected(true)) { // if text box is selected, then it consumes all click events
             // e.stopPropagation();  // bcz: not sure why this was here.  We need to allow the DocumentView to get clicks to process doubleClicks
-- 
cgit v1.2.3-70-g09d2


From b9aed0f878936eda5cd6de717885ba83b1799d60 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 17 Feb 2021 22:37:16 -0500
Subject: added a placeholder for dynamically discovering Tours during lightbox
 navigation

---
 src/client/views/LightboxView.scss   |  2 +-
 src/client/views/LightboxView.tsx    | 50 +++++++++++++++++++++++++++---------
 src/client/views/nodes/FilterBox.tsx |  2 +-
 3 files changed, 40 insertions(+), 14 deletions(-)

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

diff --git a/src/client/views/LightboxView.scss b/src/client/views/LightboxView.scss
index 35f11b699..4ea2dc2d6 100644
--- a/src/client/views/LightboxView.scss
+++ b/src/client/views/LightboxView.scss
@@ -17,7 +17,7 @@
             border-radius: 8;
             color:white;
             opacity: 0.7;
-            width: 30;
+            width: 35;
             &:hover {
                 opacity: 1;
             }
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index a2088a8fc..7cfebcc3e 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -3,7 +3,7 @@ import { action, observable, computed } from 'mobx';
 import { observer } from 'mobx-react';
 import "normalize.css";
 import * as React from 'react';
-import { Doc, Opt, DocListCast } from '../../fields/Doc';
+import { Doc, Opt, DocListCast, DocListCastAsync } from '../../fields/Doc';
 import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../Utils';
 import { Transform } from '../util/Transform';
 import "./LightboxView.scss";
@@ -13,8 +13,9 @@ import { DocUtils } from '../documents/Documents';
 import { DocumentManager } from '../util/DocumentManager';
 import { SelectionManager } from '../util/SelectionManager';
 import { TabDocView } from './collections/TabDocView';
-import { Cast, NumCast } from '../../fields/Types';
-import { path } from 'animejs';
+import { Cast, NumCast, StrCast } from '../../fields/Types';
+import { LinkManager } from '../util/LinkManager';
+import { List } from '../../fields/List';
 
 interface LightboxViewProps {
     PanelWidth: number;
@@ -27,12 +28,15 @@ export class LightboxView extends React.Component<LightboxViewProps> {
     public static SavedState: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number>, transition: Opt<string> }>;
     @observable static LightboxDoc: Opt<Doc>;
     @observable static LightboxDocTarget: Opt<Doc>;
+    @observable static LightboxTourmap: Opt<Doc[]> = [];   // list of all tours available from the current target
+    @observable static LightboxDocFilters: string[] = [];
     public static LightboxHistory: Opt<Doc[]> = [];
     public static LightboxFuture: Opt<Doc[]> = [];
     public static LightboxDocView: Opt<DocumentView>;
     static path: { doc: Opt<Doc>, target: Opt<Doc>, history: Opt<Doc[]>, future: Opt<Doc[]>, saved: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number>, transition: Opt<string> }> }[] = [];
     @action public static SetLightboxDoc(doc: Opt<Doc>, future?: Doc[]) {
         if (!doc) {
+            LightboxView.LightboxDocFilters.length = 0;
             if (this.LightboxDoc) {
                 this.LightboxDoc._panX = this.SavedState?.panX;
                 this.LightboxDoc._panY = this.SavedState?.panY;
@@ -52,6 +56,10 @@ export class LightboxView extends React.Component<LightboxViewProps> {
             LightboxView.LightboxFuture = future.slice().sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)).sort((a, b) => DocListCast(a.links).length - DocListCast(b.links).length);
         }
         LightboxView.LightboxDoc = LightboxView.LightboxDocTarget = doc;
+        LightboxView.LightboxTourmap = DocListCast(doc?.links).map(link => {
+            const opp = LinkManager.getOppositeAnchor(link, doc!);
+            return opp?.TourMap ? opp : undefined;
+        }).filter(m => m).map(m => m!);
 
         return true;
     }
@@ -61,15 +69,16 @@ export class LightboxView extends React.Component<LightboxViewProps> {
     lightboxWidth = () => this.props.PanelWidth - this.leftBorder * 2;
     lightboxHeight = () => this.props.PanelHeight - this.topBorder * 2;
     lightboxScreenToLocal = () => new Transform(-this.leftBorder, -this.topBorder, 1);
-    navBtn = (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: string, display: () => string, click: (e: React.MouseEvent) => void) => {
+    navBtn = (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: string, display: () => string, click: (e: React.MouseEvent) => void, color?: string) => {
         return <div className="lightboxView-navBtn-frame" style={{
             display: display(),
             left,
             width: bottom !== undefined ? undefined : Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]),
             bottom
         }}>
-            <div className="lightboxView-navBtn" style={{ top }}
+            <div className="lightboxView-navBtn" title={color} style={{ top, color: color ? "red" : "white", background: color ? "white" : undefined }}
                 onClick={click}>
+                <div style={{ height: 10 }}>{color}</div>
                 <FontAwesomeIcon icon={icon as any} size="3x" />
             </div>
         </div>;
@@ -83,6 +92,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
             ]
                 .sort((a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)));
     }
+    docFilters = () => LightboxView.LightboxDocFilters || [];
     addDocTab = LightboxView.AddDocTab;
     @action
     stepForward = () => {
@@ -112,6 +122,10 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                 LightboxView.SetLightboxDoc(target);
             }
         }
+        LightboxView.LightboxTourmap = DocListCast(LightboxView.LightboxDocTarget?.links).map(link => {
+            const opp = LinkManager.getOppositeAnchor(link, LightboxView.LightboxDocTarget!);
+            return opp?.TourMap ? opp : undefined;
+        }).filter(m => m).map(m => m!);
     }
     @action
     stepBackward = () => {
@@ -124,6 +138,10 @@ export class LightboxView extends React.Component<LightboxViewProps> {
         } else {
             LightboxView.SetLightboxDoc(target);
         }
+        LightboxView.LightboxTourmap = DocListCast(LightboxView.LightboxDocTarget?.links).map(link => {
+            const opp = LinkManager.getOppositeAnchor(link, LightboxView.LightboxDocTarget!);
+            return opp?.TourMap ? opp : undefined;
+        }).filter(m => m).map(m => m!);
     }
     @action
     stepInto = () => {
@@ -134,11 +152,17 @@ export class LightboxView extends React.Component<LightboxViewProps> {
             history: LightboxView.LightboxHistory,
             saved: LightboxView.SavedState
         });
-        const coll = LightboxView.LightboxDocTarget;
-        if (coll) {
-            const fieldKey = Doc.LayoutFieldKey(coll);
-            LightboxView.SetLightboxDoc(coll, [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + "-annotations"])]);
-            TabDocView.PinDoc(coll, { hidePresBox: true });
+        const tours = LightboxView.LightboxTourmap;
+        if (tours && tours.length) {
+            const fieldKey = Doc.LayoutFieldKey(tours[0]);
+            LightboxView.LightboxFuture?.push(...DocListCast(tours[0][fieldKey]).reverse());
+        } else {
+            const coll = LightboxView.LightboxDocTarget;
+            if (coll) {
+                const fieldKey = Doc.LayoutFieldKey(coll);
+                LightboxView.SetLightboxDoc(coll, [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + "-annotations"])]);
+                TabDocView.PinDoc(coll, { hidePresBox: true });
+            }
         }
         setTimeout(() => this.stepForward());
     }
@@ -181,6 +205,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                         pinToPres={TabDocView.PinDoc}
                         rootSelected={returnTrue}
                         docViewPath={returnEmptyDoclist}
+                        docFilters={this.docFilters}
                         removeDocument={undefined}
                         styleProvider={DefaultStyleProvider}
                         layerProvider={returnTrue}
@@ -191,7 +216,6 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                         parentActive={returnTrue}
                         whenActiveChanged={emptyFunction}
                         bringToFront={emptyFunction}
-                        docFilters={returnEmptyFilter}
                         docRangeFilters={returnEmptyFilter}
                         searchFilterDocs={returnEmptyDoclist}
                         ContainingCollectionView={undefined}
@@ -212,7 +236,9 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                     () => LightboxView.LightboxDoc && LightboxView.LightboxFuture?.length ? "" : "none", e => {
                         e.stopPropagation();
                         this.stepInto();
-                    })}
+                    },
+                    StrCast(LightboxView.LightboxTourmap?.lastElement()?.TourMap)
+                )}
             </div>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index cc924ff7a..1ab3ede6e 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -71,7 +71,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
                         subDocs.forEach((t) => {
                             const facetVal = t[facetKey];
                             if (facetVal instanceof RichTextField) rtFields++;
-                            valueSet.add(Field.toString(facetVal as Field));
+                            facetVal && valueSet.add(Field.toString(facetVal as Field));
                             const fieldKey = Doc.LayoutFieldKey(t);
                             const annos = !Field.toString(Doc.LayoutField(t) as Field).includes("CollectionView");
                             DocListCast(t[annos ? fieldKey + "-annotations" : fieldKey]).forEach((newdoc) => newarray.push(newdoc));
-- 
cgit v1.2.3-70-g09d2


From 0d59f6bc23c755c4eab2503add28699f5a5b1992 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 18 Feb 2021 00:26:45 -0500
Subject: better version of forward/backward view management in lightBoxview.

---
 src/client/util/DocumentManager.ts                 |   3 +-
 src/client/views/DocumentDecorations.tsx           |   2 +-
 src/client/views/LightboxView.tsx                  | 149 ++++++++++++---------
 src/client/views/collections/CollectionMenu.tsx    |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   2 +-
 5 files changed, 88 insertions(+), 70 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index d028258b2..67e05f8d0 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -9,6 +9,7 @@ import { CollectionView } from '../views/collections/CollectionView';
 import { LightboxView } from '../views/LightboxView';
 import { DocumentView, ViewAdjustment } from '../views/nodes/DocumentView';
 import { Scripting } from './Scripting';
+import { CurrentUserUtils } from './CurrentUserUtils';
 
 export class DocumentManager {
 
@@ -102,7 +103,7 @@ export class DocumentManager {
     public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
         const docViews = DocumentManager.Instance.DocumentViews;
         const views: DocumentView[] = [];
-        docViews.map(view => LightboxView.IsLightboxDocView(view.docViewPath) && view.rootDoc === toFind && views.push(view));
+        docViews.map(view => LightboxView.IsLightboxDocView(view.docViewPath) && Doc.AreProtosEqual(view.rootDoc, toFind) && views.push(view));
         return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined);
     }
     public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 87ed142f8..c8a5b338a 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -175,7 +175,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
                 } else if (e.altKey) {    // open same document in new tab
                     CollectionDockingView.ToggleSplit(Cast(selectedDocs[0].props.Document._fullScreenView, Doc, null) || selectedDocs[0].props.Document, "right");
                 } else {
-                    LightboxView.SetLightboxDoc(selectedDocs[0].props.Document, selectedDocs.slice(1).map(view => view.props.Document));
+                    LightboxView.SetLightboxDoc(selectedDocs[0].props.Document, undefined, selectedDocs.slice(1).map(view => view.props.Document));
                 }
             }
         }
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 7cfebcc3e..af07ead97 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -1,21 +1,20 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable, computed } from 'mobx';
+import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import "normalize.css";
 import * as React from 'react';
-import { Doc, Opt, DocListCast, DocListCastAsync } from '../../fields/Doc';
-import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../Utils';
-import { Transform } from '../util/Transform';
-import "./LightboxView.scss";
-import { DocumentView } from './nodes/DocumentView';
-import { DefaultStyleProvider } from './StyleProvider';
+import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { Cast, NumCast, StrCast } from '../../fields/Types';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../Utils';
 import { DocUtils } from '../documents/Documents';
 import { DocumentManager } from '../util/DocumentManager';
+import { LinkManager } from '../util/LinkManager';
 import { SelectionManager } from '../util/SelectionManager';
+import { Transform } from '../util/Transform';
 import { TabDocView } from './collections/TabDocView';
-import { Cast, NumCast, StrCast } from '../../fields/Types';
-import { LinkManager } from '../util/LinkManager';
-import { List } from '../../fields/List';
+import "./LightboxView.scss";
+import { DocumentView } from './nodes/DocumentView';
+import { DefaultStyleProvider } from './StyleProvider';
 
 interface LightboxViewProps {
     PanelWidth: number;
@@ -25,27 +24,29 @@ interface LightboxViewProps {
 
 @observer
 export class LightboxView extends React.Component<LightboxViewProps> {
-    public static SavedState: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number>, transition: Opt<string> }>;
-    @observable static LightboxDoc: Opt<Doc>;
-    @observable static LightboxDocTarget: Opt<Doc>;
-    @observable static LightboxTourmap: Opt<Doc[]> = [];   // list of all tours available from the current target
-    @observable static LightboxDocFilters: string[] = [];
-    public static LightboxHistory: Opt<Doc[]> = [];
-    public static LightboxFuture: Opt<Doc[]> = [];
-    public static LightboxDocView: Opt<DocumentView>;
-    static path: { doc: Opt<Doc>, target: Opt<Doc>, history: Opt<Doc[]>, future: Opt<Doc[]>, saved: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number>, transition: Opt<string> }> }[] = [];
-    @action public static SetLightboxDoc(doc: Opt<Doc>, future?: Doc[]) {
+    @computed public static get LightboxDoc() { return this._doc; }
+    @observable private static _doc: Opt<Doc>;
+    @observable private static _docTarget: Opt<Doc>;
+    @observable private static _tourMap: Opt<Doc[]> = [];   // list of all tours available from the current target
+    @observable private static _docFilters: string[] = []; // filters
+    private static _savedState: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number>, transition: Opt<string> }>;
+    private static _history: Opt<{ doc: Doc, target?: Doc }[]> = [];
+    private static _future: Opt<Doc[]> = [];
+    private static _docView: Opt<DocumentView>;
+    static path: { doc: Opt<Doc>, target: Opt<Doc>, history: Opt<{ doc: Doc, target?: Doc }[]>, future: Opt<Doc[]>, saved: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number>, transition: Opt<string> }> }[] = [];
+    @action public static SetLightboxDoc(doc: Opt<Doc>, target?: Doc, future?: Doc[]) {
         if (!doc) {
-            LightboxView.LightboxDocFilters.length = 0;
+            this._docFilters && (this._docFilters.length = 0);
             if (this.LightboxDoc) {
-                this.LightboxDoc._panX = this.SavedState?.panX;
-                this.LightboxDoc._panY = this.SavedState?.panY;
-                this.LightboxDoc._viewScale = this.SavedState?.scale;
-                this.LightboxDoc._viewTransition = this.SavedState?.transition;
+                this.LightboxDoc._panX = this._savedState?.panX;
+                this.LightboxDoc._panY = this._savedState?.panY;
+                this.LightboxDoc._viewScale = this._savedState?.scale;
+                this.LightboxDoc._viewTransition = this._savedState?.transition;
             }
-            LightboxView.LightboxFuture = LightboxView.LightboxHistory = [];
+            this._future = this._history = [];
         } else {
-            LightboxView.SavedState = {
+            this._history ? this._history.push({ doc, target }) : this._history = [{ doc, target }];
+            this._savedState = {
                 panX: Cast(doc._panX, "number", null),
                 panY: Cast(doc._panY, "number", null),
                 scale: Cast(doc._viewScale, "number", null),
@@ -53,17 +54,18 @@ export class LightboxView extends React.Component<LightboxViewProps> {
             };
         }
         if (future) {
-            LightboxView.LightboxFuture = future.slice().sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)).sort((a, b) => DocListCast(a.links).length - DocListCast(b.links).length);
+            this._future = future.slice().sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)).sort((a, b) => DocListCast(a.links).length - DocListCast(b.links).length);
         }
-        LightboxView.LightboxDoc = LightboxView.LightboxDocTarget = doc;
-        LightboxView.LightboxTourmap = DocListCast(doc?.links).map(link => {
+        this._doc = doc;
+        this._docTarget = target || doc;
+        this._tourMap = DocListCast(doc?.links).map(link => {
             const opp = LinkManager.getOppositeAnchor(link, doc!);
             return opp?.TourMap ? opp : undefined;
         }).filter(m => m).map(m => m!);
 
         return true;
     }
-    public static IsLightboxDocView(path: DocumentView[]) { return path.includes(LightboxView.LightboxDocView!); }
+    public static IsLightboxDocView(path: DocumentView[]) { return path.includes(this._docView!); }
     @computed get leftBorder() { return Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]); }
     @computed get topBorder() { return Math.min(this.props.PanelHeight / 4, this.props.maxBorder[1]); }
     lightboxWidth = () => this.props.PanelWidth - this.leftBorder * 2;
@@ -83,27 +85,36 @@ export class LightboxView extends React.Component<LightboxViewProps> {
             </div>
         </div>;
     }
+    public static GetSavedState(doc: Doc) {
+        return this.LightboxDoc === doc && this._savedState ? this._savedState : undefined;
+    }
+    public static SetDocFilter(filter: string) {
+        if (this.LightboxDoc && filter) {
+            this._docFilters = [`cookies:${filter}:match`];
+        }
+    }
     public static AddDocTab = (doc: Doc, location: string) => {
         SelectionManager.DeselectAll();
-        return LightboxView.SetLightboxDoc(doc,
+        return LightboxView.SetLightboxDoc(doc, undefined,
             [...DocListCast(doc[Doc.LayoutFieldKey(doc)]),
             ...DocListCast(doc[Doc.LayoutFieldKey(doc) + "-annotations"]),
-            ...(LightboxView.LightboxFuture ?? [])
+            ...(LightboxView._future ?? [])
             ]
                 .sort((a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)));
     }
-    docFilters = () => LightboxView.LightboxDocFilters || [];
+    docFilters = () => LightboxView._docFilters || [];
     addDocTab = LightboxView.AddDocTab;
     @action
     stepForward = () => {
-        const target = LightboxView.LightboxDocTarget = LightboxView.LightboxFuture?.pop();
+        const doc = LightboxView._doc!;
+        const target = LightboxView._docTarget = LightboxView._future?.pop();
         const docView = target && DocumentManager.Instance.getLightboxDocumentView(target);
         if (docView && target) {
             docView.focus(target, { willZoom: true, scale: 0.9 });
-            if (LightboxView.LightboxHistory?.lastElement() !== target) LightboxView.LightboxHistory?.push(target);
+            if (LightboxView._history?.lastElement().target !== target) LightboxView._history?.push({ doc, target: LightboxView._docTarget });
         } else {
             if (!target && LightboxView.path.length) {
-                const saved = LightboxView.SavedState;
+                const saved = LightboxView._savedState;
                 if (LightboxView.LightboxDoc) {
                     LightboxView.LightboxDoc._panX = saved?.panX;
                     LightboxView.LightboxDoc._panY = saved?.panY;
@@ -112,34 +123,41 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                 }
                 const pop = LightboxView.path.pop();
                 if (pop) {
-                    LightboxView.LightboxDoc = pop.doc;
-                    LightboxView.LightboxDocTarget = pop.target;
-                    LightboxView.LightboxFuture = pop.future;
-                    LightboxView.LightboxHistory = pop.history;
-                    LightboxView.SavedState = pop.saved;
+                    LightboxView._doc = pop.doc;
+                    LightboxView._docTarget = pop.target;
+                    LightboxView._future = pop.future;
+                    LightboxView._history = pop.history;
+                    LightboxView._savedState = pop.saved;
                 }
             } else {
                 LightboxView.SetLightboxDoc(target);
             }
         }
-        LightboxView.LightboxTourmap = DocListCast(LightboxView.LightboxDocTarget?.links).map(link => {
-            const opp = LinkManager.getOppositeAnchor(link, LightboxView.LightboxDocTarget!);
+        LightboxView._tourMap = DocListCast(LightboxView._docTarget?.links).map(link => {
+            const opp = LinkManager.getOppositeAnchor(link, LightboxView._docTarget!);
             return opp?.TourMap ? opp : undefined;
         }).filter(m => m).map(m => m!);
     }
     @action
     stepBackward = () => {
-        const previous = LightboxView.LightboxHistory?.pop();
-        const target = LightboxView.LightboxDocTarget = LightboxView.LightboxHistory?.lastElement();
+        const previous = LightboxView._history?.pop();
+        if (!previous || !LightboxView._history?.length) {
+            LightboxView.SetLightboxDoc(undefined);
+            return;
+        }
+        const { doc, target } = LightboxView._history?.lastElement()!;
         const docView = target && DocumentManager.Instance.getLightboxDocumentView(target);
         if (docView && target) {
-            if (LightboxView.LightboxFuture?.lastElement() !== previous) LightboxView.LightboxFuture?.push(previous!);
+            LightboxView._doc = doc;
+            LightboxView._docTarget = target || doc;
+            if (LightboxView._future?.lastElement() !== previous.target || previous.doc) LightboxView._future?.push(previous.target || previous.doc);
             docView.focus(target, { willZoom: true, scale: 0.9 });
         } else {
-            LightboxView.SetLightboxDoc(target);
+            LightboxView._doc = doc;
+            LightboxView._docTarget = target || doc;
         }
-        LightboxView.LightboxTourmap = DocListCast(LightboxView.LightboxDocTarget?.links).map(link => {
-            const opp = LinkManager.getOppositeAnchor(link, LightboxView.LightboxDocTarget!);
+        LightboxView._tourMap = DocListCast(LightboxView._docTarget?.links).map(link => {
+            const opp = LinkManager.getOppositeAnchor(link, LightboxView._docTarget!);
             return opp?.TourMap ? opp : undefined;
         }).filter(m => m).map(m => m!);
     }
@@ -147,29 +165,28 @@ export class LightboxView extends React.Component<LightboxViewProps> {
     stepInto = () => {
         LightboxView.path.push({
             doc: LightboxView.LightboxDoc,
-            target: LightboxView.LightboxDocTarget,
-            future: LightboxView.LightboxFuture,
-            history: LightboxView.LightboxHistory,
-            saved: LightboxView.SavedState
+            target: LightboxView._docTarget,
+            future: LightboxView._future,
+            history: LightboxView._history,
+            saved: LightboxView._savedState
         });
-        const tours = LightboxView.LightboxTourmap;
+        const tours = LightboxView._tourMap;
         if (tours && tours.length) {
             const fieldKey = Doc.LayoutFieldKey(tours[0]);
-            LightboxView.LightboxFuture?.push(...DocListCast(tours[0][fieldKey]).reverse());
+            LightboxView._future?.push(...DocListCast(tours[0][fieldKey]).reverse());
         } else {
-            const coll = LightboxView.LightboxDocTarget;
+            const coll = LightboxView._docTarget;
             if (coll) {
                 const fieldKey = Doc.LayoutFieldKey(coll);
-                LightboxView.SetLightboxDoc(coll, [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + "-annotations"])]);
+                LightboxView.SetLightboxDoc(coll, undefined, [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + "-annotations"])]);
                 TabDocView.PinDoc(coll, { hidePresBox: true });
             }
         }
         setTimeout(() => this.stepForward());
     }
 
-    fitToBox = () => LightboxView.LightboxDocTarget === LightboxView.LightboxDoc;
+    fitToBox = () => LightboxView._docTarget === LightboxView.LightboxDoc;
     render() {
-        if (LightboxView.LightboxHistory?.lastElement() !== LightboxView.LightboxDoc) LightboxView.LightboxHistory?.push(LightboxView.LightboxDoc!);
         let downx = 0, downy = 0;
         return !LightboxView.LightboxDoc ? (null) :
             <div className="lightboxView-frame"
@@ -186,7 +203,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                     height: this.lightboxHeight()
                 }}>
                     <DocumentView ref={action((r: DocumentView | null) => {
-                        LightboxView.LightboxDocView = r !== null ? r : undefined;
+                        LightboxView._docView = r !== null ? r : undefined;
                         setTimeout(action(() => {
                             const vals = r?.ComponentView?.freeformData?.();
                             if (vals && r) {
@@ -194,7 +211,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                                 r.layoutDoc._panY = vals.panY;
                                 r.layoutDoc._viewScale = vals.scale;
                             }
-                            LightboxView.LightboxDocTarget = undefined;
+                            r && (LightboxView._docTarget = undefined);
                         }));
                     })}
                         Document={LightboxView.LightboxDoc}
@@ -223,21 +240,21 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                         renderDepth={0} />
                 </div>
                 {this.navBtn(0, undefined, this.props.PanelHeight / 2 - 12.50, "chevron-left",
-                    () => LightboxView.LightboxDoc && LightboxView.LightboxHistory?.length ? "" : "none", e => {
+                    () => LightboxView.LightboxDoc && LightboxView._history?.length ? "" : "none", e => {
                         e.stopPropagation();
                         this.stepBackward();
                     })}
                 {this.navBtn(this.props.PanelWidth - Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]), undefined, this.props.PanelHeight / 2 - 12.50, "chevron-right",
-                    () => LightboxView.LightboxDoc && LightboxView.LightboxFuture?.length ? "" : "none", e => {
+                    () => LightboxView.LightboxDoc && LightboxView._future?.length ? "" : "none", e => {
                         e.stopPropagation();
                         this.stepForward();
                     })}
                 {this.navBtn("50%", 0, 0, "chevron-down",
-                    () => LightboxView.LightboxDoc && LightboxView.LightboxFuture?.length ? "" : "none", e => {
+                    () => LightboxView.LightboxDoc && LightboxView._future?.length ? "" : "none", e => {
                         e.stopPropagation();
                         this.stepInto();
                     },
-                    StrCast(LightboxView.LightboxTourmap?.lastElement()?.TourMap)
+                    StrCast(LightboxView._tourMap?.lastElement()?.TourMap)
                 )}
             </div>;
     }
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 5fa988f06..423c94005 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -492,7 +492,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
             <button className="antimodeMenu-button" onPointerDown={() => {
                 const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]);
                 if (docs.length) {
-                    LightboxView.SetLightboxDoc(targetDoc, docs);
+                    LightboxView.SetLightboxDoc(targetDoc, undefined, docs);
                 }
             }}>
                 <FontAwesomeIcon className="documentdecorations-icon" icon="desktop" size="lg" />
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3ca2258ee..58288a7b1 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -916,7 +916,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 HistoryUtil.pushState(state);
             }
         }
-        const LightboxState = LightboxView.LightboxDoc === this.props.Document && LightboxView.SavedState ? LightboxView.SavedState : undefined;
+        const LightboxState = LightboxView.GetSavedState(this.props.Document);
         SelectionManager.DeselectAll();
         if (this.props.Document.scrollHeight) {
             this.props.focus(doc, options);
-- 
cgit v1.2.3-70-g09d2

