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/collections/CollectionSchemaView.tsx | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 66064e354..d2ed5427b 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -25,6 +25,7 @@ import { DefaultStyleProvider } from "../StyleProvider";
 import "./CollectionSchemaView.scss";
 import { CollectionSubView } from "./CollectionSubView";
 import { SchemaTable } from "./SchemaTable";
+import { DocUtils } from "../../documents/Documents";
 // bcz: need to add drag and drop of rows and columns.  This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
 
 export enum ColumnType {
@@ -404,7 +405,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
                     fitContentsToDoc={true}
                     freezeDimensions={true}
                     dontCenter={"y"}
-                    focus={emptyFunction}
+                    focus={DocUtils.DefaultFocus}
                     renderDepth={this.props.renderDepth}
                     rootSelected={this.rootSelected}
                     PanelWidth={this.previewWidth}
-- 
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/collections/CollectionSchemaView.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 3dae4d3d8ac10f6f3c9f937282dbfa5882584aad Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 16 Feb 2021 14:39:41 -0500
Subject: added getAnchor() to freeformView to capture viewtype & filters. 
 fixed setPanIntoView bug.  added setViewSpec componentView method to allow
 anchor to set view specs.

---
 .../views/collections/CollectionSchemaCells.tsx    |  2 +-
 .../views/collections/CollectionSchemaView.tsx     |  4 +-
 .../views/collections/CollectionTimeView.tsx       | 65 ++++++++++++----------
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 38 ++++++++++++-
 src/client/views/nodes/DocumentView.tsx            | 17 ++++--
 src/client/views/nodes/LinkDocPreview.tsx          |  6 +-
 6 files changed, 91 insertions(+), 41 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 904b4c761..2e6186680 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -216,7 +216,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
         const placeholder = type === "number" ? "0" : contents === "" ? "--" : "undefined";
         return (
             <div className="collectionSchemaView-cellContainer" style={{ cursor: field instanceof Doc ? "grab" : "auto" }}
-                ref={dragRef} onPointerDown={this.onPointerDown} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}>
+                ref={dragRef} onPointerDown={this.onPointerDown} onClick={action(e => this._isEditing = true)} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}>
                 <div className={className} ref={this._focusRef} tabIndex={-1}>
                     <div className="collectionSchemaView-cellContents" ref={type === undefined || type === "document" ? this.dropRef : null}>
                         {!this.props.Document._searchDoc ?
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 528cdc8b7..504cf32a0 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -103,7 +103,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     @action setHeaderIsEditing = (isEditing: boolean) => this._headerIsEditing = isEditing;
 
     @undoBatch
-    setColumnType = (columnField: SchemaHeaderField, type: ColumnType): void => {
+    setColumnType = action((columnField: SchemaHeaderField, type: ColumnType): void => {
         this._openTypes = false;
         if (columnTypes.get(columnField.heading)) return;
 
@@ -114,7 +114,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
             columns[index] = columnField;
             this.columns = columns;
         }
-    }
+    });
 
     @undoBatch
     setColumnColor = (columnField: SchemaHeaderField, color: string): void => {
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 8c3e53dfd..426eced1d 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,16 +1,21 @@
+import { toUpper } from "lodash";
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, Opt, DocCastAsync, StrListCast } from "../../../fields/Doc";
+import { Doc, DocCastAsync, Opt, StrListCast } from "../../../fields/Doc";
 import { List } from "../../../fields/List";
 import { ObjectField } from "../../../fields/ObjectField";
 import { RichTextField } from "../../../fields/RichTextField";
+import { listSpec } from "../../../fields/Schema";
 import { ComputedField, ScriptField } from "../../../fields/ScriptField";
-import { NumCast, StrCast, BoolCast, Cast } from "../../../fields/Types";
-import { emptyFunction, returnFalse, setupMoveUpEvents, returnTrue } from "../../../Utils";
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
+import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from "../../../Utils";
+import { Docs, DocUtils } from "../../documents/Documents";
+import { DocumentManager } from "../../util/DocumentManager";
 import { Scripting } from "../../util/Scripting";
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
 import { EditableView } from "../EditableView";
