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/views/collections/CollectionTimeView.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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

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}
-- 
cgit v1.2.3-70-g09d2


From 05a9a3eed9a1afb55ec0ca55845b33cc745289e0 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 16 Feb 2021 00:59:28 -0500
Subject: updated pivot views to have a getAnchor() method to save the
 pivotField when linking.  added an engineProp to layoutEngiens so that you
 can preview a change in a LinkDocPreview without modifying the document.

---
 src/client/util/DocumentManager.ts                 |  16 +--
 src/client/views/StyleProvider.tsx                 |   3 +-
 .../views/collections/CollectionDockingView.tsx    |   6 +-
 .../views/collections/CollectionStackingView.tsx   |   2 -
 .../views/collections/CollectionTimeView.scss      |   2 +-
 .../views/collections/CollectionTimeView.tsx       | 115 ++++++++++++++++-----
 .../CollectionFreeFormLayoutEngines.tsx            |  14 ++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  25 +++--
 .../views/nodes/formattedText/FormattedTextBox.tsx |   8 +-
 .../formattedText/FormattedTextBoxComment.tsx      |   2 +-
 .../views/nodes/formattedText/RichTextRules.ts     |   3 +-
 11 files changed, 128 insertions(+), 68 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 6013a2ef3..d028258b2 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -144,10 +144,6 @@ export class DocumentManager {
         originalTarget = originalTarget ?? targetDoc;
         const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView;
         const docView = getFirstDocView(targetDoc, originatingDoc);
-        const highlight = () => {
-            const finalDocView = getFirstDocView(targetDoc);
-            finalDocView && Doc.linkFollowHighlight(finalDocView.rootDoc);
-        };
         const focusAndFinish = (didFocus: boolean) => {
             if (originatingDoc?.isPushpin) {
                 if (!didFocus || targetDoc.hidden) {
@@ -157,7 +153,6 @@ export class DocumentManager {
                 targetDoc.hidden && (targetDoc.hidden = undefined);
                 docView?.select(false);
             }
-            highlight();
             finished?.();
             return false;
         };
@@ -167,11 +162,10 @@ export class DocumentManager {
         const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext : undefined;
         const targetDocContext = contextDoc || annotatedDoc;
         const targetDocContextView = targetDocContext && getFirstDocView(targetDocContext);
-        if (!docView && targetDoc.type === DocumentType.TEXTANCHOR && rtfView) {
-            rtfView.focus(targetDoc);
-        }
-        else if (docView) {
-            docView.props.focus(targetDoc, {
+        const focusView = !docView && targetDoc.type === DocumentType.TEXTANCHOR && rtfView ? rtfView : docView;
+        if (focusView) {
+            focusView && Doc.linkFollowHighlight(focusView.rootDoc);
+            focusView.focus(targetDoc, {
                 originalTarget, willZoom, afterFocus: (didFocus: boolean) =>
                     new Promise<ViewAdjustment>(res => {
                         focusAndFinish(didFocus);
@@ -181,7 +175,6 @@ export class DocumentManager {
         } else {
             if (!targetDocContext) { // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default
                 createViewFunc(Doc.BrushDoc(targetDoc), finished); // bcz: should we use this?: Doc.MakeAlias(targetDoc)));
-                highlight();
             } 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";
@@ -202,7 +195,6 @@ export class DocumentManager {
                                             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.
                                 if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.rootDoc);
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 0ab8c5dfb..73a82d7ab 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -76,8 +76,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps |
         case StyleProp.HideLinkButton: return isAnchor || props?.dontRegisterView || (!selected && (doc?.isLinkButton || doc?.hideLinkButton));
         case StyleProp.ShowTitle: return doc && !doc.presentationTargetDoc && StrCast(doc._showTitle,
             !Doc.IsSystem(doc) && doc.type === DocumentType.RTF ?
-                (doc.author === Doc.CurrentUserEmail ? StrCast(Doc.UserDoc().showTitle) : "author;creationDate") :
-                undefined);
+                (doc.author === Doc.CurrentUserEmail ? StrCast(Doc.UserDoc().showTitle) : "author;creationDate") : "") || "";
         case StyleProp.Color:
             if (isCaption) return "white";
             const backColor = backgroundCol() || "black";
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 47c5746d4..7130ec29e 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -21,9 +21,9 @@ import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView";
 import { CollectionViewType } from './CollectionView';
 import { TabDocView } from './TabDocView';
 import React = require("react");
-import { stat } from 'fs';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { listSpec } from '../../../fields/Schema';
+import { LightboxView } from '../LightboxView';
 const _global = (window /* browser */ || global /* node */) as any;
 
 @observer
@@ -435,6 +435,8 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
     }
 }
 
+Scripting.addGlobal(function openInLightbox(doc: any) { LightboxView.AddDocTab(doc, "lightbox"); },
+    "opens up document in a lightbox", "(doc: any)");
 Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddSplit(doc, "right"); },
-    "opens up the inputted document on the right side of the screen", "(doc: any)");
+    "opens up document in tab on right side of the screen", "(doc: any)");
 Scripting.addGlobal(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.ReplaceTab(doc, "right", undefined, shiftKey); });
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 0ac1cdb28..77046f5ea 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -170,7 +170,6 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
 
     focusDocument = (doc: Doc, options?: DocFocusOptions) => {
         Doc.BrushDoc(doc);
-        Doc.linkFollowHighlight(doc);
 
         let focusSpeed = 0;
         const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName("documentView-node")).find((node: any) => node.id === doc[Id]);
@@ -186,7 +185,6 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
             willZoom: options?.willZoom, scale: options?.scale, afterFocus: (didFocus: boolean) =>
                 new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed))
         });
-
     }
 
     styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => {
diff --git a/src/client/views/collections/CollectionTimeView.scss b/src/client/views/collections/CollectionTimeView.scss
index fa7c87f4e..d995cbcd2 100644
--- a/src/client/views/collections/CollectionTimeView.scss
+++ b/src/client/views/collections/CollectionTimeView.scss
@@ -75,7 +75,7 @@
 
     .collectionTimeView-innards {
         display: inline-block;
-        width: calc(100% - 200px);
+        width: 100%;
         height: 100%;
     }
 }
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 869e01fea..8c3e53dfd 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,6 +1,6 @@
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, Opt, DocCastAsync } from "../../../fields/Doc";
+import { Doc, Opt, DocCastAsync, StrListCast } from "../../../fields/Doc";
 import { List } from "../../../fields/List";
 import { ObjectField } from "../../../fields/ObjectField";
 import { RichTextField } from "../../../fields/RichTextField";
