From 51e8786c33a1af207081212802e6da03476edf4a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 9 Nov 2022 15:46:33 -0500
Subject: added tab highlighting when tab doc is target of hyperlink.  made
 region annotations on images be transparent.  don't show lock icon for
 lockedPosition documents that still get pointer events.

---
 src/client/views/StyleProvider.tsx | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

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

diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 5abf4bde2..a268707e0 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -253,7 +253,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             if (!doc || opacity() === 0) return undefined; // if it's not visible, then no shadow)
             if (doc.boxShadow === 'standard') return Shadows.STANDARD_SHADOW;
             if (doc?.isLinkButton && ![DocumentType.LINK, DocumentType.INK].includes(doc.type as any)) return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
-
             switch (doc?.type) {
                 case DocumentType.COL:
                     return StrCast(
@@ -288,7 +287,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             return undefined;
         case StyleProp.Decorations:
             if (props?.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform || doc?.x !== undefined || doc?.y !== undefined) {
-                return doc && isBackground() && !Doc.IsSystem(doc) && (props?.renderDepth || 0) > 0 ? (
+                return doc && doc.pointerEvents === 'none' && isBackground() && !Doc.IsSystem(doc) && (props?.renderDepth || 0) > 0 ? (
                     <div className="styleProvider-lock" onClick={() => toggleLockedPosition(doc)}>
                         <FontAwesomeIcon icon={isBackground() ? 'lock' : 'unlock'} style={{ color: isBackground() ? 'red' : undefined }} size="lg" />
                     </div>
-- 
cgit v1.2.3-70-g09d2


From 493faff1b3c84d8f31796e8059a3eaa5bd80fbd6 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 11 Nov 2022 00:15:46 -0500
Subject: fixed converting strokes to inkMasks. cleaned up rendering inkStrkes
 w/ w/o closed/borders.  fixed highlighting strokes not to show boundingbox.

---
 src/client/views/InkingStroke.tsx                  | 30 +++++-----------------
 src/client/views/StyleProvider.tsx                 |  4 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  4 +--
 4 files changed, 12 insertions(+), 28 deletions(-)

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

diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 8fbe6d7c1..1e18e1dff 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -390,12 +390,9 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
             1.0,
             false
         );
-        const highlightIndex = /*BoolCast(this.props.Document.isLinkButton) && */ Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
-        const highlightColor = isInkMask
-            ? 'transparent'
-            : !highlightIndex
-            ? StrCast(this.layoutDoc.strokeOutlineColor, !closed && fillColor && fillColor !== 'transparent' ? StrCast(this.layoutDoc.color, 'transparent') : 'transparent')
-            : ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'yellow', 'magenta', 'cyan', 'orange'][highlightIndex];
+        const highlight = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Highlighting);
+        const highlightIndex = highlight?.highlightIndex;
+        const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : StrCast(this.layoutDoc.strokeOutlineColor, !closed && fillColor && fillColor !== 'transparent' ? StrCast(this.layoutDoc.color, 'transparent') : 'transparent');
         // Invisible polygonal line that enables the ink to be selected by the user.
         const clickableLine = (downHdlr?: (e: React.PointerEvent) => void, suppressFill: boolean = false) =>
             InteractionUtils.CreatePolyline(
@@ -404,7 +401,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                 inkTop,
                 highlightColor,
                 inkStrokeWidth,
-                Math.max(5, fillColor && closed && highlightIndex ? highlightIndex / 2 : inkStrokeWidth + (fillColor ? (closed ? 0 : highlightIndex + 2) : 0)),
+                inkStrokeWidth + (fillColor ? (closed ? 2 : (highlightIndex ?? 0) + 2) : 2),
                 StrCast(this.layoutDoc.strokeLineJoin),
                 StrCast(this.layoutDoc.strokeLineCap),
                 StrCast(this.layoutDoc.strokeBezier),
@@ -446,9 +443,9 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                         // mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
                         cursor: this.props.isSelected() ? 'default' : undefined,
                     }}
-                    {...(!closed ? interactions : {})}>
-                    {closed ? inkLine : clickableLine(this.onPointerDown)}
-                    {closed ? clickableLine(this.onPointerDown) : inkLine}
+                    {...interactions}>
+                    {clickableLine(this.onPointerDown)}
+                    {inkLine}
                 </svg>
                 {!closed || (!RTFCast(this.rootDoc.text)?.Text && !this.props.isSelected()) ? null : (
                     <div
@@ -475,19 +472,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                         />
                     </div>
                 )}
-                {!closed ? null : (
-                    <svg
-                        className="inkStroke"
-                        style={{
-                            transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
-                            mixBlendMode: 'unset',
-                            cursor: this.props.isSelected() ? 'default' : undefined,
-                            position: 'absolute',
-                        }}
-                        {...interactions}>
-                        {clickableLine(this.onPointerDown, true)}
-                    </svg>
-                )}
             </div>
         );
     }
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index a268707e0..caee19e14 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -113,10 +113,10 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                 const highlightIndex = Doc.isBrushedHighlightedDegree(doc);
                 const highlightColor = ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
                 const highlightStyle = ['solid', 'dashed', 'solid', 'solid', 'solid'][highlightIndex];
-                const excludeTypes = !props?.treeViewDoc ? [DocumentType.FONTICON, DocumentType.INK] : [DocumentType.FONTICON];
+                const excludeTypes = [DocumentType.FONTICON];
                 let highlighting = !props?.disableDocBrushing && highlightIndex && !excludeTypes.includes(doc.type as any) && doc._viewType !== CollectionViewType.Linear; // bcz: hack to turn off highlighting onsidebar panel documents.  need to flag a document as not highlightable in a more direct way
                 if (highlighting && props?.focus !== emptyFunction && StrCast(doc.title) !== '[pres element template]') {
-                    return { highlightStyle, highlightColor, highlightIndex };
+                    return { highlightStyle, highlightColor, highlightIndex, highlightStroke: doc.type === DocumentType.INK };
                 }
             }
             return undefined;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b83d911c7..d44c2f160 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1496,7 +1496,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 elements.push({
                     ele: this.getChildDocView(entry[1]),
                     bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica),
-                    inkMask: BoolCast(entry[1].pair.layout.isInkMask) ? NumCast(entry[1].pair.layout.opacity) : -1,
+                    inkMask: BoolCast(entry[1].pair.layout.isInkMask) ? NumCast(entry[1].pair.layout.opacity, 1) : -1,
                 })
             );
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0ec5839db..6b96362bf 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1385,8 +1385,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 : this.boxShadow || (this.props.Document.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined);
         const renderDoc = this.renderDoc({
             borderRadius: this.borderRounding,
-            outline: highlighting && !this.borderRounding && highlighting ? `${highlighting.highlightColor} ${highlighting.highlightStyle} ${highlighting.highlightIndex}px` : 'solid 0px',
-            border: highlighting && this.borderRounding && highlighting && highlighting.highlightStyle === 'dashed' ? `${highlighting.highlightStyle} ${highlighting.highlightColor} ${highlighting.highlightIndex}px` : undefined,
+            outline: highlighting && !this.borderRounding && !highlighting.highlightStroke? `${highlighting.highlightColor} ${highlighting.highlightStyle} ${highlighting.highlightIndex}px` : 'solid 0px',
+            border: highlighting && this.borderRounding && highlighting.highlightStyle === 'dashed' ? `${highlighting.highlightStyle} ${highlighting.highlightColor} ${highlighting.highlightIndex}px` : undefined,
             boxShadow,
             clipPath: borderPath.path ? `path('${borderPath.path}')` : undefined,
         });
-- 
cgit v1.2.3-70-g09d2


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

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

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

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


From ae324ff50865929be836edf3bbf129207638a9c9 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 16 Nov 2022 16:26:31 -0500
Subject: big changes to make link following use the same code as pinning docs
 for trails.