+import { ViewSpecPrefix } from "../nodes/DocumentView";
 import { ViewDefBounds } from "./collectionFreeForm/CollectionFreeFormLayoutEngines";
 import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
 import { CollectionSubView } from "./CollectionSubView";
@@ -19,12 +24,6 @@ const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 import React = require("react");
-import { DocUtils, Docs } from "../../documents/Documents";
-import { toUpper } from "lodash";
-import { DocumentManager } from "../../util/DocumentManager";
-import { listSpec } from "../../../fields/Schema";
-import { LinkManager } from "../../util/LinkManager";
-import { LinkDocPreview } from "../nodes/LinkDocPreview";
 
 @observer
 export class CollectionTimeView extends CollectionSubView(doc => doc) {
@@ -33,6 +32,9 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
     @observable _collapsed: boolean = false;
     @observable _childClickedScript: Opt<ScriptField>;
     @observable _viewDefDivClick: Opt<ScriptField>;
+    @observable _focusDocFilters: Opt<string[]>; // fields that get overridden by a focus anchor
+    @observable _focusPivotField: Opt<string>;
+    @observable _focusRangeFilters: Opt<string[]>;
 
     getAnchor = () => {
         const anchor = Docs.Create.TextanchorDocument({
@@ -41,9 +43,15 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
             hideLinkButton: true,
             annotationOn: this.rootDoc
         });
-        Doc.GetProto(anchor).pivotField = this.pivotField;
-        Doc.GetProto(anchor).docFilters = ObjectField.MakeCopy(this.layoutDoc._docFilters as ObjectField);
-        Doc.GetProto(anchor)._docRangeFilters = ObjectField.MakeCopy(this.layoutDoc._docRangeFilters as ObjectField);
+
+        // save view spec information for anchor
+        const proto = Doc.GetProto(anchor);
+        proto.pivotField = this.pivotField;
+        proto.docFilters = ObjectField.MakeCopy(this.layoutDoc._docFilters as ObjectField) || new List<string>([]);
+        proto.docRangeFilters = ObjectField.MakeCopy(this.layoutDoc._docRangeFilters as ObjectField) || new List<string>([]);
+        proto[ViewSpecPrefix + "_viewType"] = this.layoutDoc._viewType;
+
+        // store anchor in annotations list of document (not technically needed since these anchors are never drawn)
         if (Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), null) !== undefined) {
             Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), []).push(anchor);
         } else {
@@ -63,26 +71,24 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         });
     }
 
-    @observable _scrollFilters: Opt<string[]>;
-    @observable _scrollPivotField: Opt<string>;
-    timeDocFilters = () => this._scrollFilters || ([] as string[]);
-    get pivotField() { return this._scrollPivotField || StrCast(this.layoutDoc._pivotField); }
+    get pivotField() { return this._focusPivotField || StrCast(this.layoutDoc._pivotField); }
     @action
-    scrollFocus = (anchor: Doc, smooth: boolean) => {
-        if (!LinkDocPreview.LinkInfo) {
-            if (anchor.pivotField !== undefined) {
-                this.layoutDoc._prevFilterIndex = 1;
-                this.layoutDoc._pivotField = StrCast(anchor.pivotField);
-                this.layoutDoc._docFilters = ObjectField.MakeCopy(Doc.GetProto(anchor).docFilters as ObjectField);
-                this.layoutDoc._docRangeFilters = ObjectField.MakeCopy(Doc.GetProto(anchor).docRangeFilters as ObjectField);
-            }
-        } else {
-            this._scrollPivotField = StrCast(anchor.pivotField);
-            this._scrollFilters = StrListCast(Doc.GetProto(anchor).docFilters);
+    setViewSpec = (anchor: Doc, preview: boolean) => {
+        if (preview) {   // if in preview, then override document's fields with view spec
+            this._focusPivotField = StrCast(anchor.pivotField);
+            this._focusDocFilters = StrListCast(anchor.docFilters);
+            this._focusRangeFilters = StrListCast(anchor.docRangeFilters);
+        } else if (anchor.pivotField !== undefined) {  // otherwise set document's fields based on anchor view spec
+            this.layoutDoc._prevFilterIndex = 1;
+            this.layoutDoc._pivotField = StrCast(anchor.pivotField);
+            this.layoutDoc._docFilters = new List<string>(this.pivotDocFilters());
+            this.layoutDoc._docRangeFilters = new List<string>(this.pivotDocRangeFilters());
         }
         return 0;
     }
 
+    pivotDocFilters = () => this._focusDocFilters || this.props.docFilters();
+    pivotDocRangeFilters = () => this._focusRangeFilters || this.props.docRangeFilters();
     layoutEngine = () => this._layoutEngine;
     toggleVisibility = action(() => this._collapsed = !this._collapsed);
 
@@ -136,9 +142,10 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         return <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this.props.active() ? undefined : "none" }}
             onClick={this.contentsDown}>
             <CollectionFreeFormView {...this.props}