@@ -19,7 +19,12 @@ const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 import React = require("react");
-import { DocUtils } from "../../documents/Documents";
+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) {
@@ -28,15 +33,56 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
     @observable _collapsed: boolean = false;
     @observable _childClickedScript: Opt<ScriptField>;
     @observable _viewDefDivClick: Opt<ScriptField>;
+
+    getAnchor = () => {
+        const anchor = Docs.Create.TextanchorDocument({
+            title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any,
+            useLinkSmallAnchor: true,
+            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);
+        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;
+    }
+
     async componentDidMount() {
-        const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || DocUtils.findTemplate("detailView", StrCast(this.props.Document.type), "");
-        const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); ";
+        this.props.setContentView?.(this);
+        const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || DocUtils.findTemplate("detailView", StrCast(this.rootDoc.type), "");
+        ///const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); ";
+        const childText = "openInLightbox(self, shiftKey); ";
         runInAction(() => {
             this._childClickedScript = ScriptField.MakeScript(childText, { this: Doc.name, shiftKey: "boolean" }, { detailView: detailView! });
             this._viewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" });
         });
     }
 
+    @observable _scrollFilters: Opt<string[]>;
+    @observable _scrollPivotField: Opt<string>;
+    timeDocFilters = () => this._scrollFilters || ([] as string[]);
+    get pivotField() { return this._scrollPivotField || 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);
+        }
+        return 0;
+    }
+
     layoutEngine = () => this._layoutEngine;
     toggleVisibility = action(() => this._collapsed = !this._collapsed);
 