---
 src/client/documents/Documents.ts                  |   1 +
 src/client/util/DocumentManager.ts                 |  30 ++---
 src/client/util/LinkFollower.ts                    |   7 +-
 src/client/views/DocumentButtonBar.tsx             |   6 +-
 src/client/views/GlobalKeyHandler.ts               |   4 +-
 src/client/views/MarqueeAnnotator.tsx              |   1 +
 src/client/views/StyleProvider.tsx                 |   2 +-
 .../collections/CollectionStackedTimeline.tsx      |   2 +-
 src/client/views/collections/TabDocView.tsx        |   4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  18 ++-
 src/client/views/nodes/ComparisonBox.tsx           |   2 +-
 src/client/views/nodes/DocumentView.tsx            |   8 +-
 src/client/views/nodes/ImageBox.tsx                |  60 ++++------
 src/client/views/nodes/PDFBox.tsx                  |  16 ++-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   2 +-
 src/client/views/nodes/trails/PresBox.tsx          | 131 +++++++++++++--------
 src/client/views/pdf/Annotation.tsx                |   2 +-
 src/client/views/pdf/PDFViewer.tsx                 |  11 +-
 18 files changed, 178 insertions(+), 129 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 8f45802fe..e68b9e27b 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -237,6 +237,7 @@ export class DocumentOptions {
     childContextMenuScripts?: List<ScriptField>;
     childContextMenuLabels?: List<string>;
     childContextMenuIcons?: List<string>;
+    followLinkZoom?: boolean; // whether to zoom to the target of a link
     hideLinkButton?: boolean; // whether the blue link counter button should be hidden
     hideDecorationTitle?: boolean;
     hideOpenButton?: boolean;
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 9336717c0..a60c1ed6b 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -151,6 +151,21 @@ export class DocumentManager {
         return toReturn;
     }
 
+    static playAudioAnno(doc: Doc) {
+        const anno = Cast(doc[Doc.LayoutFieldKey(doc) + '-audioAnnotations'], listSpec(AudioField), null)?.lastElement();
+        if (anno) {
+            if (anno instanceof AudioField) {
+                new Howl({
+                    src: [anno.url.href],
+                    format: ['mp3'],
+                    autoplay: true,
+                    loop: false,
+                    volume: 0.5,
+                });
+            }
+        }
+    }
+
     static addView = (doc: Doc, finished?: () => void) => {
         CollectionDockingView.AddSplit(doc, 'right');
         finished?.();
@@ -189,20 +204,6 @@ export class DocumentManager {
             } else {
                 finalTargetDoc.hidden && (finalTargetDoc.hidden = undefined);
                 !noSelect && docView?.select(false);
-                if (originatingDoc?.followLinkAudio) {
-                    const anno = Cast(finalTargetDoc[Doc.LayoutFieldKey(finalTargetDoc) + '-audioAnnotations'], listSpec(AudioField), null)?.lastElement();
-                    if (anno) {
-                        if (anno instanceof AudioField) {
-                            new Howl({
-                                src: [anno.url.href],
-                                format: ['mp3'],
-                                autoplay: true,
-                                loop: false,
-                                volume: 0.5,
-                            });
-                        }
-                    }
-                }
             }
             finished?.();
         };
@@ -233,6 +234,7 @@ export class DocumentManager {
         }
         if (focusView) {
             !noSelect && Doc.linkFollowHighlight(focusView.rootDoc); //TODO:glr make this a setting in PresBox
+            if (originatingDoc?.followLinkAudio) DocumentManager.playAudioAnno(focusView.rootDoc);
             const doFocus = (forceDidFocus: boolean) =>
                 focusView.focus(originalTarget ?? targetDoc, {
                     originalTarget,
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index c6fc7b372..68716a207 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -6,6 +6,7 @@ import { DocumentDecorations } from '../views/DocumentDecorations';
 import { LightboxView } from '../views/LightboxView';
 import { DocumentViewSharedProps, ViewAdjustment } from '../views/nodes/DocumentView';
 import { DocumentManager } from './DocumentManager';
+import { LinkManager } from './LinkManager';
 import { UndoManager } from './UndoManager';
 
 type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void;
@@ -25,7 +26,7 @@ export class LinkFollower {
     // follows a link - if the target is on screen, it highlights/pans to it.
     // if the target isn't onscreen, then it will open up the target in the lightbox, or in place
     // depending on the followLinkLocation property of the source (or the link itself as a fallback);
-    public static FollowLink = (linkDoc: Opt<Doc>, sourceDoc: Doc, docViewProps: DocumentViewSharedProps, altKey: boolean, zoom: boolean = false) => {
+    public static FollowLink = (linkDoc: Opt<Doc>, sourceDoc: Doc, docViewProps: DocumentViewSharedProps, altKey: boolean) => {
         const batch = UndoManager.StartBatch('follow link click');
         // open up target if it's not already in view ...
         const createViewFunc = (doc: Doc, followLoc: string, finished?: Opt<() => void>) => {
@@ -63,7 +64,6 @@ export class LinkFollower {
             linkDoc,
             sourceDoc,
             createViewFunc,
-            BoolCast(sourceDoc.followLinkZoom, zoom),
             docViewProps.ContainingCollectionDoc,
             action(() => {
                 batch.end();
@@ -73,7 +73,7 @@ export class LinkFollower {
         );
     };
 
-    public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) {
+    public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, createViewFunc: CreateViewFunc, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) {
         const linkDocs = link ? [link] : DocListCast(sourceDoc.links);
         const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor1
         const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor2
@@ -96,6 +96,7 @@ export class LinkFollower {
                         : linkDoc.anchor1
                 ) as Doc;
                 if (target) {
+                    const zoom = BoolCast(LinkManager.getOppositeAnchor(linkDoc, target)?.followLinkZoom, false);
                     if (target.TourMap) {
                         const fieldKey = Doc.LayoutFieldKey(target);
                         const tour = DocListCast(target[fieldKey]).reverse();
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 794b51cc5..681349ccf 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -284,7 +284,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                                     .views()
                                     .filter(v => v)
                                     .map(dv => dv!.rootDoc);
-                                TabDocView.PinDoc(docs, { pinDocLayout, pinDocContent, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) });
+                                TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout, pinDocContent, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) });
                                 e.stopPropagation();
                             }}
                         />
@@ -301,7 +301,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                             .views()
                             .filter(v => v)
                             .map(dv => dv!.rootDoc);
-                        TabDocView.PinDoc(docs, { pinDocLayout: e.shiftKey, pinDocContent: e.altKey, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) });
+                        TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinDocContent: e.altKey, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) });
                         e.stopPropagation();
                     }}>
                     <div className="documentButtonBar-pinTypes">
@@ -488,7 +488,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                     Doc.noviceMode ? null : <div className="documentButtonBar-button">{this.templateButton}</div>
                     /*<div className="documentButtonBar-button"> {this.metadataButton} </div> */
                 }
-                {Doc.noviceMode || !SelectionManager.Views()?.some(v => v.allLinks.length) ? null : <div className="documentButtonBar-button">{this.followLinkButton}</div>}
+                {!SelectionManager.Views()?.some(v => v.allLinks.length) ? null : <div className="documentButtonBar-button">{this.followLinkButton}</div>}
                 <div className="documentButtonBar-button">{this.pinButton}</div>
                 <div className="documentButtonBar-button">{this.recordButton}</div>
                 {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null : <div className="documentButtonBar-button">{this.shareButton}</div>}
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 5a6caf995..4890d9624 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -6,7 +6,7 @@ import { Id } from '../../fields/FieldSymbols';
 import { InkTool } from '../../fields/InkField';
 import { List } from '../../fields/List';
 import { ScriptField } from '../../fields/ScriptField';
-import { Cast, PromiseValue } from '../../fields/Types';
+import { Cast, DocCast, PromiseValue } from '../../fields/Types';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { DocServer } from '../DocServer';
 import { DocumentType } from '../documents/DocumentTypes';