-                engineProps={{ pivotField: this.pivotField, docFilters: this.docFilters }}
+                engineProps={{ pivotField: this.pivotField, docFilters: this.docFilters, docRangeFilters: this.docRangeFilters }}
                 fitContentsToDoc={returnTrue}
-                docFilters={this.timeDocFilters}
+                docFilters={this.pivotDocFilters}
+                docRangeFilters={this.pivotDocRangeFilters}
                 childClickScript={this._childClickedScript}
                 viewDefDivClick={this._viewDefDivClick}
                 childFreezeDimensions={true}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b2ee6bbd0..c54596a9e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -6,6 +6,7 @@ import { collectionSchema, documentSchema } from "../../../../fields/documentSch
 import { Id } from "../../../../fields/FieldSymbols";
 import { InkData, InkField, InkTool } from "../../../../fields/InkField";
 import { List } from "../../../../fields/List";
+import { ObjectField } from "../../../../fields/ObjectField";
 import { RichTextField } from "../../../../fields/RichTextField";
 import { createSchema, listSpec, makeInterface } from "../../../../fields/Schema";
 import { ScriptField } from "../../../../fields/ScriptField";
@@ -35,7 +36,7 @@ import { DocumentDecorations } from "../../DocumentDecorations";
 import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth } from "../../InkingStroke";
 import { LightboxView } from "../../LightboxView";
 import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
-import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from "../../nodes/DocumentView";
+import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from "../../nodes/DocumentView";
 import { FieldViewProps } from "../../nodes/FieldView";
 import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
 import { pageSchema } from "../../nodes/ImageBox";
@@ -114,6 +115,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @observable _clusterSets: (Doc[])[] = [];
     @observable _timelineRef = React.createRef<Timeline>();
     @observable _marqueeRef = React.createRef<HTMLDivElement>();