@@ -69,24 +115,30 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         }), returnFalse, emptyFunction);
     }
 
-    contentsDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, returnFalse, returnFalse, action(() => {
-            let prevFilterIndex = NumCast(this.props.Document._prevFilterIndex);
-            if (prevFilterIndex > 0) {
-                prevFilterIndex--;
-                this.props.Document._docFilters = ObjectField.MakeCopy(this.props.Document["_prevDocFilter" + prevFilterIndex] as ObjectField);
-                this.props.Document._docRangeFilters = ObjectField.MakeCopy(this.props.Document["_prevDocRangeFilters" + prevFilterIndex] as ObjectField);
-                this.props.Document._prevFilterIndex = prevFilterIndex;
-            } else {
-                this.props.Document._docFilters = new List([]);
-            }
-        }), false);
+    goTo = (prevFilterIndex: number) => {
+        this.layoutDoc._pivotField = this.layoutDoc["_prevPivotFields" + prevFilterIndex];
+        this.layoutDoc._docFilters = ObjectField.MakeCopy(this.layoutDoc["_prevDocFilter" + prevFilterIndex] as ObjectField);
+        this.layoutDoc._docRangeFilters = ObjectField.MakeCopy(this.layoutDoc["_prevDocRangeFilters" + prevFilterIndex] as ObjectField);
+        this.layoutDoc._prevFilterIndex = prevFilterIndex;
+    }
+
+    @action
+    contentsDown = (e: React.MouseEvent) => {
+        let prevFilterIndex = NumCast(this.layoutDoc._prevFilterIndex);
+        if (prevFilterIndex > 0) {
+            this.goTo(prevFilterIndex - 1);
+        } else {
+            this.layoutDoc._docFilters = new List([]);
+        }
     }
 
     @computed get contents() {
-        return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%", pointerEvents: this.props.active() ? undefined : "none" }} onPointerDown={this.contentsDown}>
+        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 }}
                 fitContentsToDoc={returnTrue}
+                docFilters={this.timeDocFilters}
                 childClickScript={this._childClickedScript}
                 viewDefDivClick={this._viewDefDivClick}
                 childFreezeDimensions={true}
@@ -100,7 +152,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
     }
     specificMenu = (e: React.MouseEvent) => {
         const layoutItems: ContextMenuProps[] = [];
-        const doc = this.props.Document;
+        const doc = this.layoutDoc;
 
         layoutItems.push({ description: "Force Timeline", event: () => { doc._forceRenderEngine = "timeline"; }, icon: "compress-arrows-alt" });
         layoutItems.push({ description: "Force Pivot", event: () => { doc._forceRenderEngine = "pivot"; }, icon: "compress-arrows-alt" });
@@ -123,10 +175,10 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         this.childLayoutPairs.map(pair => this._allFacets.filter(fieldKey =>
             pair.layout[fieldKey] instanceof RichTextField ||
             typeof (pair.layout[fieldKey]) === "number" ||
-            typeof (pair.layout[fieldKey]) === "string").map(fieldKey => keySet.add(fieldKey)));
+            typeof (pair.layout[fieldKey]) === "string").filter(fieldKey => fieldKey[0] !== "_" && (fieldKey[0] === "#" || fieldKey[0] === toUpper(fieldKey)[0])).map(fieldKey => keySet.add(fieldKey)));
         Array.from(keySet).map(fieldKey =>
-            docItems.push({ description: ":" + fieldKey, event: () => this.props.Document._pivotField = fieldKey, icon: "compress-arrows-alt" }));
-        docItems.push({ description: ":(null)", event: () => this.props.Document._pivotField = undefined, icon: "compress-arrows-alt" });
+            docItems.push({ description: ":" + fieldKey, event: () => this.layoutDoc._pivotField = fieldKey, icon: "compress-arrows-alt" }));
+        docItems.push({ description: ":(null)", event: () => this.layoutDoc._pivotField = undefined, icon: "compress-arrows-alt" });
         ContextMenu.Instance.addItem({ description: "Pivot Fields ...", subitems: docItems, icon: "eye" });
         const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
         ContextMenu.Instance.displayMenu(x, y, ":");