@@ -245,7 +245,7 @@ export class KeyManager {
                 if (SelectionManager.Views().length === 1 && SelectionManager.Views()[0].ComponentView?.search) {
                     SelectionManager.Views()[0].ComponentView?.search?.('', false, false);
                 } else {
-                    const searchBtn = Doc.MySearcher;
+                    const searchBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MySearcher);
                     if (searchBtn) {
                         MainView.Instance.selectMenu(searchBtn);
                     }
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index c0dd62a05..1162cde50 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -93,6 +93,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
                 dragComplete: e => {
                     if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
                         e.annoDragData.linkSourceDoc.isPushpin = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
+                        e.annoDragData.linkSourceDoc.followLinkZoom = false;
                     }
                 },
             });
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index a04f4a4f4..8ee5ebdb6 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -236,7 +236,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                                 ? Colors.DARK_GRAY
                                 : Colors.LIGHT_GRAY // system docs (seen in treeView) get a grayish background
                             : doc.annotationOn
-                            ? '#00000015' // faint interior for collections on PDFs, images, etc
+                            ? '#00000010' // faint interior for collections on PDFs, images, etc
                             : doc?._isGroup
                             ? undefined
                             : Cast((props?.renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (darkScheme() ? Colors.BLACK : 'linear-gradient(#065fff, #85c1f9)'));
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 7bf798656..39ae470b6 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -748,7 +748,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
                     time < NumCast(this.props.mark[this.props.endTag]) &&
                     this._lastTimecode < NumCast(this.props.mark[this.props.startTag]) - 1e-5
                 ) {
-                    LinkFollower.FollowLink(undefined, this.props.mark, this.props as any as DocumentViewProps, false, true);
+                    LinkFollower.FollowLink(undefined, this.props.mark, this.props as any as DocumentViewProps, false);
                 }
                 this._lastTimecode = time;
             }
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 1a9006356..e21649648 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -229,7 +229,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
      * Adds a document to the presentation view
      **/
     @action
-    public static PinDoc(docs: Doc | Doc[], pinProps?: PinProps) {
+    public static PinDoc(docs: Doc | Doc[], pinProps: PinProps) {
         const docList = docs instanceof Doc ? [docs] : docs;
 
         const batch = UndoManager.StartBatch('pinning doc');
@@ -271,7 +271,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 pinDoc.presStartTime = NumCast(doc.clipStart);
                 pinDoc.presEndTime = NumCast(doc.clipEnd, duration);
             }
-            PresBox.pinDocView(pinDoc, pinProps, doc);
+            PresBox.pinDocView(pinDoc, pinProps.pinDocContent ? { ...pinProps, pinData: PresBox.pinDataTypes(doc) } : pinProps, doc);
             pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)');
             Doc.AddDocToList(curPres, 'data', pinDoc, presSelected);
             //save position
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 932bd789d..8a97797c7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1530,11 +1530,27 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return 0;
     };
 
+    @action
+    scrollFocus = (anchor: Doc, smooth: boolean) => {
+        let focusSpeed: Opt<number>;
+        PresBox.restoreTargetDocView(
+            this.rootDoc, //
+            { pinDocLayout: BoolCast(anchor.presPinDocLayout) },
+            anchor,
+            (focusSpeed = !smooth ? 0 : NumCast(anchor.presTransition)),
+            {
+                pannable: anchor.presPinData ? true : false,
+            }
+        );
+        return focusSpeed;
+    }; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
+
     getAnchor = () => {
         if (this.props.Document.annotationOn) {
             return this.rootDoc;
         }
-        const anchor = Docs.Create.TextanchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), annotationOn: this.rootDoc });
+        const anchor = Docs.Create.TextanchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), presTransition: 500, annotationOn: this.rootDoc });
+        PresBox.pinDocView(anchor, { pinData: { pannable: true } }, this.rootDoc);
         const proto = Doc.GetProto(anchor);
         proto[ViewSpecPrefix + '_viewType'] = this.layoutDoc._viewType;
         proto.docFilters = ObjectField.MakeCopy(this.layoutDoc.docFilters as ObjectField) || new List<string>([]);
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index d74da9748..dd03b9b99 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -103,7 +103,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                 <>
                     <DocumentView
                         ref={r => {
-                            whichDoc !== targetDoc && r?.focus(whichDoc, {});
+                            whichDoc !== targetDoc && r?.focus(whichDoc, { instant: true });
                         }}
                         {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit}
                         isContentActive={returnFalse}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 84cacd919..dc468cf89 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -51,7 +51,7 @@ import { LinkAnchorBox } from './LinkAnchorBox';
 import { LinkDocPreview } from './LinkDocPreview';
 import { RadialMenu } from './RadialMenu';
 import { ScriptingBox } from './ScriptingBox';
-import { PresBox } from './trails/PresBox';
+import { PinProps, PresBox } from './trails/PresBox';
 import React = require('react');
 const { Howl } = require('howler');
 
@@ -144,7 +144,7 @@ export interface DocumentViewSharedProps {
     addDocument?: (doc: Doc | Doc[]) => boolean;
     removeDocument?: (doc: Doc | Doc[]) => boolean;
     moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
-    pinToPres: (document: Doc) => void;
+    pinToPres: (document: Doc, pinProps: PinProps) => void;
     ScreenToLocalTransform: () => Transform;
     bringToFront: (doc: Doc, sendToBack?: boolean) => void;
     canEmbedOnDrag?: boolean;
@@ -486,7 +486,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 selected: -1,
             });
         // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "add:right"), icon: "trash", selected: -1 });
-        RadialMenu.Instance.addItem({ description: 'Pin', event: () => this.props.pinToPres(this.props.Document), icon: 'map-pin', selected: -1 });
+        RadialMenu.Instance.addItem({ description: 'Pin', event: () => this.props.pinToPres(this.props.Document, {}), icon: 'map-pin', selected: -1 });
         RadialMenu.Instance.addItem({ description: 'Open', event: () => MobileInterface.Instance.handleClick(this.props.Document), icon: 'trash', selected: -1 });
 
         SelectionManager.DeselectAll();
@@ -545,7 +545,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             .forEach(spec => (this.layoutDoc[spec.replace(ViewSpecPrefix, '')] = (field => (field instanceof ObjectField ? ObjectField.MakeCopy(field) : field))(anchor[spec])));
         // after a render the general viewSpec should have created the right _componentView, so after a timeout, call the componentview to update its specific view specs
         setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false));