+    @observable _focusFilters: Opt<string[]>; // fields that get overriden by focus anchor
 
     @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && (this.props.ContainingCollectionView?.active() || this.props.active()); }
     @computed get fitToContentVals() {
@@ -985,7 +987,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 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 if ((screen.right - screen.left) < (bounds.right - bounds.left)) {
+        } else if ((screen.right - screen.left) < (bounds.right - bounds.left) ||
+            (screen.bot - screen.top) < (bounds.bot - bounds.top)) {
             return {
                 panX: (bounds.left + bounds.right) / 2,
                 panY: (bounds.top + bounds.bot) / 2,
@@ -1023,7 +1026,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
             PanelWidth: childLayout[WidthSym],
             PanelHeight: childLayout[HeightSym],
-            docFilters: this.docFilters,
+            docFilters: this.freeformDocFilters,
             docRangeFilters: this.docRangeFilters,
             searchFilterDocs: this.searchFilterDocs,
             focus: this.focusDocument,
@@ -1202,6 +1205,35 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         this.Document._useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true);
         return elements;
     }
+
+    freeformDocFilters = () => this._focusFilters || this.docFilters();
+    @action
+    setViewSpec = (anchor: Doc, preview: boolean) => {
+        if (preview) {
+            this._focusFilters = StrListCast(Doc.GetProto(anchor).docFilters);
+        } else if (anchor.pivotField !== undefined) {
+            this.layoutDoc._docFilters = ObjectField.MakeCopy(Doc.GetProto(anchor).docFilters as ObjectField);
+        }
+        return 0;
+    }
+
+    getAnchor = () => {
+        const anchor = Docs.Create.TextanchorDocument({
+            title: StrCast(this.layoutDoc._viewType),
+            useLinkSmallAnchor: true,
+            hideLinkButton: true,
+            annotationOn: this.rootDoc
+        });
+        const proto = Doc.GetProto(anchor);
+        proto[ViewSpecPrefix + "_viewType"] = this.layoutDoc._viewType;
+        proto[ViewSpecPrefix + "_docFilters"] = ObjectField.MakeCopy(this.layoutDoc.docFilters as ObjectField) || new List<string>([]);
+        if (Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), null) !== undefined) {
+            Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), []).push(anchor);
+        } else {
+            this.dataDoc[this.props.fieldKey + "-annotations"] = new List<Doc>([anchor]);
+        }
+        return anchor;
+    }
     freeformData = (force?: boolean) => this.fitToContent || force ? this.fitToContentVals : undefined;
     @action
     componentDidMount() {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4aa424af3..e52791901 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -45,6 +45,7 @@ import { LinkDocPreview } from "./LinkDocPreview";
 import { PresBox } from './PresBox';
 import { RadialMenu } from './RadialMenu';
 import React = require("react");
+import { ObjectField } from "../../../fields/ObjectField";
 const { Howl } = require('howler');
 
 interface Window {
@@ -56,12 +57,13 @@ declare class MediaRecorder {
     constructor(e: any);
 }
 
-
 export enum ViewAdjustment {
     resetView = 1,
     doNothing = 0
-
 }
+
+export const ViewSpecPrefix = "_VIEW";  // field prefix for anchor fields that are immediately copied over to the target document when link is followed.  Other anchor properties will be copied over in the specific setViewSpec() method on their view (which allows for seting preview values instead of writing to the document)
+
 export interface DocFocusOptions {
     originalTarget?: Doc; // set in JumpToDocument, used by TabDocView to determine whether to fit contents to tab
     willZoom?: boolean;   // determines whether to zoom in on target document
@@ -75,6 +77,7 @@ export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps | F
 export interface DocComponentView {
     getAnchor?: () => Doc;
     scrollFocus?: (doc: Doc, smooth: boolean) => Opt<number>; // returns the duration of the focus
+    setViewSpec?: (anchor: Doc, preview: boolean) => void;
     back?: () => boolean;
     forward?: () => boolean;
     url?: () => string;
@@ -405,10 +408,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
     }
 
-    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      
+    focus = (anchor: Doc, options?: DocFocusOptions) => {
+        // copying over _VIEW fields immediately allows the view type to switch to create the right _componentView
+        Array.from(Object.keys(Doc.GetProto(anchor))).filter(key => key.startsWith(ViewSpecPrefix)).forEach(spec => this.layoutDoc[spec.replace(ViewSpecPrefix, "")] = ((field) => field instanceof ObjectField ? ObjectField.MakeCopy(field) : field)(anchor[spec]));
+        // after  a timeout, the right _componentView should have been created, so call it to update its view spec values
+        setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false));
+        const focusSpeed = this._componentView?.scrollFocus?.(anchor, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here      
         const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : ViewAdjustment.doNothing;
-        this.props.focus(options?.docTransform ? doc : this.rootDoc, {
+        this.props.focus(options?.docTransform ? anchor : this.rootDoc, {
             ...options, afterFocus: (didFocus: boolean) =>
                 new Promise<ViewAdjustment>(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : ViewAdjustment.doNothing), focusSpeed ?? 0))
         });
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 7bd5d14d2..716810536 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -45,7 +45,11 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
         if (anchor1 && anchor2) {
             linkTarget = Doc.AreProtosEqual(anchor1, this._linkSrc) || Doc.AreProtosEqual(anchor1?.annotationOn as Doc, this._linkSrc) ? anchor2 : anchor1;
         }
-        linkTarget && DocCastAsync(linkTarget.annotationOn).then(action(anno => this._targetDoc = anno));
+        if (linkTarget?.annotationOn) {
+            linkTarget && DocCastAsync(linkTarget.annotationOn).then(action(anno => this._targetDoc = anno));
+        } else {
+            this._targetDoc = linkTarget;
+        }
         this._toolTipText = "";
     }
     componentDidUpdate(props: any) {
-- 
cgit v1.2.3-70-g09d2