@@ -137,13 +189,13 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
             GetValue: () => "",
             SetValue: (value: any) => {
                 if (value?.length) {
-                    this.props.Document._pivotField = value;
+                    this.layoutDoc._pivotField = value;
                     return true;
                 }
                 return false;
             },
             showMenuOnLoad: true,
-            contents: ":" + StrCast(this.props.Document._pivotField),
+            contents: ":" + StrCast(this.layoutDoc._pivotField),
             toggle: this.toggleVisibility,
             color: "#f1efeb" // this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
         };
@@ -155,12 +207,12 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
     render() {
         let nonNumbers = 0;
         this.childDocs.map(doc => {
-            const num = NumCast(doc[StrCast(this.props.Document._pivotField)], Number(StrCast(doc[StrCast(this.props.Document._pivotField)])));
+            const num = NumCast(doc[this.pivotField], Number(StrCast(doc[this.pivotField])));
             if (Number.isNaN(num)) {
                 nonNumbers++;
             }
         });
-        const forceLayout = StrCast(this.props.Document._forceRenderEngine);
+        const forceLayout = StrCast(this.layoutDoc._forceRenderEngine);
         const doTimeline = forceLayout ? (forceLayout === "timeline") : nonNumbers / this.childDocs.length < 0.1 && this.props.PanelWidth() / this.props.PanelHeight() > 6;
         if (doTimeline !== (this._layoutEngine === "timeline")) {
             if (!this._changing) {
@@ -173,7 +225,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         }
 
         return <div className={"collectionTimeView" + (doTimeline ? "" : "-pivot")} onContextMenu={this.specificMenu}
-            style={{ width: this.props.PanelWidth(), height: `calc(100%  - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}>
+            style={{ width: this.props.PanelWidth(), height: `calc(100%  - ${this.layoutDoc._chromeStatus === "enabled" ? 51 : 0}px)` }}>
             {this.pivotKeyUI}
             {this.contents}
             {!this.props.isSelected() || !doTimeline ? (null) : <>
@@ -189,10 +241,17 @@ Scripting.addGlobal(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBoun
     let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex);
     pivotDoc["_prevDocFilter" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField);
     pivotDoc["_prevDocRangeFilters" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docRangeFilters as ObjectField);
+    pivotDoc["_prevPivotFields" + prevFilterIndex] = pivotDoc._pivotField;
     pivotDoc._prevFilterIndex = ++prevFilterIndex;
     runInAction(() => {
         pivotDoc._docFilters = new List();
-        (bounds.payload as string[]).map(filterVal =>
-            Doc.setDocFilter(pivotDoc, StrCast(pivotDoc._pivotField), filterVal, "check"));
+        const filterVals = (bounds.payload as string[]);
+        filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, StrCast(pivotDoc._pivotField), filterVal, "check"));
+        const pivotView = DocumentManager.Instance.getDocumentView(pivotDoc);
+        if (pivotDoc && pivotView?.ComponentView instanceof CollectionTimeView && filterVals.length === 1) {
+            if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) {
+                pivotDoc._pivotField = filterVals[0];
+            }
+        }
     });
 });
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 5cf758e9b..272773d87 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -85,7 +85,8 @@ export function computerPassLayout(
     pivotDoc: Doc,
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
-    viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
+    viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[],
+    engineProps: any
 ) {
     const docMap = new Map<string, PoolData>();
     childPairs.forEach(({ layout, data }, i) => {
@@ -106,7 +107,8 @@ export function computerStarburstLayout(
     pivotDoc: Doc,
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
-    viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
+    viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[],
+    engineProps: any
 ) {
     const docMap = new Map<string, PoolData>();
     const burstRadius = [NumCast(pivotDoc._starburstRadius, panelDim[0]), NumCast(pivotDoc._starburstRadius, panelDim[1])];
@@ -133,14 +135,15 @@ export function computePivotLayout(
     pivotDoc: Doc,
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
-    viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
+    viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[],
+    engineProps: any
 ) {
     const docMap = new Map<string, PoolData>();
     const fieldKey = "data";
     const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>();
 
     let nonNumbers = 0;
-    const pivotFieldKey = toLabel(pivotDoc._pivotField);
+    const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField);
     childPairs.map(pair => {
         const lval = pivotFieldKey === "#" ? Array.from(Object.keys(Doc.GetProto(pair.layout))).filter(k => k.startsWith("#")).map(k => k.substring(1)) :
             Cast(pair.layout[pivotFieldKey], listSpec("string"), null);
@@ -274,7 +277,8 @@ export function computeTimelineLayout(
     pivotDoc: Doc,
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
-    viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
+    viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[],
+    engineProps?: any
 ) {
     const fieldKey = "data";
     const pivotDateGroups = new Map<number, Doc[]>();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 038fe8272..b2ee6bbd0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -78,6 +78,7 @@ export type collectionFreeformViewProps = {
     parentActive: (outsideReaction: boolean) => boolean;
     scaleField?: string;
     noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
+    engineProps?: any;
 };
 
 @observer
@@ -921,7 +922,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             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.layoutEngine?.() || this.props.isAnnotationOverlay || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
+            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 };
@@ -933,10 +934,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             // 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
-                options?.willZoom && (this.Document[this.scaleFieldKey] = scale);
+                scale && (this.Document[this.scaleFieldKey] = scale);
             }
-            Doc.BrushDoc(this.rootDoc);
-            !doc.hidden && Doc.linkFollowHighlight(doc);
+            Doc.BrushDoc(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
@@ -973,6 +973,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y));
         const pt2 = xf.transformPoint(NumCast(doc.x) + doc[WidthSym](), NumCast(doc.y) + doc[HeightSym]());
         const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1] };
+        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 };
 
         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
@@ -982,10 +985,13 @@ 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)) {
+            return {
+                panX: (bounds.left + bounds.right) / 2,
+                panY: (bounds.top + bounds.bot) / 2,
+                scale: Math.min(this.props.PanelHeight() / (bounds.bot - bounds.top), this.props.PanelWidth() / (bounds.right - bounds.left)) / 1.1
+            };
         } 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 {
                 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),
@@ -1116,9 +1122,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             pivotDoc: Doc,
             childPairs: { layout: Doc, data?: Doc }[],
             panelDim: number[],
-            viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[])) => ViewDefResult[]
+            viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[]),
+            engineProps: any) => ViewDefResult[]
     ) {
-        return engine(poolData, this.props.Document, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
+        return engine(poolData, this.props.Document, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX, this.props.engineProps);
     }
 
     doFreeformLayout(poolData: Map<string, PoolData>) {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 779da91e6..da7ed9ac7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -887,12 +887,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
                 }
                 editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
-                const escAnchorId = anchorId[0] > '0' && anchorId[0] <= '9' ? `\\3${anchorId[0]} ${anchorId.substr(1)}` : anchorId;
+                const escAnchorId = anchorId[0] >= '0' && anchorId[0] <= '9' ? `\\3${anchorId[0]} ${anchorId.substr(1)}` : anchorId;
                 addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: "yellow" });
-                setTimeout(() => {
-                    clearStyleSheetRules(FormattedTextBox._highlightStyleSheet);
-                    this.focusSpeed = undefined;
-                }, this.focusSpeed);
+                setTimeout(() => this.focusSpeed = undefined, this.focusSpeed);
+                setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this.focusSpeed || 0, 1500));
             }
         }
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 900b702c6..15c669338 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -115,7 +115,7 @@ export class FormattedTextBoxComment {
             nbef && naft && LinkDocPreview.SetLinkInfo({
                 docProps: textBox.props,
                 linkSrc: textBox.rootDoc,
-                location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - nbef)),
+                location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, nbef - 1))),
                 hrefs,
                 showHeader: true
             });
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 9501d8021..e5d924f42 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -262,9 +262,10 @@ export class RichTextRules {
                 }),
 
             // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document 
-            // [[ <fieldKey> : <Doc>]]   
+            // [[<fieldKey> : <Doc>]]   
             // [[:Doc]] => hyperlink   
             // [[fieldKey]] => show field   
+            // [[fieldKey=value]] => show field and also set its value
             // [[fieldKey:Doc]] => show field of doc
             new InputRule(
                 new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/),
-- 
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/CollectionTimeView.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


From 05b87c394731fbed21f9b358967c337ce294272d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 16 Feb 2021 20:48:29 -0500
Subject: added doubel tap on links button to enter portal for document + links

---
 .../views/collections/CollectionTimeView.tsx       |  4 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  8 +++--
 src/client/views/nodes/DocumentLinksButton.tsx     | 35 ++++++++++++++++++++--
 3 files changed, 41 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 426eced1d..53eb6e0be 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -81,8 +81,8 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         } 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());
+            this.layoutDoc._docFilters = new List<string>(StrListCast(anchor.docFilters));
+            this.layoutDoc._docRangeFilters = new List<string>(StrListCast(anchor.docRangeFilters));
         }
         return 0;
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c54596a9e..a5d7489bd 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -116,6 +116,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @observable _timelineRef = React.createRef<Timeline>();
     @observable _marqueeRef = React.createRef<HTMLDivElement>();
     @observable _focusFilters: Opt<string[]>; // fields that get overriden by focus anchor
+    @observable _focusRangeFilters: 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() {
@@ -1027,7 +1028,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             PanelWidth: childLayout[WidthSym],
             PanelHeight: childLayout[HeightSym],
             docFilters: this.freeformDocFilters,
-            docRangeFilters: this.docRangeFilters,
+            docRangeFilters: this.freeformRangeDocFilters,
             searchFilterDocs: this.searchFilterDocs,
             focus: this.focusDocument,
             styleProvider: this.getClusterColor,
@@ -1207,12 +1208,15 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     }
 
     freeformDocFilters = () => this._focusFilters || this.docFilters();
+    freeformRangeDocFilters = () => this._focusRangeFilters || this.docRangeFilters();
     @action
     setViewSpec = (anchor: Doc, preview: boolean) => {
         if (preview) {
             this._focusFilters = StrListCast(Doc.GetProto(anchor).docFilters);
+            this._focusRangeFilters = StrListCast(Doc.GetProto(anchor).docRangeFilters);
         } else if (anchor.pivotField !== undefined) {
-            this.layoutDoc._docFilters = ObjectField.MakeCopy(Doc.GetProto(anchor).docFilters as ObjectField);
+            this.layoutDoc._docFilters = new List<string>(StrListCast(anchor.docFilters));
+            this.layoutDoc._docRangeFilters = new List<string>(StrListCast(anchor.docRangeFilters));
         }
         return 0;
     }
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 13a6c9df8..8a90d5d62 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -2,7 +2,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { Tooltip } from "@material-ui/core";
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, Opt } from "../../../fields/Doc";
+import { Doc, DocListCast, Opt, WidthSym, DocListCastAsync } from "../../../fields/Doc";
 import { emptyFunction, setupMoveUpEvents, returnFalse, Utils, emptyPath } from "../../../Utils";
 import { TraceMobx } from "../../../fields/util";
 import { DocUtils, Docs } from "../../documents/Documents";
@@ -17,6 +17,9 @@ import { Id } from "../../../fields/FieldSymbols";
 import { TaskCompletionBox } from "./TaskCompletedBox";
 import React = require("react");
 import './DocumentLinksButton.scss';
+import { DocServer } from "../../DocServer";
+import { LightboxView } from "../LightboxView";
+import { cat } from "shelljs";
 
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
@@ -83,7 +86,35 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                     DocumentLinksButton.StartLinkView = this.props.View;
                 }
             } else if (!this.props.InMenu) {
-                DocumentLinksButton.LinkEditorDocView = this.props.View;
+                if (doubleTap) {
+                    const rootDoc = this.props.View.rootDoc;
+                    const docid = Doc.CurrentUserEmail + Doc.GetProto(rootDoc)[Id] + "-pivotish";
+                    DocServer.GetRefField(docid).then(async docx => {
+                        const rootAlias = () => {
+                            const rootAlias = Doc.MakeAlias(rootDoc);
+                            rootAlias.x = rootAlias.y = 0;
+                            return rootAlias;
+                        }
+                        let wid = rootDoc[WidthSym]();
+                        const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([rootAlias()], { title: this.props.View.Document.title + "-pivot", _width: 500, _height: 500, }, docid);
+                        const docs = await DocListCastAsync(Doc.GetProto(target).data);
+                        if (!target.pivotFocusish) (Doc.GetProto(target).pivotFocusish = target);
+                        DocListCast(rootDoc.links).forEach(link => {
+                            const other = LinkManager.getOppositeAnchor(link, rootDoc);
+                            const otherdoc = !other ? undefined : other.annotationOn ? Cast(other.annotationOn, Doc, null) : other;
+                            if (otherdoc && !docs?.some(d => Doc.AreProtosEqual(d, otherdoc))) {
+                                const alias = Doc.MakeAlias(otherdoc);
+                                alias.x = wid;
+                                alias.y = 0;
+                                alias.lockedPosition = false;
+                                wid += otherdoc[WidthSym]();
+                                Doc.AddDocToList(Doc.GetProto(target), "data", alias);
+                            }
+                        });
+                        LightboxView.SetLightboxDoc(target);
+                    });
+                }
+                else DocumentLinksButton.LinkEditorDocView = this.props.View;
             }
         }));
     }
-- 
cgit v1.2.3-70-g09d2


From 0824590ee966ca004aca2e7bb312dd95a09ca87b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 17 Feb 2021 12:44:26 -0500
Subject: changed pivotview tags a bit to keep list small

---
 src/client/views/collections/CollectionTimeView.tsx                    | 3 ++-
 .../collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 53eb6e0be..8067e1d07 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -182,7 +182,8 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         this.childLayoutPairs.map(pair => this._allFacets.filter(fieldKey =>
             pair.layout[fieldKey] instanceof RichTextField ||
             typeof (pair.layout[fieldKey]) === "number" ||
-            typeof (pair.layout[fieldKey]) === "string").filter(fieldKey => fieldKey[0] !== "_" && (fieldKey[0] === "#" || fieldKey[0] === toUpper(fieldKey)[0])).map(fieldKey => keySet.add(fieldKey)));
+            typeof (pair.layout[fieldKey]) === "boolean" ||
+            typeof (pair.layout[fieldKey]) === "string").filter(fieldKey => fieldKey[0] !== "_" && (fieldKey[0] !== "#" || fieldKey === "#") && (fieldKey === "tags" || fieldKey[0] === toUpper(fieldKey)[0])).map(fieldKey => keySet.add(fieldKey)));
         Array.from(keySet).map(fieldKey =>
             docItems.push({ description: ":" + fieldKey, event: () => this.layoutDoc._pivotField = fieldKey, icon: "compress-arrows-alt" }));
         docItems.push({ description: ":(null)", event: () => this.layoutDoc._pivotField = undefined, icon: "compress-arrows-alt" });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 272773d87..afc1babeb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -145,7 +145,7 @@ export function computePivotLayout(
     let nonNumbers = 0;
     const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField);
     childPairs.map(pair => {
-        const lval = pivotFieldKey === "#" ? Array.from(Object.keys(Doc.GetProto(pair.layout))).filter(k => k.startsWith("#")).map(k => k.substring(1)) :
+        const lval = pivotFieldKey === "#" || pivotFieldKey === "tags" ? Array.from(Object.keys(Doc.GetProto(pair.layout))).filter(k => k.startsWith("#")).map(k => k.substring(1)) :
             Cast(pair.layout[pivotFieldKey], listSpec("string"), null);
 
         const num = toNumber(pair.layout[pivotFieldKey]);
-- 
cgit v1.2.3-70-g09d2