-        const focusSpeed = this._componentView?.scrollFocus?.(anchor, options?.instant === false || !LinkDocPreview.LinkInfo);
+        const focusSpeed = this._componentView?.scrollFocus?.(anchor, options?.instant !== true && !LinkDocPreview.LinkInfo);
         const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus?.(true) ?? ViewAdjustment.doNothing;
         this.props.focus(options?.docTransform ? anchor : this.rootDoc, {
             ...options,
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 76ba7765c..2e594d96a 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -30,6 +30,8 @@ import { FaceRectangles } from './FaceRectangles';
 import { FieldView, FieldViewProps } from './FieldView';
 import './ImageBox.scss';
 import React = require('react');
+import { PresBox } from './trails';
+import { DocumentViewProps } from './DocumentView';
 
 export const pageSchema = createSchema({
     googlePhotosUrl: 'string',
@@ -49,23 +51,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         return FieldView.LayoutString(ImageBox, fieldKey);
     }
     private _dropDisposer?: DragManager.DragDropDisposer;
-    private _transitioning: any; // timer for setting view spec properties
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _getAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined;
     @observable _curSuffix = '';
     @observable _uploadIcon = uploadIcons.idle;
-    @observable _focusViewScale: number | undefined = 1;
-    @observable _focusPanX: number | undefined = 0;
-    @observable _focusPanY: number | undefined = 0;
-    get viewScale() {
-        return this._focusViewScale || StrCast(this.layoutDoc._viewScale);
-    }
-    get panX() {
-        return this._focusPanX || StrCast(this.layoutDoc._panX);
-    }
-    get panY() {
-        return this._focusPanY || StrCast(this.layoutDoc._panY);
-    }
 
     protected createDropTarget = (ele: HTMLDivElement) => {
         this._dropDisposer?.();
@@ -73,33 +62,30 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     };
 
     @action
-    setViewSpec = (anchor: Doc, preview: boolean) => {
-        if (preview && anchor._viewScale !== undefined) {
-            this._focusViewScale = Cast(anchor._viewScale, 'number', null);
-            this._focusPanX = Cast(anchor._panX, 'number', null);
-            this._focusPanY = Cast(anchor._panX, 'number', null);
-        } else if (anchor._viewScale !== undefined) {
-            const smoothTime = NumCast(anchor.viewTransitionTime);
-            this.layoutDoc.viewTransition = `all ${smoothTime}ms`;
-            this.layoutDoc._panX = NumCast(anchor._panX, NumCast(this.layoutDoc._panY));
-            this.layoutDoc._panY = NumCast(anchor._panY, NumCast(this.layoutDoc._panX));
-            this.layoutDoc._viewScale = NumCast(anchor._viewScale, NumCast(this.layoutDoc._viewScale));
-            this.layoutDoc[this.fieldKey + '-useAlt'] = Cast(anchor._useAlt, 'boolean', null);
-            if (anchor.type === DocumentType.MARKER) {
-                this.dataDoc[this.annotationKey] = new List<Doc>(DocListCast(anchor._annotations));
-            }
-            clearTimeout(this._transitioning);
-            this._transitioning = setTimeout(() => (this.layoutDoc.viewTransition = undefined), smoothTime);
-        }
+    scrollFocus = (anchor: Doc, smooth: boolean) => {
+        let focusSpeed: Opt<number>;
+        PresBox.restoreTargetDocView(
+            this.rootDoc, //
+            { pinDocLayout: BoolCast(anchor.presPinDocLayout) },
+            anchor,
+            (focusSpeed = !smooth ? 0 : NumCast(anchor.presTransition)),
+            !anchor.presPinData
+                ? {}
+                : {
+                      pannable: true,
+                      dataannos: anchor.presAnnotations !== undefined,
+                      dataview: true,
+                  }
+        );
+        return focusSpeed;
     }; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
 
     getAnchor = () => {
         const anchor =
             this._getAnchor?.(this._savedAnnotations) ?? // use marquee anchor, otherwise, save zoom/pan as anchor
-            Docs.Create.ImageanchorDocument({ viewTransitionTime: 1000, unrendered: true, annotationOn: this.rootDoc, _viewScale: NumCast(this.layoutDoc._viewScale, 1), _panX: NumCast(this.layoutDoc._panX), _panY: NumCast(this.layoutDoc._panY) });
+            Docs.Create.ImageanchorDocument({ presTransition: 1000, unrendered: true, annotationOn: this.rootDoc });
         if (anchor) {
-            anchor._useAlt = Cast(this.layoutDoc[this.fieldKey + '-useAlt'], 'boolean', null);
-            anchor._annotations = new List<Doc>(DocListCast(this.dataDoc[this.annotationKey]));
+            PresBox.pinDocView(anchor, { pinData: { pannable: true, dataview: true, dataannos: true } }, this.rootDoc);
             this.addDocument(anchor);
             return anchor;
         }
@@ -130,7 +116,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     }
 
     componentWillUnmount() {
-        clearTimeout(this._transitioning);
         Object.values(this._disposers).forEach(disposer => disposer?.());
     }
 
@@ -425,6 +410,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         this.props.select(false);
     };
     savedAnnotations = () => this._savedAnnotations;
+    styleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
+        if (property === StyleProp.BoxShadow) return undefined;
+        return this.props.styleProvider?.(doc, props, property);
+    };
 
     render() {
         TraceMobx();
@@ -445,6 +434,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
                     renderDepth={this.props.renderDepth + 1}
                     fieldKey={this.annotationKey}
+                    styleProvider={this.styleProvider}
                     CollectionView={undefined}
                     isAnnotationOverlay={true}
                     annotationLayerHostsContent={true}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index fcb3ccb07..39e323247 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -5,7 +5,7 @@ import * as Pdfjs from 'pdfjs-dist';
 import 'pdfjs-dist/web/pdf_viewer.css';
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
-import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField, PdfField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils';
@@ -27,6 +27,7 @@ import { ImageBox } from './ImageBox';
 import './PDFBox.scss';
 import { VideoBox } from './VideoBox';
 import React = require('react');
+import { PresBox } from './trails';
 
 @observer
 export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
@@ -207,16 +208,19 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1;
         this._initialScrollTarget = doc;
-        return this._pdfViewer?.scrollFocus(doc, smooth) ?? (didToggle ? 1 : undefined);
+        PresBox.restoreTargetDocView(this.rootDoc, {}, doc, NumCast(doc.presTransition, NumCast(doc.focusSpeed, 500)), { pannable: doc.presPinData ? true : false });
+        return this._pdfViewer?.scrollFocus(doc, NumCast(doc.presPinViewScroll, NumCast(doc.y)), smooth) ?? (didToggle ? 1 : undefined);
     };
     getAnchor = () => {
-        const anchor =
-            this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations()) ??
-            Docs.Create.TextanchorDocument({
+        const docAnchor = () => {
+            const anchor = Docs.Create.TextanchorDocument({
                 title: StrCast(this.rootDoc.title + '@' + NumCast(this.layoutDoc._scrollTop)?.toFixed(0)),
-                y: NumCast(this.layoutDoc._scrollTop),
                 unrendered: true,
             });
+            PresBox.pinDocView(anchor, { pinData: { scrollable: true, pannable: true } }, this.rootDoc);
+            return anchor;
+        };
+        const anchor = this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations()) ?? docAnchor();
         this.addDocument(anchor);
         return anchor;
     };
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2984feba5..fdd61463d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -686,7 +686,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     };
 
     @undoBatch
-    pinToPres = (anchor: Doc) => this.props.pinToPres(anchor);
+    pinToPres = (anchor: Doc) => this.props.pinToPres(anchor, {});
 
     @undoBatch
     makePushpin = (anchor: Doc) => (anchor.isPushpin = !anchor.isPushpin);
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 8d805c663..10f2dc016 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -12,7 +12,8 @@ import { List } from '../../../../fields/List';
 import { ObjectField } from '../../../../fields/ObjectField';
 import { listSpec } from '../../../../fields/Schema';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse, returnOne, returnTrue, setupMoveUpEvents, StopEvent } from '../../../../Utils';
+import { AudioField } from '../../../../fields/URLField';
+import { emptyFunction, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { Docs } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
@@ -33,6 +34,7 @@ import { FieldView, FieldViewProps } from '../FieldView';
 import { ScriptingBox } from '../ScriptingBox';
 import './PresBox.scss';
 import { PresEffect, PresMovement, PresStatus } from './PresEnums';
+const { Howl } = require('howler');
 
 export interface PinProps {
     audioRange?: boolean;
@@ -41,6 +43,17 @@ export interface PinProps {
     pinViewport?: MarqueeViewBounds; // pin a specific viewport on a freeform view (use MarqueeView.CurViewBounds to compute if no region has been selected)
     pinDocLayout?: boolean; // pin layout info (width/height/x/y)
     pinDocContent?: boolean; // pin data info (scroll/pan/zoom/text)
+    pinAudioPlay?: boolean; // pin audio annotation
+    pinData?: {
+        scrollable?: boolean | undefined;
+        pannable?: boolean | undefined;
+        temporal?: boolean | undefined;
+        clippable?: boolean | undefined;
+        dataview?: boolean | undefined;
+        textview?: boolean | undefined;
+        poslayoutview?: boolean | undefined;
+        dataannos?: boolean | undefined;
+    };
 }
 
 @observer
@@ -333,26 +346,37 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             this.onHideDocument(); //Handles hide after/before
         }
     });
-    static pinDataTypes(target: Doc) {
+    static pinDataTypes(target: Doc): { scrollable?: boolean; pannable?: boolean; temporal?: boolean; clippable?: boolean; dataview?: boolean; textview?: boolean; poslayoutview?: boolean; dataannos?: boolean } {
         const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(target.type as any) || target._viewType === CollectionViewType.Stacking;
-        const pannable = [DocumentType.IMG].includes(target.type as any) || (target.type === DocumentType.COL && target._viewType === CollectionViewType.Freeform);
+        const pannable = [DocumentType.IMG, DocumentType.PDF].includes(target.type as any) || (target.type === DocumentType.COL && target._viewType === CollectionViewType.Freeform);
         const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(target.type as any);
         const clippable = [DocumentType.COMPARISON].includes(target.type as any);
         const dataview = [DocumentType.INK, DocumentType.COL, DocumentType.IMG].includes(target.type as any) && target.activeFrame === undefined;
         const poslayoutview = [DocumentType.COL].includes(target.type as any) && target.activeFrame === undefined;
         const textview = [DocumentType.RTF].includes(target.type as any) && target.activeFrame === undefined;
-        return { scrollable, pannable, temporal, clippable, dataview, textview, poslayoutview };
+        const dataannos = false;
+        return { scrollable, pannable, temporal, clippable, dataview, textview, poslayoutview, dataannos };
     }
 
     @action
-    static restoreTargetDocView(bestTarget: Doc, activeItem: Doc) {
-        const transTime = NumCast(activeItem.presTransition, 500);
+    playAnnotation = (anno: AudioField) => {};
+    @action
+    static restoreTargetDocView(bestTarget: Doc, pinProps: PinProps | undefined, activeItem: Doc, transTime: number, pinDataTypes = this.pinDataTypes(bestTarget)) {
         const presTransitionTime = `all ${transTime}ms`;
-        const { scrollable, pannable, temporal, clippable, dataview, textview, poslayoutview } = this.pinDataTypes(bestTarget);
         bestTarget._viewTransition = presTransitionTime;
-        if (clippable) bestTarget._clipWidth = activeItem.presPinClipWidth;
-        if (temporal) bestTarget._currentTimecode = activeItem.presStartTime;
-        if (scrollable) {
+        if (pinProps?.pinDocLayout) {
+            const transTime = NumCast(activeItem.presTransition, 500);
+            bestTarget._dataTransition = `all ${transTime}ms`;
+            bestTarget.x = NumCast(activeItem.presX, NumCast(bestTarget.x));
+            bestTarget.y = NumCast(activeItem.presY, NumCast(bestTarget.y));
+            bestTarget.rotation = NumCast(activeItem.presRot, NumCast(bestTarget.rotation));
+            bestTarget.width = NumCast(activeItem.presWidth, NumCast(bestTarget.width));
+            bestTarget.height = NumCast(activeItem.presHeight, NumCast(bestTarget.height));
+            setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10);
+        }
+        if (pinDataTypes.clippable) bestTarget._clipWidth = activeItem.presPinClipWidth;
+        if (pinDataTypes.temporal) bestTarget._currentTimecode = activeItem.presStartTime;
+        if (pinDataTypes.scrollable) {
             bestTarget._scrollTop = activeItem.presPinViewScroll;
             const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number'));
             if (contentBounds) {
@@ -360,13 +384,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 dv?.brushView?.({ panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] });
             }
         }
-        if (dataview && activeItem.presData !== undefined) {
+        if (pinDataTypes.dataannos) {
+            const fkey = Doc.LayoutFieldKey(bestTarget);
+            Doc.GetProto(bestTarget)[fkey + '-annotations'] = new List<Doc>(DocListCast(activeItem.presAnnotations));
+        }
+        if (pinDataTypes.dataview && activeItem.presData !== undefined) {
             const fkey = Doc.LayoutFieldKey(bestTarget);
             Doc.GetProto(bestTarget)[fkey] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData;
             bestTarget[fkey + '-useAlt'] = activeItem.presUseAlt;
         }
-        if (textview && activeItem.presData !== undefined) Doc.GetProto(bestTarget)[Doc.LayoutFieldKey(bestTarget)] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData;
-        if (poslayoutview) {
+        if (pinDataTypes.textview && activeItem.presData !== undefined) Doc.GetProto(bestTarget)[Doc.LayoutFieldKey(bestTarget)] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData;
+        if (pinDataTypes.poslayoutview) {
             StrListCast(activeItem.presPinLayoutData)
                 .map(str => JSON.parse(str) as { id: string; x: number; y: number; w: number; h: number })
                 .forEach(data => {
@@ -385,7 +413,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 transTime + 10
             );
         }
-        if (pannable) {
+        if (pinDataTypes.pannable) {
             const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number'));
             if (contentBounds) {
                 const viewport = { panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] };
@@ -410,9 +438,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     /// reserved fields on the pinDoc so that those values can be restored to the
     /// target doc when navigating to it.
     @action
-    static pinDocView(pinDoc: Doc, pinProps: PinProps | undefined, targetDoc: Doc) {
-        const { scrollable, pannable, temporal, clippable, dataview, textview, poslayoutview } = this.pinDataTypes(pinDoc);
-        if (pinProps?.pinDocLayout) {
+    static pinDocView(pinDoc: Doc, pinProps: PinProps, targetDoc: Doc) {
+        if (pinProps.pinDocLayout) {
             pinDoc.presPinLayout = true;
             pinDoc.presX = NumCast(targetDoc.x);
             pinDoc.presY = NumCast(targetDoc.y);
@@ -420,33 +447,46 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             pinDoc.presWidth = NumCast(targetDoc.width);
             pinDoc.presHeight = NumCast(targetDoc.height);
         }
-        if (pinProps?.pinDocContent) {
-            pinDoc.presPinData = scrollable || temporal || pannable || clippable || dataview || textview || poslayoutview || pinProps.activeFrame !== undefined;
-            if (dataview) {
+        if (pinProps.pinAudioPlay) pinDoc.followLinkAudio = true;
+        if (pinProps.pinData) {
+            pinDoc.presPinData =
+                pinProps.pinData.scrollable ||
+                pinProps.pinData.temporal ||
+                pinProps.pinData.pannable ||
+                pinProps.pinData.clippable ||
+                pinProps.pinData.dataview ||
+                pinProps.pinData.textview ||
+                pinProps.pinData.poslayoutview ||
+                pinProps?.activeFrame !== undefined;
+            if (pinProps.pinData.dataview) {
                 const fkey = Doc.LayoutFieldKey(targetDoc);
                 pinDoc.presUseAlt = targetDoc[fkey + '-useAlt'];
                 pinDoc.presData = targetDoc[fkey] instanceof ObjectField ? (targetDoc[fkey] as ObjectField)[Copy]() : targetDoc.data;
             }
-            if (textview) pinDoc.presData = targetDoc[Doc.LayoutFieldKey(targetDoc)] instanceof ObjectField ? (targetDoc[Doc.LayoutFieldKey(targetDoc)] as ObjectField)[Copy]() : targetDoc.text;
-            if (scrollable) pinDoc.presPinViewScroll = pinDoc._scrollTop;
-            if (clippable) pinDoc.presPinClipWidth = pinDoc._clipWidth;
-            if (poslayoutview) pinDoc.presPinLayoutData = new List<string>(DocListCast(pinDoc.presData).map(d => JSON.stringify({ id: d[Id], x: NumCast(d.x), y: NumCast(d.y), w: NumCast(d._width), h: NumCast(d._height) })));
-            if (pannable) {
-                pinDoc.presPinViewX = NumCast(pinDoc._panX);
-                pinDoc.presPinViewY = NumCast(pinDoc._panY);
-                pinDoc.presPinViewScale = NumCast(pinDoc._viewScale, 1);
+            if (pinProps.pinData.dataannos) {
+                const fkey = Doc.LayoutFieldKey(targetDoc);
+                pinDoc.presAnnotations = new List<Doc>(DocListCast(Doc.GetProto(targetDoc)[fkey + '-annotations']));
+            }
+            if (pinProps.pinData.textview) pinDoc.presData = targetDoc[Doc.LayoutFieldKey(targetDoc)] instanceof ObjectField ? (targetDoc[Doc.LayoutFieldKey(targetDoc)] as ObjectField)[Copy]() : targetDoc.text;
+            if (pinProps.pinData.scrollable) pinDoc.presPinViewScroll = targetDoc._scrollTop;
+            if (pinProps.pinData.clippable) pinDoc.presPinClipWidth = targetDoc._clipWidth;
+            if (pinProps.pinData.poslayoutview) pinDoc.presPinLayoutData = new List<string>(DocListCast(targetDoc.presData).map(d => JSON.stringify({ id: d[Id], x: NumCast(d.x), y: NumCast(d.y), w: NumCast(d._width), h: NumCast(d._height) })));
+            if (pinProps.pinData.pannable) {
+                pinDoc.presPinViewX = NumCast(targetDoc._panX);
+                pinDoc.presPinViewY = NumCast(targetDoc._panY);
+                pinDoc.presPinViewScale = NumCast(targetDoc._viewScale, 1);
             }
-            if (temporal) {
-                pinDoc.presStartTime = pinDoc._currentTimecode;
-                const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], NumCast(pinDoc.presStartTime) + 0.1);
-                pinDoc.presEndTime = NumCast(pinDoc.clipEnd, duration);
+            if (pinProps.pinData.temporal) {
+                pinDoc.presStartTime = targetDoc._currentTimecode;
+                const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], NumCast(targetDoc.presStartTime) + 0.1);
+                pinDoc.presEndTime = NumCast(targetDoc.clipEnd, duration);
             }
         }
         if (pinProps?.pinViewport) {
             // If pinWithView option set then update scale and x / y props of slide
             const bounds = pinProps.pinViewport;
             pinDoc.presPinView = true;
-            pinDoc.presPinViewScale = NumCast(pinDoc._viewScale, 1);
+            pinDoc.presPinViewScale = NumCast(targetDoc._viewScale, 1);
             pinDoc.presPinViewX = bounds.left + bounds.width / 2;
             pinDoc.presPinViewY = bounds.top + bounds.height / 2;
             pinDoc.presPinViewBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]);
@@ -507,35 +547,24 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     static NavigateToTarget(targetDoc: Doc, activeItem: Doc, openInTab: any, srcContext: Doc, finished?: () => void) {
-        if ((activeItem.presPinLayout || activeItem.presPinView) && DocCast(targetDoc.context)?._currentFrame === undefined) {
-            const transTime = NumCast(activeItem.presTransition, 500);
-            targetDoc._dataTransition = `all ${transTime}ms`;
-            targetDoc.x = NumCast(activeItem.presX, NumCast(targetDoc.x));
-            targetDoc.y = NumCast(activeItem.presY, NumCast(targetDoc.y));
-            targetDoc.rotation = NumCast(activeItem.presRot, NumCast(targetDoc.rotation));
-            targetDoc.width = NumCast(activeItem.presWidth, NumCast(targetDoc.width));
-            targetDoc.height = NumCast(activeItem.presHeight, NumCast(targetDoc.height));
-            setTimeout(() => (targetDoc._dataTransition = undefined), transTime + 10);
-        }
         // If openDocument is selected then it should open the document for the user
         if (activeItem.openDocument) {
             LightboxView.SetLightboxDoc(targetDoc); // openInTab(targetDoc);
         } else if (targetDoc && activeItem.presMovement !== PresMovement.None) {
             LightboxView.SetLightboxDoc(undefined);
             const zooming = activeItem.presMovement !== PresMovement.Pan;
-            DocumentManager.Instance.jumpToDocument(targetDoc, zooming, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, finished, undefined, true, NumCast(activeItem.presZoom, 1));
+            DocumentManager.Instance.jumpToDocument(targetDoc, zooming, openInTab, srcContext ? [srcContext] : [], undefined, undefined, activeItem, finished, undefined, true, NumCast(activeItem.presZoom, 1));
         } else if (activeItem.presMovement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) {
             (DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.();
         }
         // After navigating to the document, if it is added as a presPinView then it will
         // adjust the pan and scale to that of the pinView when it was added.
-        if (activeItem.presPinData || activeItem.presPinView) {
+        const pinDocLayout = (BoolCast(activeItem.presPinLayout) || BoolCast(activeItem.presPinView)) && DocCast(targetDoc.context)?._currentFrame === undefined;
+        if (activeItem.presPinData || activeItem.presPinView || pinDocLayout) {
             clearTimeout(PresBox._navTimer);
             // targetDoc may or may not be displayed.  this gets the first available document (or alias) view that matches targetDoc
             const bestTargetView = DocumentManager.Instance.getFirstDocumentView(targetDoc);
-            const bestTarget = bestTargetView?.props.Document;
-            if (bestTarget) PresBox._navTimer = PresBox.restoreTargetDocView(bestTarget, activeItem);
-            activeItem.presPinAudioPlay && bestTargetView?.docView?.playAnnotation();
+            if (bestTargetView?.props.Document) PresBox._navTimer = PresBox.restoreTargetDocView(bestTargetView?.props.Document, { pinDocLayout }, activeItem, NumCast(activeItem.presTransition, 500));
         }
     }
 
@@ -747,7 +776,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             } else {
                 if (!doc.aliasOf) {
                     const original = Doc.MakeAlias(doc);
-                    TabDocView.PinDoc(original);
+                    TabDocView.PinDoc(original, {});
                     setTimeout(() => this.removeDocument(doc), 0);
                     return false;
                 } else {
@@ -1316,7 +1345,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             Effects
                             <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
                                 <div className="presBox-subheading">Play Audio Annotation</div>
-                                <input className="presBox-checkbox" style={{ margin: 10 }} type="checkbox" onChange={() => (activeItem.presPinAudioPlay = !BoolCast(activeItem.presPinAudioPlay))} checked={BoolCast(activeItem.presPinAudioPlay)} />
+                                <input className="presBox-checkbox" style={{ margin: 10 }} type="checkbox" onChange={() => (activeItem.followLinkAudio = !BoolCast(activeItem.followLinkAudio))} checked={BoolCast(activeItem.followLinkAudio)} />
                             </div>
                             <div
                                 className="presBox-dropdown"
@@ -1694,7 +1723,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const presData = Cast(this.rootDoc.data, listSpec(Doc));
             if (data && presData) {
                 data.push(doc);
-                TabDocView.PinDoc(doc);
+                TabDocView.PinDoc(doc, {});
                 this.gotoDocument(this.childDocs.length, this.activeItem);
             } else {
                 this.props.addDocTab(doc, 'add:right');
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 9af0949eb..133b882f6 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -52,7 +52,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
     };
 
     @undoBatch
-    pinToPres = () => this.props.pinToPres(this.annoTextRegion);
+    pinToPres = () => this.props.pinToPres(this.annoTextRegion, {});
 
     @undoBatch
     makePushpin = () => (this.annoTextRegion.isPushpin = !this.annoTextRegion.isPushpin);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 6ff87ef9f..5a5c63c3d 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -166,12 +166,12 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     // scrolls to focus on a nested annotation document.  if this is part a link preview then it will jump to the scroll location,
     // otherwise it will scroll smoothly.
-    scrollFocus = (doc: Doc, smooth: boolean) => {
+    scrollFocus = (doc: Doc, scrollTop: number, smooth: boolean) => {
         const mainCont = this._mainCont.current;
         let focusSpeed: Opt<number>;
         if (doc !== this.props.rootDoc && mainCont) {
             const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
-            const scrollTo = doc.unrendered ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, 0.1 * windowHeight, NumCast(this.props.Document.scrollHeight));
+            const scrollTo = doc.unrendered ? scrollTop : Utils.scrollIntoView(scrollTop, doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, 0.1 * windowHeight, NumCast(this.props.Document.scrollHeight));
             if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) {
                 if (!this._pdfViewer) this._initialScroll = scrollTo;
                 else if (smooth) smoothScroll((focusSpeed = NumCast(doc.focusSpeed, 500)), mainCont, scrollTo);
@@ -203,6 +203,11 @@ export class PDFViewer extends React.Component<IViewerProps> {
         }
         document.removeEventListener('pagesinit', this.pagesinit);
         var quickScroll: string | undefined = this._initialScroll ? this._initialScroll.toString() : '';
+        this._disposers.scale = reaction(
+            () => NumCast(this.props.layoutDoc._viewScale, 1),
+            scale => (this._pdfViewer.currentScaleValue = scale),
+            { fireImmediately: true }
+        );
         this._disposers.scroll = reaction(
             () => Math.abs(NumCast(this.props.Document._scrollTop)),
             pos => {
@@ -290,7 +295,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     @action
     scrollToAnnotation = (scrollToAnnotation: Doc) => {
         if (scrollToAnnotation) {
-            this.scrollFocus(scrollToAnnotation, true);
+            this.scrollFocus(scrollToAnnotation, NumCast(scrollToAnnotation.y), true);
             Doc.linkFollowHighlight(scrollToAnnotation);
         }
     };
-- 
cgit v1.2.3-70-g09d2


From ae8e487fbf7a5e662a04c14e805041bbdf49c87d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 30 Nov 2022 12:58:47 -0500
Subject: show link count for isLinkButton docs, don't show link links for
 sidebaranno links by default.

---
 src/client/views/SidebarAnnos.tsx       | 3 ++-
 src/client/views/StyleProvider.tsx      | 2 +-
 src/client/views/nodes/DocumentView.tsx | 5 ++---
 3 files changed, 5 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index ec68a6b36..6d06bbbf6 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -72,7 +72,8 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
         });
         FormattedTextBox.SelectOnLoad = target[Id];
         FormattedTextBox.DontSelectInitialText = true;
-        DocUtils.MakeLink({ doc: anchor }, { doc: target }, 'inline comment:comment on');
+        const link = DocUtils.MakeLink({ doc: anchor }, { doc: target }, 'inline comment:comment on');
+        link && (link.linkDisplay = false);
 
         const taggedContent = this.docFilters()
             .filter(data => data.split(':')[0])
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 8ee5ebdb6..dc7a4e047 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -129,7 +129,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.Opacity:
             return Cast(doc?._opacity, 'number', Cast(doc?.opacity, 'number', null));
         case StyleProp.HideLinkButton:
-            return props?.hideLinkButton || (!selected && (doc?.isLinkButton || doc?.hideLinkButton));
+            return props?.hideLinkButton || (!selected && doc?.hideLinkButton);
         case StyleProp.FontSize:
             return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(doc?._fontSize, StrCast(Doc.UserDoc().fontSize)));
         case StyleProp.FontFamily:
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a8bea61c9..48e32c071 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1535,9 +1535,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     linkButtonInverseScaling = () => (this.props.NativeDimScaling?.() || 1) * this.screenToLocalTransform().Scale;
 
     @computed get linkCountView() {
-        return this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (this.isSelected() && this.props.renderDepth) || !this._isHovering || this.hideLinkButton ? null : (
-            <DocumentLinksButton View={this} scaling={this.linkButtonInverseScaling} OnHover={true} Bottom={this.topMost} ShowCount={true} />
-        );
+        const hideCount = this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (this.isSelected() && this.props.renderDepth) || !this._isHovering || this.hideLinkButton;
+        return hideCount ? null : <DocumentLinksButton View={this} scaling={this.linkButtonInverseScaling} OnHover={true} Bottom={this.topMost} ShowCount={true} />;
     }
 
     @computed get docViewPath(): DocumentView[] {
-- 
cgit v1.2.3-70-g09d2


From ea5c60384399e29f7b34e27e2607320134bc5432 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 6 Dec 2022 14:08:12 -0500
Subject: added long press to select documents.  fixed/reactivated link
 size/colors.  fixed stackedTimelineItems so that they don't get pointer
 events until container content is active.

---
 src/client/views/PropertiesView.tsx                | 14 +++++----
 src/client/views/StyleProvider.tsx                 |  4 +--
 .../collections/CollectionStackedTimeline.tsx      | 33 ++++------------------
 .../CollectionFreeFormLinkView.tsx                 |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 32 ++++++++++++++++-----
 src/client/views/nodes/LinkDocPreview.tsx          |  4 ++-
 6 files changed, 45 insertions(+), 44 deletions(-)

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

diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index bc08e920a..905f9e2d0 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -1332,8 +1332,12 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         );
     }
 
-    @observable description = Field.toString(LinkManager.currentLink?.description as any as Field);
-    @observable relationship = StrCast(LinkManager.currentLink?.linkRelationship);
+    @computed get description() {
+        return Field.toString(LinkManager.currentLink?.description as any as Field);
+    }
+    @computed get relationship() {
+        return StrCast(LinkManager.currentLink?.linkRelationship);
+    }
     @observable private relationshipButtonColor: string = '';
 
     // @action
@@ -1343,8 +1347,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     @undoBatch
     handleDescriptionChange = action((value: string) => {
         if (LinkManager.currentLink && this.selectedDoc) {
-            this.selectedDoc.description = value;
-            this.description = value;
+            this.setDescripValue(value);
             return true;
         }
     });
@@ -1352,8 +1355,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     @undoBatch
     handleLinkRelationshipChange = action((value: string) => {
         if (LinkManager.currentLink && this.selectedDoc) {
-            this.selectedDoc.linkRelationship = value;
-            this.relationship = value;
+            this.setLinkRelationshipValue(value);
             return true;
         }
     });
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index dc7a4e047..b77f01250 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, runInAction } from 'mobx';
 import { extname } from 'path';
-import { Doc, Opt } from '../../fields/Doc';
+import { Doc, DocListCast, Opt } from '../../fields/Doc';
 import { BoolCast, Cast, DocCast, ImageCast, NumCast, StrCast } from '../../fields/Types';
 import { DashColor, emptyFunction, lightOrDark } from '../../Utils';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
@@ -252,7 +252,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.BoxShadow: {
             if (!doc || opacity() === 0 || doc.noShadow) return undefined; // if it's not visible, then no shadow)
             if (doc.boxShadow === 'standard') return Shadows.STANDARD_SHADOW;
-            if (doc?.isLinkButton && ![DocumentType.LINK, DocumentType.INK].includes(doc.type as any)) return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
+            if (doc?.isLinkButton && DocListCast(doc?.links).length && ![DocumentType.LINK, DocumentType.INK].includes(doc.type as any)) return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
             switch (doc?.type) {
                 case DocumentType.COL:
                     return StrCast(
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 39ae470b6..e9bf03208 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -8,6 +8,7 @@ import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { Cast, NumCast } from '../../../fields/Types';
+import { ImageField } from '../../../fields/URLField';
 import { emptyFunction, formatTime, OmitKeys, returnFalse, returnOne, returnTrue, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -23,13 +24,10 @@ import { AudioWaveform } from '../AudioWaveform';
 import { CollectionSubView } from '../collections/CollectionSubView';
 import { Colors } from '../global/globalEnums';
 import { LightboxView } from '../LightboxView';
-import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps, DocumentViewSharedProps } from '../nodes/DocumentView';
+import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
 import { LabelBox } from '../nodes/LabelBox';
-import './CollectionStackedTimeline.scss';
 import { VideoBox } from '../nodes/VideoBox';
-import { ImageField } from '../../../fields/URLField';
-import { StyleProp } from '../StyleProvider';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import './CollectionStackedTimeline.scss';
 
 export type CollectionStackedTimelineProps = {
     Play: () => void;
@@ -610,7 +608,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                                         height={height}
                                         toTimeline={this.toTimeline}
                                         layoutDoc={this.layoutDoc}
-                                        // isDocumentActive={this.props.childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
+                                        isDocumentActive={this.isContentActive}
                                         currentTimecode={this.currentTimecode}
                                         _timeline={this._timeline}
                                         stackedTimeline={this}
@@ -702,7 +700,7 @@ interface StackedTimelineAnchorProps {
     endTag: string;
     renderDepth: number;
     layoutDoc: Doc;
-    isDocumentActive?: () => boolean;
+    isDocumentActive?: () => boolean | undefined;
     ScreenToLocalTransform: () => Transform;
     _timeline: HTMLDivElement | null;
     focus: DocFocusFunc;
@@ -806,25 +804,6 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
         return [resetTitle];
     };
 
-    innerStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
-        if (property === StyleProp.Decorations && doc && NumCast(doc.timecodeToHide) - NumCast(doc.timecodeToShow) < 0.0002) {
-            return (
-                <div className="styleProvider-lock">
-                    <FontAwesomeIcon
-                        icon={'camera'}
-                        style={{ color: 'red' }}
-                        onClick={e => {
-                            LinkFollower.FollowLink(undefined, doc, props as DocumentViewSharedProps, e.altKey);
-                            e.stopPropagation();
-                        }}
-                        size="lg"
-                    />
-                </div>
-            );
-        }
-        return this.props.styleProvider?.(doc, props, property);
-    };
-
     // renders anchor LabelBox
     renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), screenXf: () => Transform, width: () => number, height: () => number) {
         const anchor = observable({ view: undefined as any });
@@ -841,7 +820,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
                     ref={action((r: DocumentView | null) => (anchor.view = r))}
                     Document={mark}
                     DataDoc={undefined}
-                    styleProvider={this.innerStyleProvider}
+                    styleProvider={this.props.styleProvider}
                     renderDepth={this.props.renderDepth + 1}
                     LayoutTemplate={undefined}
                     LayoutTemplateString={LabelBox.LayoutStringWithTitle('data', this.computeTitle())}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index c57810a98..b85da2ce7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -241,7 +241,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
 
         const link = this.props.LinkDocs[0];
         const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData;
-        const linkRelationship = Field.toString(LinkManager.currentLink?.linkRelationship as any as Field); //get string representing relationship
+        const linkRelationship = Field.toString(link?.linkRelationship as any as Field); //get string representing relationship
         const linkRelationshipList = Doc.UserDoc().linkRelationshipList as List<string>;
         const linkColorList = Doc.UserDoc().linkColorList as List<string>;
         const linkRelationshipSizes = Doc.UserDoc().linkRelationshipSizes as List<number>;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4abfef563..778965553 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -239,9 +239,8 @@ export interface DocumentViewInternalProps extends DocumentViewProps {
 @observer
 export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps>() {
     public static SelectAfterContextMenu = true; //  whether a document should be selected after it's contextmenu is triggered.
-    @observable _animateScaleTime: Opt<number>; // milliseconds for animating between views.  defaults to 300 if not uset
-    @observable _animateScalingTo = 0;
-    @observable _pendingDoubleClick = false;
+    private _cursorTimer: NodeJS.Timeout | undefined;
+    private _longPress = false;
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _downX: number = 0;
     private _downY: number = 0;
@@ -255,7 +254,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
     protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+
     @observable _componentView: Opt<DocComponentView>; // needs to be accessed from DocumentView wrapper class
+    @observable _animateScaleTime: Opt<number>; // milliseconds for animating between views.  defaults to 300 if not uset
+    @observable _animateScalingTo = 0;
+    @observable _pendingDoubleClick = false;
+    @observable _cursorPress = false;
 
     private get topMost() {
         return this.props.renderDepth === 0 && !LightboxView.LightboxDoc;
@@ -623,7 +627,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     SelectionManager.DeselectAll();
                     Doc.UnBrushDoc(this.props.Document);
                 }
-            } else if (this.onClickHandler?.script && !isScriptBox()) {
+            } else if (!this._longPress && this.onClickHandler?.script && !isScriptBox()) {
                 // bcz: hack? don't execute script if you're clicking on a scripting box itself
                 const { clientX, clientY, shiftKey, altKey } = e;
                 const func = () =>
@@ -652,12 +656,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         clickFunc();
                     }, 350);
                 } else clickFunc();
-            } else if (this.allLinks.length && this.Document.type !== DocumentType.LINK && !isScriptBox() && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
+            } else if (!this._longPress && this.allLinks.length && this.Document.type !== DocumentType.LINK && !isScriptBox() && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
                 SelectionManager.DeselectAll();
                 this.allLinks.length && LinkFollower.FollowLink(undefined, this.props.Document, this.props, e.altKey);
             } else {
                 if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) {
-                    // onDragStart implies a button doc that we don't want to select when clicking.   RootDocument & isTemplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
+                    // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
                     stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
                 } else {
                     runInAction(() => (this._pendingDoubleClick = true));
@@ -688,6 +692,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             }
             return;
         }
+        this._cursorTimer = setTimeout(
+            action(() => {
+                this._cursorPress = true;
+                this.props.select(false);
+            }),
+            1000 // long press required duration
+        );
         this._downX = e.clientX;
         this._downY = e.clientY;
         if ((Doc.ActiveTool === InkTool.None || this.props.addDocTab === returnFalse) && !(this.props.Document.rootDocument && !(e.ctrlKey || e.button > 0))) {
@@ -713,6 +724,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
     };
 
+    @action
     onPointerMove = (e: PointerEvent): void => {
         if (e.cancelBubble) return;
         if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) return;
@@ -722,6 +734,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
                     document.removeEventListener('pointermove', this.onPointerMove);
                     document.removeEventListener('pointerup', this.onPointerUp);
+                    this._cursorTimer && clearTimeout(this._cursorTimer);
+                    this._cursorPress = false;
                     this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'alias') || ((this.props.dropAction || this.Document.dropAction || undefined) as dropActionType));
                 }
             }
@@ -736,8 +750,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         document.removeEventListener('pointerup', this.onPointerUp);
     };
 
+    @action
     onPointerUp = (e: PointerEvent): void => {
         this.cleanupPointerEvents();
+        this._longPress = this._cursorPress;
+        this._cursorTimer && clearTimeout(this._cursorTimer);
+        this._cursorPress = false;
 
         if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
             this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log);
@@ -1395,7 +1413,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         ...style,
                         background: isButton || thumb ? undefined : this.backgroundColor,
                         opacity: this.opacity,
-                        cursor: Doc.ActiveTool === InkTool.None ? 'grab' : 'crosshair',
+                        cursor: this._cursorPress ? 'wait' : Doc.ActiveTool === InkTool.None ? 'grab' : 'crosshair',
                         color: StrCast(this.layoutDoc.color, 'inherit'),
                         fontFamily: StrCast(this.Document._fontFamily, 'inherit'),
                         fontSize: Cast(this.Document._fontSize, 'string', null),
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 097035661..b2fdd93fc 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -17,6 +17,7 @@ import { Transform } from '../../util/Transform';
 import { DocumentView, DocumentViewSharedProps, OpenWhere } from './DocumentView';
 import './LinkDocPreview.scss';
 import React = require('react');
+import { SelectionManager } from '../../util/SelectionManager';
 
 interface LinkDocPreviewProps {
     linkDoc?: Doc;
@@ -131,8 +132,9 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
             returnFalse,
             emptyFunction,
             action(() => {
-                LinkManager.currentLink = this._linkDoc === LinkManager.currentLink ? undefined : this._linkDoc;
+                LinkManager.currentLink = this._linkDoc;
                 LinkManager.currentLinkAnchor = this._linkSrc;
+                this.props.docProps.DocumentView?.().select(false);
                 if ((SettingsManager.propertiesWidth ?? 0) < 100) {
                     SettingsManager.propertiesWidth = 250;
                 }
-- 
cgit v1.2.3-70-g09d2


From b92b366cdf6b745848a3ecea9b68fc7d9edd676f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 8 Dec 2022 14:49:19 -0500
Subject: adjusting explore mode to work better within nested collections.

---
 src/client/views/StyleProvider.tsx        | 1 +
 src/client/views/linking/LinkMenuItem.tsx | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index b77f01250..ece224c68 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -279,6 +279,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             }
         }
         case StyleProp.PointerEvents:
+            if (MainView.Instance._exploreMode) return 'all';
             if (doc?.pointerEvents) return StrCast(doc.pointerEvents);
             if (props?.pointerEvents?.() === 'none') return 'none';
             const isInk = doc && StrCast(Doc.Layout(doc).layout).includes(InkingStroke.name);
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index e5a3200ca..91b63c1a6 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -12,13 +12,13 @@ import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from '../../util/DragManager';
 import { LinkFollower } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
+import { SelectionManager } from '../../util/SelectionManager';
 import { SettingsManager } from '../../util/SettingsManager';
 import { undoBatch } from '../../util/UndoManager';
 import { DocumentView } from '../nodes/DocumentView';
 import { LinkDocPreview } from '../nodes/LinkDocPreview';
 import './LinkMenuItem.scss';
 import React = require('react');
-import { SelectionManager } from '../../util/SelectionManager';
 
 interface LinkMenuItemProps {
     groupType: string;
-- 
cgit v1.2.3-70-g09d2

