From 495b60322b56d50f90398757c3a3c1f56da66cdc Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 11 Dec 2020 02:07:40 -0500
Subject: cleaning up more doumentViewProps.  reduced use of treeViewDoc prop. 
 added hideTitle.  fixed background shadow on isButton documents.

---
 src/client/views/nodes/LinkAnchorBox.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'src/client/views/nodes/LinkAnchorBox.tsx')

diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 7ce9abf27..b5b53501d 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -4,7 +4,7 @@ import { Doc, DocListCast } from "../../../fields/Doc";
 import { documentSchema } from "../../../fields/documentSchemas";
 import { makeInterface } from "../../../fields/Schema";
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
-import { Utils, setupMoveUpEvents, emptyFunction } from '../../../Utils';
+import { Utils, setupMoveUpEvents, emptyFunction, OmitKeys } from '../../../Utils';
 import { DocumentManager } from "../../util/DocumentManager";
 import { DragManager } from "../../util/DragManager";
 import { ViewBoxBaseComponent } from "../DocComponent";
@@ -117,7 +117,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
         const small = this.props.PanelWidth() <= 1; // this happens when rendered in a treeView
         const x = NumCast(this.rootDoc[this.fieldKey + "_x"], 100);
         const y = NumCast(this.rootDoc[this.fieldKey + "_y"], 100);
-        const c = StrCast(this.layoutDoc._backgroundColor, StrCast(this.layoutDoc.backgroundColor, StrCast(this.dataDoc.backgroundColor, "lightBlue"))); // note this is not where the typical lightBlue default color comes from.  See Documents.Create.LinkDocument()
+        const c = this.props.styleProvider?.(this.dataDoc, OmitKeys(this.props, ["LayoutTemplateString"]).omit, "backgroundColor");
         const anchor = this.fieldKey === "anchor1" ? "anchor2" : "anchor1";
         const anchorScale = !this.dataDoc[this.fieldKey + "-useLinkSmallAnchor"] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : .25;
 
-- 
cgit v1.2.3-70-g09d2


From 6346a318634f6d4ebe561a871905982d748bf43e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 11 Dec 2020 12:25:06 -0500
Subject: converted all style sheet properties to enums.

---
 src/client/views/MainView.tsx                      |  13 +-
 src/client/views/OverlayView.tsx                   |   3 +-
 src/client/views/PropertiesView.tsx                |   3 +-
 src/client/views/StyleProvider.scss                |  19 +++
 src/client/views/StyleProvider.tsx                 | 153 +++++++++++++++++++++
 .../views/collections/CollectionCarouselView.tsx   |   3 +-
 .../views/collections/CollectionStackingView.tsx   |   3 +-
 .../views/collections/CollectionTreeView.tsx       |   3 +-
 src/client/views/collections/CollectionView.tsx    |   3 +-
 src/client/views/collections/TabDocView.tsx        | 127 ++---------------
 src/client/views/collections/TreeView.tsx          |  17 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  23 ++--
 .../collections/collectionFreeForm/MarqueeView.tsx |   4 +-
 .../collectionGrid/CollectionGridView.tsx          |   2 -
 src/client/views/nodes/AudioBox.tsx                |   9 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  11 +-
 .../views/nodes/ContentFittingDocumentView.tsx     |  19 ---
 src/client/views/nodes/DocHolderBox.tsx            |   3 +-
 src/client/views/nodes/DocumentView.scss           |  19 ---
 src/client/views/nodes/DocumentView.tsx            |  39 +++---
 src/client/views/nodes/FontIconBox.tsx             |   3 +-
 src/client/views/nodes/LabelBox.tsx                |   3 +-
 src/client/views/nodes/LinkAnchorBox.tsx           |  10 +-
 src/client/views/nodes/LinkBox.tsx                 |   3 +-
 src/client/views/nodes/SliderBox.tsx               |   3 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   3 +-
 .../views/presentationview/PresElementBox.tsx      |   5 +-
 27 files changed, 280 insertions(+), 226 deletions(-)
 create mode 100644 src/client/views/StyleProvider.scss
 create mode 100644 src/client/views/StyleProvider.tsx

(limited to 'src/client/views/nodes/LinkAnchorBox.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 2705d2c34..2cb529317 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -59,6 +59,7 @@ import { TraceMobx } from '../../fields/util';
 import { SelectionManager } from '../util/SelectionManager';
 import { UndoManager } from '../util/UndoManager';
 import { TabDocView } from './collections/TabDocView';
+import { DefaultStyleProvider } from './StyleProvider';
 const _global = (window /* browser */ || global /* node */) as any;
 
 @observer
@@ -319,7 +320,7 @@ export class MainView extends React.Component {
                         PanelHeight={this.getContentsHeight}
                         renderDepth={0}
                         focus={emptyFunction}
-                        styleProvider={TabDocView.styleProvider}
+                        styleProvider={DefaultStyleProvider}
                         parentActive={returnTrue}
                         whenActiveChanged={emptyFunction}
                         bringToFront={emptyFunction}
@@ -352,7 +353,7 @@ export class MainView extends React.Component {
                 PanelHeight={this.getContentsHeight}
                 renderDepth={0}
                 focus={emptyFunction}
-                styleProvider={TabDocView.styleProvider}
+                styleProvider={DefaultStyleProvider}
                 parentActive={returnTrue}
                 whenActiveChanged={emptyFunction}
                 bringToFront={emptyFunction}
@@ -404,7 +405,7 @@ export class MainView extends React.Component {
                 <div className="mainView-propertiesDragger" onPointerDown={this.onPropertiesPointerDown} style={{ right: this.propertiesWidth() - 1 }}>
                     <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? "chevron-left" : "chevron-right"} color={this.darkScheme ? "white" : "black"} size="sm" />
                 </div>
-                {this.propertiesWidth() < 10 ? (null) : <PropertiesView styleProvider={TabDocView.styleProvider} width={this.propertiesWidth()} height={this.getContentsHeight()} />}
+                {this.propertiesWidth() < 10 ? (null) : <PropertiesView styleProvider={DefaultStyleProvider} width={this.propertiesWidth()} height={this.getContentsHeight()} />}
             </div>
         </>;
     }
@@ -457,7 +458,7 @@ export class MainView extends React.Component {
                     fieldKey={"data"}
                     dropAction={"alias"}
                     parentActive={returnFalse}
-                    styleProvider={TabDocView.styleProvider}
+                    styleProvider={DefaultStyleProvider}
                     rootSelected={returnTrue}
                     bringToFront={emptyFunction}
                     select={emptyFunction}
@@ -532,7 +533,7 @@ export class MainView extends React.Component {
                 pinToPres={emptyFunction}
                 rootSelected={returnTrue}
                 onClick={undefined}
-                styleProvider={TabDocView.styleProvider}
+                styleProvider={DefaultStyleProvider}
                 removeDocument={undefined}
                 ScreenToLocalTransform={Transform.Identity}
                 ContentScaling={returnOne}
@@ -595,7 +596,7 @@ export class MainView extends React.Component {
             <CollectionMenu />
             {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
             {DocumentLinksButton.EditLink ? <LinkMenu docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (null)}
-            {LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} styleProvider={TabDocView.styleProvider}
+            {LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} styleProvider={DefaultStyleProvider}
                 linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href}
                 addDocTab={LinkDocPreview.LinkInfo.addDocTab} /> : (null)}
             <GestureOverlay >
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index b7bd88344..124da6016 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -16,6 +16,7 @@ import { DragManager } from "../util/DragManager";
 import { List } from "../../fields/List";
 import { CurrentUserUtils } from "../util/CurrentUserUtils";
 import { TabDocView } from "./collections/TabDocView";
+import { DefaultStyleProvider } from "./StyleProvider";
 
 export type OverlayDisposer = () => void;
 
@@ -195,7 +196,7 @@ export class OverlayView extends React.Component {
                     parentActive={returnTrue}
                     whenActiveChanged={emptyFunction}
                     focus={emptyFunction}
-                    styleProvider={TabDocView.styleProvider}
+                    styleProvider={DefaultStyleProvider}
                     addDocTab={returnFalse}
                     pinToPres={emptyFunction}
                     docFilters={returnEmptyFilter}
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 03e0a35ec..eb7df0248 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -29,6 +29,7 @@ import { PropertiesDocContextSelector } from "./PropertiesDocContextSelector";
 import "./PropertiesView.scss";
 import { CollectionViewType } from "./collections/CollectionView";
 import { DocumentViewProps, StyleProviderFunc } from "./nodes/DocumentView";
+import { DefaultStyleProvider } from "./StyleProvider";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -269,7 +270,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                     DataDoc={this.dataDoc}
                     renderDepth={1}
                     rootSelected={returnFalse}
-                    styleProvider={this.props.styleProvider}
+                    styleProvider={DefaultStyleProvider}
                     fitToBox={true}
                     freezeDimensions={true}
                     dontCenter={"y"}
diff --git a/src/client/views/StyleProvider.scss b/src/client/views/StyleProvider.scss
new file mode 100644
index 000000000..df63288f1
--- /dev/null
+++ b/src/client/views/StyleProvider.scss
@@ -0,0 +1,19 @@
+.styleProvider-lock {
+    font-size: 12px;
+    width: 20; 
+    height: 20; 
+    position: absolute; 
+    right: -5; 
+    top: -5;
+    background: transparent;
+    pointer-events: all;
+    opacity: 0.3;
+    display: flex;
+    color: gold;
+    border-radius: 3px;
+    justify-content: center;
+    cursor: default;
+}
+.styleProvider-lock:hover {
+    opacity:1;
+}
\ No newline at end of file
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
new file mode 100644
index 000000000..0fe6df84c
--- /dev/null
+++ b/src/client/views/StyleProvider.tsx
@@ -0,0 +1,153 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import 'golden-layout/src/css/goldenlayout-base.css';
+import 'golden-layout/src/css/goldenlayout-dark-theme.css';
+import { runInAction } from 'mobx';
+import { Doc, Opt, StrListCast } from "../../fields/Doc";
+import { List } from '../../fields/List';
+import { BoolCast, Cast, StrCast } from "../../fields/Types";
+import { DocumentType } from '../documents/DocumentTypes';
+import { CurrentUserUtils } from '../util/CurrentUserUtils';
+import { SnappingManager } from '../util/SnappingManager';
+import { UndoManager } from '../util/UndoManager';
+import { CollectionViewType } from './collections/CollectionView';
+import { DocumentViewProps } from "./nodes/DocumentView";
+import "./StyleProvider.scss";
+import React = require("react");
+import Color = require('color');
+import { listSpec } from '../../fields/Schema';
+
+export enum StyleLayers {
+    Background = "background"
+}
+
+export enum StyleProp {
+    DocContents = "docContents",
+    Opacity = "opacity",
+    Hidden = "hidden",
+    BoxShadow = "boxShadow",
+    BorderRounding = "borderRounding",
+    BackgroundColor = "backgroundColor",
+    WidgetColor = "widgetColor",
+    LinkBackgroundColor = "linkBackgroundColor",
+    HideLinkButton = "hideLinkButton",
+    LinkSource = "linkSource",
+    PointerEvents = "pointerEvents",
+    Decorations = "decorations",
+}
+
+function darkScheme() { return BoolCast(CurrentUserUtils.ActiveDashboard?.darkScheme); }
+
+function toggleBackground(doc: Doc) {
+    UndoManager.RunInBatch(() => runInAction(() => {
+        const layers = StrListCast(doc.layers);
+        if (!layers.includes(StyleLayers.Background)) {
+            if (!layers.length) doc.layers = new List<string>([StyleLayers.Background]);
+            else layers.push(StyleLayers.Background);
+        }
+        else layers.splice(layers.indexOf(StyleLayers.Background), 1);
+        doc._overflow = !layers.includes(StyleLayers.Background) ? "visible" : undefined;
+        if (!layers.includes(StyleLayers.Background)) {
+            //this.props.bringToFront(doc, true);
+            // const wid = this.Document[WidthSym]();    // change the nativewidth and height if the background is to be a collection that aggregates stuff that is added to it.
+            // const hgt = this.Document[HeightSym]();
+            // Doc.SetNativeWidth(this.props.Document[DataSym], wid);
+            // Doc.SetNativeHeight(this.props.Document[DataSym], hgt);
+        }
+    }), "toggleBackground");
+}
+
+//
+// a preliminary implementation of a dash style sheet for setting rendering properties of documents nested within a Tab
+// 
+export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any {
+    switch (property.split(":")[0]) {
+        case StyleProp.DocContents: return undefined;
+        case StyleProp.WidgetColor: return darkScheme() ? "lightgrey" : "dimgrey";
+        case StyleProp.Opacity: return Cast(doc?._opacity, "number", Cast(doc?.opacity, "number", null));
+        case StyleProp.Hidden: return BoolCast(doc?._hidden, BoolCast(doc?.hidden));
+        case StyleProp.BorderRounding: return !doc ? undefined : StrCast(doc._borderRounding, StrCast(doc.borderRounding));
+        case StyleProp.BackgroundColor: {
+            if (Doc.UserDoc().renderStyle === "comic") return undefined;
+            let docColor: Opt<string> = StrCast(doc?._backgroundColor, StrCast(doc?.backgroundColor));
+            if (!docColor) {
+                switch (doc?.type) {
+                    case DocumentType.PRESELEMENT: docColor = darkScheme() ? "" : ""; break;
+                    case DocumentType.PRES: docColor = darkScheme() ? "#3e3e3e" : "white"; break;
+                    case DocumentType.FONTICON: docColor = "black"; break;
+                    case DocumentType.RTF: docColor = darkScheme() ? "#2d2d2d" : "#f1efeb"; break;
+                    case DocumentType.LABEL:
+                    case DocumentType.BUTTON: docColor = darkScheme() ? "#2d2d2d" : "lightgray"; break;
+                    case DocumentType.LINK:
+                    case DocumentType.COL:
+                        docColor = Doc.IsSystem(doc) ? (darkScheme() ? "rgb(62,62,62)" : "lightgrey") :
+                            StrListCast(doc.layers).includes(StyleLayers.Background) ? "cyan" :
+                                doc.annotationOn ? "#00000015" :
+                                    StrCast((props?.renderDepth || 0) > 0 ?
+                                        Doc.UserDoc().activeCollectionNestedBackground :
+                                        Doc.UserDoc().activeCollectionBackground);
+                        break;
+                    //if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)";
+                    default: docColor = darkScheme() ? "black" : "white"; break;
+                }
+            }
+            if (docColor && (!doc || props?.layerProvider?.(doc) === false)) docColor = Color(docColor.toLowerCase()).fade(0.5).toString();
+            return docColor;
+        }
+        case StyleProp.BoxShadow: {
+            if (!doc || props?.styleProvider?.(doc, props, StyleProp.Opacity) === 0) return undefined;  // if it's not visible, then no shadow)
+            const isBackground = StrListCast(doc.layers).includes(StyleLayers.Background);
+            switch (doc?.type) {
+                case DocumentType.COL: return isBackground ? undefined :
+                    `${darkScheme() ? "rgb(30, 32, 31) " : "#9c9396 "} ${StrCast(doc.boxShadow, "0.2vw 0.2vw 0.8vw")}`;
+                default:
+                    return doc.z ? `#9c9396  ${StrCast(doc?.boxShadow, "10px 10px 0.9vw")}` :  // if it's a floating doc, give it a big shadow
+                        props?.backgroundHalo?.(doc) && doc.type !== DocumentType.INK ? (`${props?.styleProvider?.(doc, props, StyleProp.BackgroundColor)} ${StrCast(doc.boxShadow, `0vw 0vw ${(isBackground ? 100 : 50) / props.ContentScaling()}px`)}`) :  // if it's just in a cluster, make the shadown roughly match the cluster border extent
+                            isBackground ? undefined :  // if it's a background & has a cluster color, make the shadow spread really big
+                                StrCast(doc.boxShadow, "");
+            }
+        }
+        case StyleProp.PointerEvents:
+            const layer = doc && props?.layerProvider?.(doc);
+            if (props?.styleProvider?.(doc, props, StyleProp.Opacity) === 0 || doc?.type === DocumentType.INK || doc?.isInkMask) return "none";
+            if (layer === false && !property.includes(":selected") && !SnappingManager.GetIsDragging()) return "none";
+            if (doc?.type !== DocumentType.INK && layer === true) return "all";
+            return undefined;
+        case StyleProp.Decorations:
+            if (props?.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform) {
+                const isBackground = StrListCast(doc?.layers).includes(StyleLayers.Background);
+                return doc && (isBackground || property.includes(":selected")) && (props?.renderDepth || 0) > 0 &&
+                    ((doc.type === DocumentType.COL && doc._viewType !== CollectionViewType.Pile) || [DocumentType.RTF, DocumentType.IMG, DocumentType.INK].includes(doc.type as DocumentType)) ?
+                    <div className="styleProvider-lock" onClick={() => toggleBackground(doc)}>
+                        <FontAwesomeIcon icon={isBackground ? "unlock" : "lock"} style={{ color: isBackground ? "red" : undefined }} size="lg" />
+                    </div>
+                    : (null);
+            }
+    }
+}
+
+//
+// a preliminary semantic-"layering/grouping" mechanism for determining interactive properties of documents
+//  currently, the provider tests whether the docuemnt's layer field matches the activeLayer field of the tab.
+//     if it matches, then the document gets pointer events, otherwise it does not.
+//
+export function DefaultLayerProvider(thisDoc: Doc) {
+    return (doc: Doc, assign?: boolean) => {
+        if (doc.z) return true;
+        if (assign) {
+            const activeLayer = StrCast(thisDoc?.activeLayer);
+            if (activeLayer) {
+                const layers = Cast(doc.layers, listSpec("string"), []);
+                if (layers.length && !layers.includes(activeLayer)) layers.push(activeLayer);
+                else if (!layers.length) doc.layers = new List<string>([activeLayer]);
+                if (activeLayer === "red" || activeLayer === "green" || activeLayer === "blue") doc._backgroundColor = activeLayer;
+            }
+            return true;
+        } else {
+            if (Doc.AreProtosEqual(doc, thisDoc)) return true;
+            const layers = Cast(doc.layers, listSpec("string"), []);
+            if (!layers.length && !thisDoc?.activeLayer) return true;
+            if (layers.includes(StrCast(thisDoc?.activeLayer))) return true;
+            return false;
+        }
+    };
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 8b7a46e2f..26c36507a 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -15,6 +15,7 @@ import { ContextMenu } from '../ContextMenu';
 import { ObjectField } from '../../../fields/ObjectField';
 import { returnFalse, returnZero, OmitKeys } from '../../../Utils';
 import { ScriptField } from '../../../fields/ScriptField';
+import { StyleProp } from '../StyleProvider';
 
 type CarouselDocument = makeInterface<[typeof documentSchema, typeof collectionSchema]>;
 const CarouselDocument = makeInterface(documentSchema, collectionSchema);
@@ -65,7 +66,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
                 </div>
                 <div className="collectionCarouselView-caption" key="caption"
                     style={{
-                        background: StrCast(this.layoutDoc._captionBackgroundColor, this.props.styleProvider?.(this.props.Document, this.props, "backgroundColor")),
+                        background: StrCast(this.layoutDoc._captionBackgroundColor, this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BackgroundColor)),
                         color: StrCast(this.layoutDoc._captionColor, StrCast(this.layoutDoc.color)),
                         borderRadius: StrCast(this.layoutDoc._captionBorderRounding),
                     }}>
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 8dfb7cefe..2896d244e 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -28,6 +28,7 @@ import "./CollectionStackingView.scss";
 import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
 import { CollectionSubView } from "./CollectionSubView";
 import { CollectionViewType } from "./CollectionView";
+import { StyleProp } from "../StyleProvider";
 const _global = (window /* browser */ || global /* node */) as any;
 
 type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>;
@@ -188,7 +189,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
         const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc;
         const height = () => this.getDocHeight(doc);
         const styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
-            if (property === "opacity" && doc) {
+            if (property === StyleProp.Opacity && doc) {
                 if (this.props.childOpacity) {
                     return this.props.childOpacity();
                 }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 89ed3a147..4e521956d 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -24,6 +24,7 @@ import { CollectionSubView } from "./CollectionSubView";
 import "./CollectionTreeView.scss";
 import { TreeView } from "./TreeView";
 import React = require("react");
+import { StyleProp } from '../StyleProvider';
 
 export type collectionTreeViewProps = {
     treeViewHideTitle?: boolean;
@@ -211,7 +212,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
     render() {
         TraceMobx();
         if (!(this.doc instanceof Doc)) return (null);
-        const background = this.props.styleProvider?.(this.doc, this.props, "backgroundColor");
+        const background = this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor);
         const paddingX = `${NumCast(this.doc._xPadding, 10)}px`;
         const paddingTop = `${NumCast(this.doc._yPadding, 20)}px`;
         const pointerEvents = !this.props.active() && !SnappingManager.GetIsDragging() && !this._isChildActive ? "none" : undefined;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index dbbb3e991..04edc3d09 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -39,6 +39,7 @@ import { CollectionTimeView } from './CollectionTimeView';
 import { CollectionTreeView } from "./CollectionTreeView";
 import './CollectionView.scss';
 import { ScriptField } from '../../../fields/ScriptField';
+import { StyleProp } from '../StyleProvider';
 export const COLLECTION_BORDER_WIDTH = 2;
 const path = require('path');
 
@@ -385,7 +386,7 @@ export class CollectionView extends Touchable<CollectionViewProps> {
             CollectionView: this,
         };
         const boxShadow = Doc.UserDoc().renderStyle === "comic" || this.props.Document.treeViewOutlineMode || this.collectionViewType === CollectionViewType.Linear ? undefined :
-            this.props.styleProvider?.(this.props.Document, this.props, "boxShadow");
+            this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BoxShadow);
         return (<div className={"collectionView"} onContextMenu={this.onContextMenu}
             style={{ pointerEvents: this.props.layerProvider?.(this.props.Document) === false ? "none" : undefined, boxShadow }}>
             {this.showIsTagged()}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index ba8b5e418..8b5143013 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -32,6 +32,7 @@ import React = require("react");
 import { List } from '../../../fields/List';
 import { DocumentType } from '../../documents/DocumentTypes';
 import Color = require('color');
+import { StyleProp, DefaultStyleProvider, DefaultLayerProvider, StyleLayers } from '../StyleProvider';
 const _global = (window /* browser */ || global /* node */) as any;
 
 interface TabDocViewProps {
@@ -84,7 +85,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 toggle.style.borderLeft = "solid 1px black";
                 toggle.onclick = (e: MouseEvent) => {
                     if (tab.contentItem === tab.header.parent.getActiveContentItem()) {
-                        tab.DashDoc.activeLayer = tab.DashDoc.activeLayer ? undefined : "background";
+                        tab.DashDoc.activeLayer = tab.DashDoc.activeLayer ? undefined : StyleLayers.Background;
                     }
                 };
                 tab.element[0].style.borderTopRightRadius = "8px";
@@ -279,7 +280,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     @computed get previewPanelCenteringOffset() { return this.nativeWidth() ? (this._panelWidth - this.nativeWidth() * this.ContentScaling()) / 2 : 0; }
     @computed get widthpercent() { return this.nativeWidth() ? `${(this.nativeWidth() * this.ContentScaling()) / this._panelWidth * 100}% ` : undefined; }
     @computed get layoutDoc() { return this._document && Doc.Layout(this._document); }
-    @computed static get darkScheme() { return BoolCast(CurrentUserUtils.ActiveDashboard?.darkScheme); }
 
     // adds a tab to the layout based on the locaiton parameter which can be:
     //  close[:{left,right,top,bottom}]  - e.g., "close" will close the tab, "close:left" will close the left tab, 
@@ -303,7 +303,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
         }
     }
 
-    @computed get tabColor() { return StrCast(this._document?._backgroundColor, StrCast(this._document?.backgroundColor, TabDocView.styleProvider(this._document, undefined, "backgroundColor"))); }
+    @computed get tabColor() { return StrCast(this._document?._backgroundColor, StrCast(this._document?.backgroundColor, DefaultStyleProvider(this._document, undefined, StyleProp.BackgroundColor))); }
     @computed get renderBounds() {
         const bounds = this._document ? Cast(this._document._renderContentBounds, listSpec("number"), [0, 0, this.returnMiniSize(), this.returnMiniSize()]) : [0, 0, 0, 0];
         const xbounds = bounds[2] - bounds[0];
@@ -374,7 +374,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
 
             <Tooltip title={<div className="dash-tooltip">{"toggle minimap"}</div>}>
                 <div className="miniMap-hidden" onPointerDown={e => e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this._document!.hideMinimap = !this._document!.hideMinimap; })}
-                    style={{ background: TabDocView.styleProvider(this._document, undefined, "backgroundColor") }} >
+                    style={{ background: DefaultStyleProvider(this._document, undefined, StyleProp.BackgroundColor) }} >
                     <FontAwesomeIcon icon={"globe-asia"} size="lg" />
                 </div>
             </Tooltip>
@@ -388,123 +388,22 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     }
     active = () => this._isActive;
 
-    //
-    // a preliminary semantic-"layering/grouping" mechanism for determining interactive properties of documents
-    //  currently, the provider tests whether the docuemnt's layer field matches the activeLayer field of the tab.
-    //     if it matches, then the document gets pointer events, otherwise it does not.
-    //
-    layerProvider = (doc: Doc, assign?: boolean) => {
-        if (doc.z) return true;
-        if (assign) {
-            const activeLayer = StrCast(this._document?.activeLayer);
-            if (activeLayer) {
-                const layers = Cast(doc.layers, listSpec("string"), []);
-                if (layers.length && !layers.includes(activeLayer)) layers.push(activeLayer);
-                else if (!layers.length) doc.layers = new List<string>([activeLayer]);
-                if (activeLayer === "red" || activeLayer === "green" || activeLayer === "blue") doc._backgroundColor = activeLayer;
-            }
-            return true;
-        } else {
-            if (Doc.AreProtosEqual(doc, this._document)) return true;
-            const layers = Cast(doc.layers, listSpec("string"), []);
-            if (!layers.length && !this._document?.activeLayer) return true;
-            if (layers.includes(StrCast(this._document?.activeLayer))) return true;
-            return false;
-        }
-    }
 
-    static toggleBackground = undoBatch(action((doc: Doc) => {
-        const layers = StrListCast(doc.layers);
-        if (!layers.includes("background")) {
-            if (!layers.length) doc.layers = new List<string>(["background"]);
-            else layers.push("background");
-        }
-        else layers.splice(layers.indexOf("background"), 1);
-        doc._overflow = !layers.includes("background") ? "visible" : undefined;
-        if (!layers.includes("background")) {
-            //this.props.bringToFront(doc, true);
-            // const wid = this.Document[WidthSym]();    // change the nativewidth and height if the background is to be a collection that aggregates stuff that is added to it.
-            // const hgt = this.Document[HeightSym]();
-            // Doc.SetNativeWidth(this.props.Document[DataSym], wid);
-            // Doc.SetNativeHeight(this.props.Document[DataSym], hgt);
-        }
-    }));
-    //
-    // a preliminary implementation of a dash style sheet for setting rendering properties of documents nested within a Tab
-    // 
-    public static styleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
-        switch (property.split(":")[0]) {
-            case "docContents": return undefined;
-            case "widgetColor": return TabDocView.darkScheme ? "lightgrey" : "dimgrey";
-            case "opacity": return Cast(doc?._opacity, "number", Cast(doc?.opacity, "number", null));
-            case "hidden": return BoolCast(doc?._hidden, BoolCast(doc?.hidden));
-            case "borderRounding": return !doc ? undefined : StrCast(doc._borderRounding, StrCast(doc.borderRounding));
-            case "backgroundColor": {
-                if (Doc.UserDoc().renderStyle === "comic") return undefined;
-                let docColor: Opt<string> = StrCast(doc?._backgroundColor, StrCast(doc?.backgroundColor));
-                if (!docColor) {
-                    switch (doc?.type) {
-                        case DocumentType.PRESELEMENT: docColor = TabDocView.darkScheme ? "" : ""; break;
-                        case DocumentType.PRES: docColor = TabDocView.darkScheme ? "#3e3e3e" : "white"; break;
-                        case DocumentType.FONTICON: docColor = "black"; break;
-                        case DocumentType.RTF: docColor = TabDocView.darkScheme ? "#2d2d2d" : "#f1efeb"; break;
-                        case DocumentType.LABEL:
-                        case DocumentType.BUTTON: docColor = TabDocView.darkScheme ? "#2d2d2d" : "lightgray"; break;
-                        case DocumentType.LINK:
-                        case DocumentType.COL:
-                            docColor = Doc.IsSystem(doc) ? (TabDocView.darkScheme ? "rgb(62,62,62)" : "lightgrey") :
-                                StrCast((props?.renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground);
-                            break;
-                        //if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)";
-                        default: docColor = TabDocView.darkScheme ? "black" : "white"; break;
-                    }
-                }
-                if (docColor && (!doc || props?.layerProvider?.(doc) === false)) docColor = Color(docColor.toLowerCase()).fade(0.5).toString();
-                return docColor;
-            }
-            case "boxShadow": {
-                if (!doc || props?.styleProvider?.(doc, props, "opacity") === 0) return undefined;  // if it's not visible, then no shadow)
-                const isBackground = StrListCast(doc.layers).includes("background");
-                switch (doc?.type) {
-                    case DocumentType.COL: return isBackground ? undefined :
-                        `${TabDocView.darkScheme ? "rgb(30, 32, 31) " : "#9c9396 "} ${StrCast(doc.boxShadow, "0.2vw 0.2vw 0.8vw")}`;
-                    default:
-                        return doc.z ? `#9c9396  ${StrCast(doc?.boxShadow, "10px 10px 0.9vw")}` :  // if it's a floating doc, give it a big shadow
-                            props?.backgroundHalo?.(doc) && doc.type !== DocumentType.INK ? (`${props?.styleProvider?.(doc, props, "backgroundColor")} ${StrCast(doc.boxShadow, `0vw 0vw ${(isBackground ? 100 : 50) / props.ContentScaling()}px`)}`) :  // if it's just in a cluster, make the shadown roughly match the cluster border extent
-                                isBackground ? undefined :  // if it's a background & has a cluster color, make the shadow spread really big
-                                    StrCast(doc.boxShadow, "");
-                }
-            }
-            case "pointerEvents":
-                const layer = doc && props?.layerProvider?.(doc);
-                if (props?.styleProvider?.(doc, props, "opacity") === 0 || doc?.type === DocumentType.INK || doc?.isInkMask) return "none";
-                if (layer === false && !property.includes(":selected") && !SnappingManager.GetIsDragging()) return "none";
-                if (doc?.type !== DocumentType.INK && layer === true) return "all";
-                return undefined;
-            case "decorations":
-                if (props?.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform) {
-                    const isBackground = StrListCast(doc?.layers).includes("background");
-                    return doc && (isBackground || property.includes(":selected")) && (props?.renderDepth || 0) > 0 &&
-                        ((doc.type === DocumentType.COL && doc._viewType !== CollectionViewType.Pile) || [DocumentType.RTF, DocumentType.IMG, DocumentType.INK].includes(doc.type as DocumentType)) ?
-                        <div className="documentView-lock" onClick={() => TabDocView.toggleBackground(doc)}>
-                            <FontAwesomeIcon icon={isBackground ? "unlock" : "lock"} style={{ color: isBackground ? "red" : undefined }} size="lg" />
-                        </div>
-                        : (null);
-                }
-        }
-    }
     public static miniStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
         if (doc) {
-            switch (property) {
-                case "docContents":
+            switch (property.split(":")[0]) {
+                default: return DefaultStyleProvider(doc, props, property);
+                case StyleProp.PointerEvents: return "none";
+                case StyleProp.DocContents:
                     const background = doc.type === DocumentType.PDF ? "red" : doc.type === DocumentType.IMG ? "blue" : doc.type === DocumentType.RTF ? "orange" :
                         doc.type === DocumentType.VID ? "purple" : doc.type === DocumentType.WEB ? "yellow" : "gray";
-                    return doc.type === DocumentType.COL ? undefined : <div style={{ width: doc[WidthSym](), height: doc[HeightSym](), position: "absolute", display: "block", background }} />;
-                default:
-                    return (property.startsWith("pointerEvents")) ? "none" : TabDocView.styleProvider(doc, props, property);
+                    return doc.type === DocumentType.COL ?
+                        undefined :
+                        <div style={{ width: doc[WidthSym](), height: doc[HeightSym](), position: "absolute", display: "block", background }} />;
             }
         }
     }
+    @computed get layerProvider() { return this._document && DefaultLayerProvider(this._document); }
     @computed get docView() {
         TraceMobx();
         return !this._activated || !this._document || this._document._viewType === CollectionViewType.Docking ? (null) :
@@ -526,7 +425,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 parentActive={this.active}
                 whenActiveChanged={emptyFunction}
                 focus={this.focusFunc}
-                styleProvider={TabDocView.styleProvider}
+                styleProvider={DefaultStyleProvider}
                 addDocTab={this.addDocTab}
                 pinToPres={TabDocView.PinDoc}
                 docFilters={CollectionDockingView.Instance.docFilters}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 53cdca2af..28503a23f 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -29,6 +29,7 @@ import { CollectionTreeView } from './CollectionTreeView';
 import { CollectionView, CollectionViewType } from './CollectionView';
 import "./TreeView.scss";
 import React = require("react");
+import { StyleProp } from '../StyleProvider';
 
 export interface TreeViewProps {
     document: Doc;
@@ -441,7 +442,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         return <div className={`bullet${this.outlineMode ? "-outline" : ""}`} key={"bullet"}
             title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : "view fields"}
             onClick={this.bulletClick}
-            style={this.outlineMode ? { opacity: this.titleStyleProvider?.(this.doc, this.props.treeView.props, "opacity") } : {
+            style={this.outlineMode ? { opacity: this.titleStyleProvider?.(this.doc, this.props.treeView.props, StyleProp.Opacity) } : {
                 color: StrCast(this.doc.color, checked === "unchecked" ? "white" : "inherit"),
                 opacity: checked === "unchecked" ? undefined : 0.4
             }}>
@@ -499,22 +500,22 @@ export class TreeView extends React.Component<TreeViewProps> {
     titleStyleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => {
         if (!doc || doc !== this.doc) return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
 
-        switch (property) {
-            case "opacity": return this.outlineMode ? undefined : 1;
-            case "backgroundColor": return StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
-            case "docContents": return !props?.treeViewDoc ? (null) :
+        switch (property.split(":")[0]) {
+            case StyleProp.Opacity: return this.outlineMode ? undefined : 1;
+            case StyleProp.BackgroundColor: return StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
+            case StyleProp.DocContents: return !props?.treeViewDoc ? (null) :
                 <div className="treeView-label" style={{    // just render a title for a tree view label (identified by treeViewDoc being set in 'props')
                     maxWidth: props?.PanelWidth() || undefined,
                     position: props?.relative ? "relative" : undefined,
-                    background: props?.styleProvider?.(doc, props, "backgroundColor"),
+                    background: props?.styleProvider?.(doc, props, StyleProp.BackgroundColor),
                 }}>
                     {StrCast(doc?.title)}
                 </div>;
-            default: if (property.startsWith("decorations")) return (null);
+            case StyleProp.Decorations: return (null);
         }
     }
     embeddedStyleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => {
-        if (property.startsWith("decorations")) return (null);
+        if (property.startsWith(StyleProp.Decorations)) return (null);
         return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
     }
     onKeyDown = (e: React.KeyboardEvent) => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 75411b3c5..cfc5e8b61 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -47,6 +47,7 @@ import { MarqueeOptionsMenu } from "./MarqueeOptionsMenu";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
+import { StyleProp, StyleLayers } from "../../StyleProvider";
 
 export const panZoomSchema = createSchema({
     _panX: "number",
@@ -246,7 +247,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             const nd = [Doc.NativeWidth(layoutDoc), Doc.NativeHeight(layoutDoc)];
             layoutDoc._width = NumCast(layoutDoc._width, 300);
             layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? nd[1] / nd[0] * NumCast(layoutDoc._width) : 300);
-            !Cast(d, listSpec("string"), []).includes("background") && (d._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront
+            !StrListCast(d.layers).includes(StyleLayers.Background) && (d._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront
         }
 
         (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments);
@@ -404,8 +405,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     }
 
     getClusterColor = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => {
-        let clusterColor = this.props.styleProvider?.(doc, props, property);  // bcz: check 'props'  used to be renderDepth + 1
-        if (property !== "backgroundColor") return clusterColor;
+        let styleProp = this.props.styleProvider?.(doc, props, property);  // bcz: check 'props'  used to be renderDepth + 1
+        if (property !== StyleProp.BackgroundColor) return styleProp;
         const cluster = NumCast(doc?.cluster);
         if (this.Document._useClusters) {
             if (this._clusterSets.length <= cluster) {
@@ -413,14 +414,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             } else {
                 // choose a cluster color from a palette
                 const colors = ["#da42429e", "#31ea318c", "rgba(197, 87, 20, 0.55)", "#4a7ae2c4", "rgba(216, 9, 255, 0.5)", "#ff7601", "#1dffff", "yellow", "rgba(27, 130, 49, 0.55)", "rgba(0, 0, 0, 0.268)"];
-                clusterColor = colors[cluster % colors.length];
+                styleProp = colors[cluster % colors.length];
                 const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor);
                 // override the cluster color with an explicitly set color on a non-background document.  then override that with an explicitly set color on a background document
-                set && set.filter(s => !Cast(s.layers, listSpec("string"), []).includes("background")).map(s => clusterColor = StrCast(s.backgroundColor));
-                set && set.filter(s => Cast(s.layers, listSpec("string"), []).includes("background")).map(s => clusterColor = StrCast(s.backgroundColor));
+                set && set.filter(s => !StrListCast(s.layers).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor));
+                set && set.filter(s => StrListCast(s.layers).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor));
             }
-        } else if (doc && NumCast(doc.group, -1) !== -1) clusterColor = "gray";
-        return clusterColor;
+        } else if (doc && NumCast(doc.group, -1) !== -1) styleProp = "gray";
+        return styleProp;
     }
 
     @action
@@ -870,7 +871,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     }
 
     bringToFront = action((doc: Doc, sendToBack?: boolean) => {
-        if (sendToBack || Cast(doc.layers, listSpec("string"), []).includes("background")) {
+        if (sendToBack || StrListCast(doc.layers).includes(StyleLayers.Background)) {
             doc.zIndex = 0;
         } else if (doc.isInkMask) {
             doc.zIndex = 5000;
@@ -1053,7 +1054,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
         const layoutDoc = Doc.Layout(params.pair.layout);
         const { x, y, opacity } = this.Document._currentFrame === undefined ?
-            { x: params.pair.layout.x, y: params.pair.layout.y, opacity: this.props.styleProvider?.(params.pair.layout, this.props, "opacity") } :
+            { x: params.pair.layout.x, y: params.pair.layout.y, opacity: this.props.styleProvider?.(params.pair.layout, this.props, StyleProp.Opacity) } :
             CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document._currentFrame || 0);
         const { z, color, zIndex } = params.pair.layout;
         return {
@@ -1365,7 +1366,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const isDocInView = (doc: Doc, rect: { left: number, top: number, width: number, height: number }) => intersectRect(docDims(doc), rect);
 
         const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) };
-        let snappableDocs = activeDocs.filter(doc => !StrListCast(doc.layers).includes("background") && doc.z === undefined && isDocInView(doc, selRect));  // first see if there are any foreground docs to snap to
+        let snappableDocs = activeDocs.filter(doc => !StrListCast(doc.layers).includes(StyleLayers.Background) && doc.z === undefined && isDocInView(doc, selRect));  // first see if there are any foreground docs to snap to
         !snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect))); // if not, see if there are background docs to snap to
         !snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z !== undefined && isDocInView(doc, otherBounds))); // if not, then why not snap to floating docs
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 52310d628..f1f190bff 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -27,6 +27,7 @@ import { CollectionView } from "../CollectionView";
 import { MarqueeOptionsMenu } from "./MarqueeOptionsMenu";
 import "./MarqueeView.scss";
 import React = require("react");
+import { StyleLayers } from "../../StyleProvider";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -349,7 +350,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
         newCollection.system = undefined;
         newCollection.layers = new List<string>(layers);
-        newCollection.backgroundColor = this.props.isAnnotationOverlay ? "#00000015" : layers.includes("background") ? "cyan" : undefined;
         newCollection._width = this.Bounds.width;
         newCollection._height = this.Bounds.height;
         newCollection.x = this.Bounds.left;
@@ -523,7 +523,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
 
     @action
     background = (e: KeyboardEvent | React.PointerEvent | undefined) => {
-        const newCollection = this.getCollection([], undefined, ["background"]);
+        const newCollection = this.getCollection([], undefined, [StyleLayers.Background]);
         this.props.addDocument?.(newCollection);
         MarqueeOptionsMenu.Instance.fadeOut(true);
         this.hideMarquee();
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index bef197bee..1a4eb8b7b 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -165,8 +165,6 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
             {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
             Document={layout}
             DataDoc={layout.resolvedDataDoc as Doc}
-            styleProvider={this.props.styleProvider}
-            ContainingCollectionDoc={this.props.Document}
             PanelWidth={width}
             PanelHeight={height}
             ContentScaling={returnOne}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index c89e21312..b495cdd1b 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -29,6 +29,7 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { FormattedTextBoxComment } from "./formattedText/FormattedTextBoxComment";
 import { LinkAnchorBox } from "./LinkAnchorBox";
 import { LinkDocPreview } from "./LinkDocPreview";
+import { StyleProp } from "../StyleProvider";
 
 declare class MediaRecorder {
     // whatever MediaRecorder has
@@ -538,7 +539,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
 
     rangeScript = () => AudioBox.RangeScript;
     labelScript = () => AudioBox.LabelScript;
-
+    static audioStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
+        if (property === StyleProp.BackgroundColor) return "transparent";
+        if (property === StyleProp.PointerEvents) return "none";
+    }
     render() {
         const interactive = SnappingManager.GetIsDragging() || this.active() ? "-interactive" : "";
         this._first = true;  // for indicating the first marker that is rendered
@@ -637,8 +641,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
                                             parentActive={returnTrue}
                                             bringToFront={emptyFunction}
                                             ContentScaling={returnOne}
-                                            styleProvider={(doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => property === "backgroundColor" ? "transparent" : undefined}
-                                            pointerEvents={"none"}
+                                            styleProvider={AudioBox.audioStyleProvider}
                                             LayoutTemplate={undefined}
                                             LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(l, la2)}`)}
                                         />
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 46c311350..1b47f4551 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -16,6 +16,7 @@ import "./CollectionFreeFormDocumentView.scss";
 import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
 import { DocumentView, DocumentViewProps } from "./DocumentView";
 import React = require("react");
+import { StyleProp } from "../StyleProvider";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined;
@@ -59,7 +60,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     @computed get nativeHeight() { return returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, undefined, this.freezeDimensions)); }
 
     styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
-        if (property === "opacity" && doc === this.layoutDoc) return this.Opacity; // only change the opacity for this specific document, not its children
+        if (property === StyleProp.Opacity && doc === this.layoutDoc) return this.Opacity; // only change the opacity for this specific document, not its children
         return this.props.styleProvider?.(doc, props, property);
     }
 
@@ -147,13 +148,13 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     returnThis = () => this;
     @computed get pointerEvents() {
         if (this.props.pointerEvents === "none") return "none";
-        return this.props.styleProvider?.(this.Document, this.props, !this._contentView?.docView?.isSelected() ? "pointerEvents:selected" : "pointerEvents");
+        return this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents + (!this._contentView?.docView?.isSelected() ? ":selected" : ""));
     }
     render() {
         TraceMobx();
-        const backgroundColor = this.props.styleProvider?.(this.Document, this.props, "backgroundColor");
-        const borderRadius = this.props.styleProvider?.(this.Document, this.props, "borderRounding");
-        const boxShadow = this.props.styleProvider?.(this.Document, this.props, "boxShadow");
+        const backgroundColor = this.props.styleProvider?.(this.Document, this.props, StyleProp.BackgroundColor);
+        const borderRadius = this.props.styleProvider?.(this.Document, this.props, StyleProp.BorderRounding);
+        const boxShadow = this.props.styleProvider?.(this.Document, this.props, StyleProp.BoxShadow);
         const divProps: DocumentViewProps = {
             ...this.props,
             CollectionFreeFormDocumentView: this.returnThis,
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 7b767a10a..ae0275c3d 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -67,32 +67,13 @@ export class ContentFittingDocumentView extends React.Component<DocumentViewProp
                     }}>
                     <DocumentView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
                         ref={action((r: DocumentView | null) => this.docView = r)}
-                        Document={this.props.Document}
-                        DataDoc={this.props.DataDoc}
                         LayoutTemplate={this.props.LayoutTemplate}
-                        LayoutTemplateString={this.props.LayoutTemplateString}
                         PanelWidth={this.PanelWidth}
                         PanelHeight={this.PanelHeight}
                         ContentScaling={returnOne}
-                        fitToBox={this.props.fitToBox}
-                        layoutKey={this.props.layoutKey}
-                        dropAction={this.props.dropAction}
-                        onClick={this.props.onClick}
-                        styleProvider={this.props.styleProvider}
-                        addDocument={this.props.addDocument}
-                        removeDocument={this.props.removeDocument}
-                        moveDocument={this.props.moveDocument}
-                        whenActiveChanged={this.props.whenActiveChanged}
-                        ContainingCollectionView={this.props.ContainingCollectionView}
-                        ContainingCollectionDoc={this.props.ContainingCollectionDoc}
-                        addDocTab={this.props.addDocTab}
-                        pinToPres={this.props.pinToPres}
-                        parentActive={this.props.parentActive}
                         ScreenToLocalTransform={this.getTransform}
-                        renderDepth={this.props.renderDepth}
                         focus={this.props.focus || emptyFunction}
                         bringToFront={emptyFunction}
-                        dontRegisterView={this.props.dontRegisterView}
                     />
                 </div>)}
         </div>);
diff --git a/src/client/views/nodes/DocHolderBox.tsx b/src/client/views/nodes/DocHolderBox.tsx
index e14093e70..1bc7bc8d7 100644
--- a/src/client/views/nodes/DocHolderBox.tsx
+++ b/src/client/views/nodes/DocHolderBox.tsx
@@ -19,6 +19,7 @@ import "./DocHolderBox.scss";
 import { DocumentView } from "./DocumentView";
 import { FieldView, FieldViewProps } from "./FieldView";
 import React = require("react");
+import { StyleProp } from "../StyleProvider";
 
 type DocHolderBoxSchema = makeInterface<[typeof documentSchema, typeof collectionSchema]>;
 const DocHolderBoxDocument = makeInterface(documentSchema, collectionSchema);
@@ -182,7 +183,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
             onContextMenu={this.specificContextMenu}
             onPointerDown={this.onPointerDown} onClick={this.onClick}
             style={{
-                background: this.props.styleProvider?.(containedDoc, this.props, "backgroundColor"),
+                background: this.props.styleProvider?.(containedDoc, this.props, StyleProp.BackgroundColor),
                 border: `#00000021 solid ${this.xPad}px`,
                 borderTop: `#0000005e solid ${this.yPad}px`,
                 borderBottom: `#0000005e solid ${this.yPad}px`,
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index d25d7af05..8dadd2467 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -73,25 +73,6 @@
         pointer-events: none;
     }
 
-    .documentView-lock {
-        width: 20; 
-        height: 20; 
-        position: absolute; 
-        right: -5; 
-        top: -5;
-        background: transparent;
-        pointer-events: all;
-        opacity: 0.3;
-        display: flex;
-        color: gold;
-        border-radius: 3px;
-        justify-content: center;
-        cursor: default;
-    }
-    .documentView-lock:hover {
-        opacity:1;
-    }
-
     .documentView-contentBlocker {
         pointer-events: all;
         position: absolute;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index cbbb27450..8a7c406d5 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,6 +1,6 @@
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt } from "../../../fields/Doc";
+import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast } from "../../../fields/Doc";
 import { Document } from '../../../fields/documentSchemas';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
@@ -42,11 +42,11 @@ import { RadialMenu } from './RadialMenu';
 import { TaskCompletionBox } from './TaskCompletedBox';
 import React = require("react");
 import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView";
+import { StyleProp, StyleLayers } from "../StyleProvider";
 
 export type DocAfterFocusFunc = (notFocused: boolean) => boolean;
 export type DocFocusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, focused?: boolean) => void;
 export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => any;
-
 export interface DocumentViewSharedProps {
     ContainingCollectionView: Opt<CollectionView>;
     ContainingCollectionDoc: Opt<Doc>;
@@ -118,6 +118,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     private _doubleTap = false;
     private _mainCont = React.createRef<HTMLDivElement>();
     private _titleRef = React.createRef<EditableView>();
+    private _timeout: NodeJS.Timeout | undefined;
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _gestureEventDisposer?: GestureUtils.GestureEventDisposer;
     private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
@@ -310,14 +311,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
-    _timeout: NodeJS.Timeout | undefined;
-
     onClick = action((e: React.MouseEvent | React.PointerEvent) => {
         if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && this.props.renderDepth >= 0 &&
             (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
             let stopPropagate = true;
             let preventDefault = true;
-            !Cast(this.props.Document.layers, listSpec("string"), []).includes("background") && (this.rootDoc._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc);
+            !StrListCast(this.props.Document.layers).includes(StyleLayers.Background) && (this.rootDoc._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc);
             if (this._doubleTap && ((this.props.renderDepth && this.props.Document.type !== DocumentType.FONTICON) || this.onDoubleClickHandler)) {// && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
                 if (this._timeout) {
                     clearTimeout(this._timeout);
@@ -957,7 +956,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 onClick={this.onClickFunc}
                 layoutKey={this.finalLayoutKey} />
             {this.layoutDoc.hideAllLinks ? (null) : this.allAnchors}
-            {this.props.styleProvider?.(this.layoutDoc, this.props, "hideLinkButton") || (!this.isSelected() && (this.layoutDoc.isLinkButton || this.layoutDoc.hideLinkButton)) || this.props.dontRegisterView ? (null) :
+            {this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HideLinkButton) || (!this.isSelected() && (this.layoutDoc.isLinkButton || this.layoutDoc.hideLinkButton)) || this.props.dontRegisterView ? (null) :
                 <DocumentLinksButton View={this} links={this.allLinks} Offset={this.linkOffset} />}
         </div>
         );
@@ -980,8 +979,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     anchorPanelWidth = () => this.props.PanelWidth() || 1;
     anchorPanelHeight = () => this.props.PanelHeight() || 1;
     anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
-        if (property === "backgroundColor") return props?.LayoutTemplateString ? "transparent" : this.props.styleProvider?.(doc, props, "backgroundColor");
-        if (property === "hideLinkButton") return true;
+        switch (property.split(":")[0]) {
+            case StyleProp.BackgroundColor: return "transparent";
+            case StyleProp.LinkBackgroundColor: return this.props.styleProvider?.(doc, props, StyleProp.BackgroundColor);
+            case StyleProp.HideLinkButton: return true;
+            case StyleProp.PointerEvents: return "none";
+            case StyleProp.LinkSource: return this.props.Document;
+        }
     }
 
     @computed get directLinks() { TraceMobx(); return LinkManager.Instance.getAllDirectLinks(this.rootDoc); }
@@ -999,15 +1003,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             <div className="documentView-anchorCont" key={i + 1}>
                 <DocumentView {...this.props}
                     Document={d}
-                    ContainingCollectionView={this.props.ContainingCollectionView}
-                    ContainingCollectionDoc={this.props.Document} // bcz: hack this.props.Document is not a collection  Need a better prop for passing the containing document to the LinkAnchorBox
                     PanelWidth={this.anchorPanelWidth}
                     PanelHeight={this.anchorPanelHeight}
                     ContentScaling={returnOne}
                     dontRegisterView={false}
                     styleProvider={this.anchorStyleProvider}
                     removeDocument={this.hideLinkAnchor}
-                    pointerEvents={"none"}
                     LayoutTemplate={undefined}
                     LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(d, this.props.Document)}`)} />
             </div >);
@@ -1056,7 +1057,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
     @computed get pointerEvents() {
         if (this.props.pointerEvents === "none") return "none";
-        return this.props.styleProvider?.(this.Document, this.props, this.isSelected() ? "pointerEvents:selected" : "pointerEvents");
+        return this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents + (this.isSelected() ? ":selected" : ""));
     }
     @undoBatch
     @action
@@ -1080,13 +1081,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         TraceMobx();
         if (!(this.props.Document instanceof Doc)) return (null);
         if (GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate) return (null);
-        if (this.props.styleProvider?.(this.layoutDoc, this.props, "hidden")) return null;
-        return this.props.styleProvider?.(this.rootDoc, this.props, "docContents") ??
+        if (this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Hidden)) return null;
+        return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.DocContents) ??
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 id={this.props.Document[Id]}
                 style={{
-                    background: this.props.styleProvider?.(this.layoutDoc, this.props, "backgroundColor"),
-                    opacity: this.props.styleProvider?.(this.layoutDoc, this.props, "opacity"),
+                    background: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
+                    opacity: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Opacity),
                     color: StrCast(this.layoutDoc.color, "inherit"),
                     fontFamily: StrCast(this.Document._fontFamily, "inherit"),
                     fontSize: Cast(this.Document._fontSize, "string", null),
@@ -1096,11 +1097,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 }}>
                 {this.innards}
                 {this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <div className="documentView-contentBlocker" /> : (null)}
-                {this.props.styleProvider?.(this.rootDoc, this.props, this.isSelected() ? "decorations:selected" : "decorations") || (null)}
+                {this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Decorations + (this.isSelected() ? ":selected" : "")) || (null)}
             </div>;
     }
     render() {
-        const borderRounding = this.props.styleProvider?.(this.layoutDoc, this.props, "borderRounding");
+        const borderRounding = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow);
         const highlightIndex = this.props.LayoutTemplateString ? (Doc.IsHighlighted(this.props.Document) ? 6 : 0) : Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
         const highlightColor = (CurrentUserUtils.ActiveDashboard?.darkScheme ?
             ["transparent", "#65350c", "#65350c", "yellow", "magenta", "cyan", "orange"] :
@@ -1127,7 +1128,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 outline: highlighting && !borderRounding ? `${highlightColor} ${highlightStyle} ${highlightIndex}px` : "solid 0px",
                 border: highlighting && borderRounding && highlightStyle === "dashed" ? `${highlightStyle} ${highlightColor} ${highlightIndex}px` : undefined,
                 boxShadow: highlighting && borderRounding && highlightStyle !== "dashed" ? `0 0 0 ${highlightIndex}px ${highlightColor}` :
-                    this.Document.isLinkButton && !this.props.dontRegisterView && !this.props.styleProvider?.(this.layoutDoc, this.props, "hideLinkButton") ?
+                    this.Document.isLinkButton && !this.props.dontRegisterView && !this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HideLinkButton) ?
                         StrCast(this.layoutDoc._linkButtonShadow, "lightblue 0em 0em 1em") :
                         this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" :
                             undefined,
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index a1bb0604e..000c72e94 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -14,6 +14,7 @@ import { ScriptField } from '../../../fields/ScriptField';
 import { Tooltip } from '@material-ui/core';
 import { DragManager } from '../../util/DragManager';
 import { GetEffectiveAcl } from '../../../fields/util';
+import { StyleProp } from '../StyleProvider';
 const FontIconSchema = createSchema({
     icon: "string",
 });
@@ -61,7 +62,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
     render() {
         const label = StrCast(this.rootDoc.label, StrCast(this.rootDoc.title));
         const color = StrCast(this.layoutDoc.color, this._foregroundColor);
-        const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, "backgroundColor");
+        const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
         const shape = StrCast(this.layoutDoc.iconShape, label ? "round" : "circle");
         const icon = StrCast(this.dataDoc.icon, "user") as any;
         const presSize = shape === 'round' ? 25 : 30;
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index dd751b802..bc2090a33 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -13,6 +13,7 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxBaseComponent } from '../DocComponent';
 import { FieldView, FieldViewProps } from './FieldView';
 import './LabelBox.scss';
+import { StyleProp } from '../StyleProvider';
 
 const LabelSchema = createSchema({});
 
@@ -72,7 +73,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument
                 onMouseLeave={action(() => this._mouseOver = false)}
                 onMouseOver={action(() => this._mouseOver = true)}
                 ref={this.createDropTarget} onContextMenu={this.specificContextMenu}
-                style={{ boxShadow: this.props.styleProvider?.(this.layoutDoc, this.props, "boxShadow") }}>
+                style={{ boxShadow: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow) }}>
                 <div className="labelBox-mainButton" style={{
                     background: StrCast(this.layoutDoc.backgroundColor),
                     backgroundColor: this.backColor,
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index b5b53501d..f4528fbe0 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -19,6 +19,7 @@ import { SelectionManager } from "../../util/SelectionManager";
 import { TraceMobx } from "../../../fields/util";
 import { Id } from "../../../fields/FieldSymbols";
 import { LinkDocPreview } from "./LinkDocPreview";
+import { StyleProp } from "../StyleProvider";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -68,7 +69,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
             this.props.select(false);
         }
         if (!this._doubleTap && !e.ctrlKey && e.button < 2) {
-            const anchorContainerDoc = this.props.ContainingCollectionDoc; // bcz: hack!  need a better prop for passing the anchor's container 
+            const anchorContainerDoc = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkSource);
             this._editing = true;
             anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false);
             if (anchorContainerDoc && !this.layoutDoc.onClick && !this._isOpen) {
@@ -117,7 +118,8 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
         const small = this.props.PanelWidth() <= 1; // this happens when rendered in a treeView
         const x = NumCast(this.rootDoc[this.fieldKey + "_x"], 100);
         const y = NumCast(this.rootDoc[this.fieldKey + "_y"], 100);
-        const c = this.props.styleProvider?.(this.dataDoc, OmitKeys(this.props, ["LayoutTemplateString"]).omit, "backgroundColor");
+        const linkSource = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkSource);
+        const background = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkBackgroundColor);
         const anchor = this.fieldKey === "anchor1" ? "anchor2" : "anchor1";
         const anchorScale = !this.dataDoc[this.fieldKey + "-useLinkSmallAnchor"] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : .25;
 
@@ -135,13 +137,13 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
             onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
             onPointerEnter={action(e => LinkDocPreview.LinkInfo = {
                 addDocTab: this.props.addDocTab,
-                linkSrc: this.props.ContainingCollectionDoc!,
+                linkSrc: linkSource,
                 linkDoc: this.rootDoc,
                 Location: [e.clientX, e.clientY + 20]
             })}
             onPointerDown={this.onPointerDown} onClick={this.onClick} title={targetTitle} onContextMenu={this.specificContextMenu}
             ref={this._ref} style={{
-                background: c,
+                background,
                 left: `calc(${x}% - ${small ? 2.5 : 7.5}px)`,
                 top: `calc(${y}% - ${small ? 2.5 : 7.5}px)`,
                 transform: `scale(${anchorScale / this.props.ContentScaling()})`
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index b3fd13a77..a12a23dff 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -8,6 +8,7 @@ import { ViewBoxBaseComponent } from "../DocComponent";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./LinkBox.scss";
 import { Cast } from "../../../fields/Types";
+import { StyleProp } from "../StyleProvider";
 
 type LinkDocument = makeInterface<[typeof documentSchema]>;
 const LinkDocument = makeInterface(documentSchema);
@@ -17,7 +18,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps, LinkDocument>(
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkBox, fieldKey); }
     render() {
         return <div className={`linkBox-container${this.active() ? "-interactive" : ""}`}
-            style={{ background: this.props.styleProvider?.(this.props.Document, this.props, "backgroundColor") }} >
+            style={{ background: this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BackgroundColor) }} >
 
             <CollectionTreeView {...this.props}
                 ChromeHeight={returnZero}
diff --git a/src/client/views/nodes/SliderBox.tsx b/src/client/views/nodes/SliderBox.tsx
index bcaf04da6..2aa71b2bd 100644
--- a/src/client/views/nodes/SliderBox.tsx
+++ b/src/client/views/nodes/SliderBox.tsx
@@ -13,6 +13,7 @@ import { ScriptBox } from '../ScriptBox';
 import { FieldView, FieldViewProps } from './FieldView';
 import { Handle, Tick, TooltipRail, Track } from './SliderBox-components';
 import './SliderBox.scss';
+import { StyleProp } from '../StyleProvider';
 
 const SliderSchema = createSchema({
     _sliderMin: "number",
@@ -48,7 +49,7 @@ export class SliderBox extends ViewBoxBaseComponent<FieldViewProps, SliderDocume
         const defaultValues = [NumCast(this.dataDoc[this.minThumbKey]), NumCast(this.dataDoc[this.maxThumbKey])];
         return domain[1] <= domain[0] ? (null) : (
             <div className="sliderBox-outerDiv" onContextMenu={this.specificContextMenu} onPointerDown={e => e.stopPropagation()}
-                style={{ boxShadow: this.props.styleProvider?.(this.layoutDoc, this.props, "boxShadow") }}>
+                style={{ boxShadow: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow) }}>
                 <div className="sliderBox-mainButton" onContextMenu={this.specificContextMenu} style={{
                     background: StrCast(this.layoutDoc.backgroundColor), color: StrCast(this.layoutDoc.color, "black"),
                     fontSize: StrCast(this.layoutDoc._fontSize), letterSpacing: StrCast(this.layoutDoc.letterSpacing)
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 415c8c184..6812b4f38 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -63,6 +63,7 @@ import { CollectionViewType, CollectionViewProps } from '../../collections/Colle
 import { SnappingManager } from '../../../util/SnappingManager';
 import { LinkDocPreview } from '../LinkDocPreview';
 import { SubCollectionViewProps } from '../../collections/CollectionSubView';
+import { StyleProp } from '../../StyleProvider';
 
 export interface FormattedTextBoxProps {
     makeLink?: () => Opt<Doc>;  // bcz: hack: notifies the text document when the container has made a link.  allows the text doc to react and setup a hyeprlink for any selected text
@@ -1599,7 +1600,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const annotated = DocListCast(this.dataDoc[this.annotationKey]).filter(d => d?.author).length;
         return !this.props.isSelected() && !(annotated && !this.sidebarWidth()) ? (null) :
             <div className="formattedTextBox-sidebar-handle"
-                style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`, background: annotated ? "lightblue" : this.props.styleProvider?.(this.props.Document, this.props, "widgetColor") }}
+                style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`, background: annotated ? "lightblue" : this.props.styleProvider?.(this.props.Document, this.props, StyleProp.WidgetColor) }}
                 onPointerDown={this.sidebarDown}
             />;
     }
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 45d943bff..3b6b0259d 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -22,6 +22,7 @@ import { undoBatch } from "../../util/UndoManager";
 import { EditableView } from "../EditableView";
 import { DocumentManager } from "../../util/DocumentManager";
 import { DocumentViewProps } from "../nodes/DocumentView";
+import { StyleProp } from "../StyleProvider";
 
 export const presSchema = createSchema({
     presentationTargetDoc: Doc,
@@ -78,7 +79,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
     // embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight);
     embedWidth = (): number => this.props.PanelWidth() - 35;
     styleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => {
-        if (property === "opacity") return 1;
+        if (property === StyleProp.Opacity) return 1;
         return this.props.styleProvider?.(doc, props, property);
     }
     /**
@@ -326,7 +327,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
                     </div>}
                 {miniView ? (null) : <div ref={miniView ? null : this._dragRef} className={`presItem-slide ${isSelected ? "active" : ""}`}
                     style={{
-                        backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, "color"),
+                        backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
                         boxShadow: presBoxColor && presBoxColor !== "white" && presBoxColor !== "transparent" ? isSelected ? "0 0 0px 1.5px" + presBoxColor : undefined : undefined
                     }}>
                     <div className="presItem-name" style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105, cursor: isSelected ? 'text' : 'grab' }}>
-- 
cgit v1.2.3-70-g09d2


From 58cd2a80fbd62fe31143922171a2448ac59f8bd8 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 11 Dec 2020 18:50:30 -0500
Subject: fixed stackingview autoHeight when headers are on that don't overlap.
 improved styles a bit.  added foreground color that contrasts with background
 by default. added an itemBackgroundColor(for <item>Box's) which is distinct
 from BackgroundColor (for docViews)

---
 src/client/util/CurrentUserUtils.ts                |  2 +-
 src/client/views/MainView.tsx                      | 33 +++++++-------
 src/client/views/StyleProvider.tsx                 | 50 ++++++++++++++--------
 .../views/collections/CollectionStackingView.tsx   | 11 +++--
 .../views/nodes/ContentFittingDocumentView.tsx     | 23 ++++------
 src/client/views/nodes/DocumentView.tsx            | 17 ++++----
 src/client/views/nodes/FontIconBox.tsx             | 44 ++++++-------------
 src/client/views/nodes/LinkAnchorBox.tsx           |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  4 +-
 9 files changed, 90 insertions(+), 96 deletions(-)

(limited to 'src/client/views/nodes/LinkAnchorBox.tsx')

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 2a7e95666..82750bd53 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -910,7 +910,7 @@ export class CurrentUserUtils {
         }
         if (doc.myImportPanel === undefined) {
             const uploads = Cast(doc.myImportDocs, Doc, null);
-            const newUpload = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("importDocument()"), toolTip: "Import External document", _backgroundColor: "black", _stayInCollection: true, _hideContextMenu: true, title: "Import", icon: "upload", system: true });
+            const newUpload = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("importDocument()"), toolTip: "Import External document", _stayInCollection: true, _hideContextMenu: true, title: "Import", icon: "upload", system: true });
             doc.myImportPanel = new PrefetchProxy(Docs.Create.StackingDocument([newUpload, uploads], { title: "My ImportPanel", _yMargin: 20, ignoreClick: true, _stayInCollection: true, _hideContextMenu: true, lockedPosition: true, system: true }));
         }
     }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 2cb529317..58cc1c0d2 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,7 +1,7 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons';
-import * as fa from '@fortawesome/free-solid-svg-icons';
 import * as far from '@fortawesome/free-regular-svg-icons';
+import * as fa from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, configure, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
@@ -12,7 +12,8 @@ import { Doc, DocListCast, Opt } from '../../fields/Doc';
 import { List } from '../../fields/List';
 import { PrefetchProxy } from '../../fields/Proxy';
 import { BoolCast, PromiseValue, StrCast } from '../../fields/Types';
-import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick, Utils } from '../../Utils';
+import { TraceMobx } from '../../fields/util';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../Utils';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { DocServer } from '../DocServer';
 import { Docs } from '../documents/Documents';
@@ -22,10 +23,12 @@ import { GroupManager } from '../util/GroupManager';
 import { HistoryUtil } from '../util/History';
 import { Hypothesis } from '../util/HypothesisUtils';
 import { Scripting } from '../util/Scripting';
+import { SelectionManager } from '../util/SelectionManager';
 import { SettingsManager } from '../util/SettingsManager';
 import { SharingManager } from '../util/SharingManager';
 import { SnappingManager } from '../util/SnappingManager';
 import { Transform } from '../util/Transform';
+import { UndoManager } from '../util/UndoManager';
 import { TimelineMenu } from './animationtimeline/TimelineMenu';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
@@ -35,15 +38,15 @@ import { CollectionViewType } from './collections/CollectionView';
 import { ContextMenu } from './ContextMenu';
 import { DictationOverlay } from './DictationOverlay';
 import { DocumentDecorations } from './DocumentDecorations';
-import { InkStrokeProperties } from './InkStrokeProperties';
 import { GestureOverlay } from './GestureOverlay';
 import { MENU_PANEL_WIDTH, SEARCH_PANEL_HEIGHT } from './globalCssVariables.scss';
 import { KeyManager } from './GlobalKeyHandler';
+import { InkStrokeProperties } from './InkStrokeProperties';
 import { LinkMenu } from './linking/LinkMenu';
 import "./MainView.scss";
 import { AudioBox } from './nodes/AudioBox';
 import { DocumentLinksButton } from './nodes/DocumentLinksButton';
-import { DocumentView } from './nodes/DocumentView';
+import { DocumentView, DocumentViewProps } from './nodes/DocumentView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
 import { LinkDocPreview } from './nodes/LinkDocPreview';
@@ -55,11 +58,7 @@ import { PDFMenu } from './pdf/PDFMenu';
 import { PreviewCursor } from './PreviewCursor';
 import { PropertiesView } from './PropertiesView';
 import { SearchBox } from './search/SearchBox';
-import { TraceMobx } from '../../fields/util';
-import { SelectionManager } from '../util/SelectionManager';
-import { UndoManager } from '../util/UndoManager';
-import { TabDocView } from './collections/TabDocView';
-import { DefaultStyleProvider } from './StyleProvider';
+import { DefaultStyleProvider, StyleProp } from './StyleProvider';
 const _global = (window /* browser */ || global /* node */) as any;
 
 @observer
@@ -67,7 +66,7 @@ export class MainView extends React.Component {
     public static Instance: MainView;
     private _docBtnRef = React.createRef<HTMLDivElement>();
     private _mainViewRef = React.createRef<HTMLDivElement>();
-    private _lastButton: Doc | undefined;
+    @observable private _lastButton: Doc | undefined;
 
     @observable private _panelWidth: number = 0;
     @observable private _panelHeight: number = 0;
@@ -299,6 +298,13 @@ export class MainView extends React.Component {
             doc.dockingConfig ? CurrentUserUtils.openDashboard(Doc.UserDoc(), doc) : CollectionDockingView.AddSplit(doc, "right");
     }
 
+    menuStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
+        if (property === StyleProp.ItemBackgroundColor && this._lastButton === doc) {
+            return this.darkScheme ? "dimgrey" : "lightgrey";
+        }
+        return DefaultStyleProvider(doc, props, property);
+    }
+
     @computed get flyout() {
         return !this._flyoutWidth ? <div className={`mainView-libraryFlyout-out`}>
             {this.docButtons}
@@ -353,7 +359,7 @@ export class MainView extends React.Component {
                 PanelHeight={this.getContentsHeight}
                 renderDepth={0}
                 focus={emptyFunction}
-                styleProvider={DefaultStyleProvider}
+                styleProvider={this.menuStyleProvider}
                 parentActive={returnTrue}
                 whenActiveChanged={emptyFunction}
                 bringToFront={emptyFunction}
@@ -426,14 +432,11 @@ export class MainView extends React.Component {
     expandFlyout = action((button: Doc) => {
         this._flyoutWidth = (this._flyoutWidth || 250);
         this._sidebarContent.proto = button.target as any;
-        button._backgroundColor = this.darkScheme ? "dimgrey" : "lightgrey";
-        button.color = "black";
         this._lastButton = button;
     });
 
     closeFlyout = action(() => {
-        this._lastButton && (this._lastButton.color = "white");
-        this._lastButton && (this._lastButton._backgroundColor = "");
+        this._lastButton = undefined;
         this._panelContent = "none";
         this._sidebarContent.proto = undefined;
         this._flyoutWidth = 0;
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 78bd15cfd..adf47ec57 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -15,6 +15,7 @@ import "./StyleProvider.scss";
 import React = require("react");
 import Color = require('color');
 import { listSpec } from '../../fields/Schema';
+import { Utils } from '../../Utils';
 
 export enum StyleLayers {
     Background = "background"
@@ -26,9 +27,10 @@ export enum StyleProp {
     Hidden = "hidden",                    // whether the document view should not be isplayed
     BoxShadow = "boxShadow",              // box shadow - used for making collections standout and for showing clusters in free form views
     BorderRounding = "borderRounding",    // border radius of the document view
+    Color = "color",                      // foreground color of Document view items
     BackgroundColor = "backgroundColor",  // background color of a document view
+    ItemBackgroundColor = "itemBackgroundColor", // background color for <item>Box inside DocumentView
     WidgetColor = "widgetColor",                 // color to display UI widgets on a document view -- used for the sidebar divider dragger on a text note
-    LinkBackgroundColor = "linkBackgroundColor", // background color of a link dot -- defaults to the backgroundColor of the link document
     HideLinkButton = "hideLinkButton",  // hides the blue-dot link button.  used when a document acts like a button
     LinkSource = "linkSource",      // source document of a link -- used by LinkAnchorBox
     PointerEvents = "pointerEvents",// pointer events for DocumentView -- inherits pointer events if not specified
@@ -65,32 +67,44 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.DocContents: return undefined;
         case StyleProp.WidgetColor: return darkScheme() ? "lightgrey" : "dimgrey";
         case StyleProp.Opacity: return Cast(doc?._opacity, "number", Cast(doc?.opacity, "number", null));
+        case StyleProp.Color:
+            const backColor = props?.styleProvider?.(doc, props, StyleProp.ItemBackgroundColor) || "black";
+            const col = Color(backColor).rgb();
+            const colsum = (col.red() + col.green() + col.blue());
+            if (colsum / col.alpha() > 600 || col.alpha() < 0.25) return "black";
+            return "white";
         case StyleProp.Hidden: return BoolCast(doc?._hidden, BoolCast(doc?.hidden));
         case StyleProp.BorderRounding: return !doc ? undefined : StrCast(doc._borderRounding, StrCast(doc.borderRounding));
         case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.Masonry].includes(doc?._viewType as any) || doc?.type === DocumentType.RTF) && doc?._showTitle && !doc?._showTitleHover ? 15 : 0;
+        case StyleProp.ItemBackgroundColor:
+            let docColor: Opt<string> = StrCast(doc?._backgroundColor, StrCast(doc?.backgroundColor));
+            if (docColor) return docColor;
+            switch (doc?.type) {
+                case DocumentType.FONTICON: return "black";
+                case DocumentType.LINK: return "lightblue";
+            }
         case StyleProp.BackgroundColor: {
-            if (Doc.UserDoc().renderStyle === "comic") return undefined;
+            if (Doc.UserDoc().renderStyle === "comic") return "transparent";
             let docColor: Opt<string> = StrCast(doc?._backgroundColor, StrCast(doc?.backgroundColor));
-            if (!docColor) {
-                switch (doc?.type) {
-                    case DocumentType.PRESELEMENT: docColor = darkScheme() ? "" : ""; break;
-                    case DocumentType.PRES: docColor = darkScheme() ? "#3e3e3e" : "white"; break;
-                    case DocumentType.FONTICON: docColor = "black"; break;
-                    case DocumentType.RTF: docColor = darkScheme() ? "#2d2d2d" : "#f1efeb"; break;
-                    case DocumentType.LABEL:
-                    case DocumentType.BUTTON: docColor = darkScheme() ? "#2d2d2d" : "lightgray"; break;
-                    case DocumentType.LINK:
-                    case DocumentType.COL:
-                        docColor = Doc.IsSystem(doc) ? (darkScheme() ? "rgb(62,62,62)" : "lightgrey") :
+            switch (doc?.type) {
+                case DocumentType.PRESELEMENT: docColor = docColor || (darkScheme() ? "" : ""); break;
+                case DocumentType.PRES: docColor = docColor || (darkScheme() ? "#3e3e3e" : "white"); break;
+                case DocumentType.FONTICON: docColor = undefined; break;
+                case DocumentType.RTF: docColor = docColor || (darkScheme() ? "#2d2d2d" : "#f1efeb"); break;
+                case DocumentType.LABEL:
+                case DocumentType.BUTTON: docColor = docColor || (darkScheme() ? "#2d2d2d" : "lightgray"); break;
+                case DocumentType.LINK:
+                case DocumentType.COL:
+                    docColor = docColor ||
+                        (Doc.IsSystem(doc) ? (darkScheme() ? "rgb(62,62,62)" : "lightgrey") :
                             StrListCast(doc.layers).includes(StyleLayers.Background) ? "cyan" :
                                 doc.annotationOn ? "#00000015" :
                                     StrCast((props?.renderDepth || 0) > 0 ?
                                         Doc.UserDoc().activeCollectionNestedBackground :
-                                        Doc.UserDoc().activeCollectionBackground);
-                        break;
-                    //if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)";
-                    default: docColor = darkScheme() ? "black" : "white"; break;
-                }
+                                        Doc.UserDoc().activeCollectionBackground));
+                    break;
+                //if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)";
+                default: docColor = darkScheme() ? "black" : "white"; break;
             }
             if (docColor && (!doc || props?.layerProvider?.(doc) === false)) docColor = Color(docColor.toLowerCase()).fade(0.5).toString();
             return docColor;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 6a3a96b74..e8165f673 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -53,6 +53,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
     @computed get columnHeaders() { return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField)); }
     @computed get pivotField() { return StrCast(this.layoutDoc._pivotField); }
     @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.hidden).map(pair => pair.layout); }
+    @computed get headerMargin() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin); }
     @computed get xMargin() { return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); }
     @computed get yMargin() { return this.props.yMargin || NumCast(this.layoutDoc._yMargin, 5); } // 2 * this.gridGap)); }
     @computed get gridGap() { return NumCast(this.layoutDoc._gridGap, 10); }
@@ -211,7 +212,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
             freezeDimensions={this.props.childFreezeDimensions}
             NativeWidth={this.props.childIgnoreNativeSize ? returnZero : undefined}  // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
             NativeHeight={this.props.childIgnoreNativeSize ? returnZero : undefined}
-            dontCenter={this.props.childIgnoreNativeSize ? "xy" : ""}
+            dontCenter={this.props.childIgnoreNativeSize ? "xy" : undefined}
             fitToBox={false}
             dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.dontRegisterChildViews, this.props.dontRegisterView)}
             rootSelected={this.rootSelected}
@@ -360,7 +361,9 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
                     const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
                     this.observer = new _global.ResizeObserver(action((entries: any) => {
                         if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
-                            const height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => NumCast(Doc.Layout(doc)._viewScale, 1) * Number(getComputedStyle(r).height.replace("px", "")))));
+                            const height = this.headerMargin +
+                                Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER),
+                                    Math.max(...this.refList.map(r => NumCast(Doc.Layout(doc)._viewScale, 1) * Number(getComputedStyle(r).height.replace("px", "")))));
                             if (this.props.isAnnotationOverlay) {
                                 doc[this.props.fieldKey + "-height"] = height;
                             } else {
@@ -396,7 +399,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
 
     forceAutoHeight = () => {
         const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
-        Doc.Layout(doc)._height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0);
+        Doc.Layout(doc)._height = this.headerMargin + this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0);
     }
 
     sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[], first: boolean) => {
@@ -418,7 +421,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
                     this.observer = new _global.ResizeObserver(action((entries: any) => {
                         if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
                             const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0);
-                            Doc.Layout(doc)._height = Math.max(height, NumCast(doc[this.props.fieldKey + "-height"]));
+                            Doc.Layout(doc)._height = this.headerMargin + Math.max(height, NumCast(doc[this.props.fieldKey + "-height"]));
                         }
                     }));
                     this.observer.observe(ref);
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index ae0275c3d..e632f0e19 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -1,15 +1,15 @@
 import React = require("react");
 import { computed, observable, action } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, HeightSym, WidthSym } from "../../../fields/Doc";
-import { Cast, StrCast } from "../../../fields/Types";
+import { Doc } from "../../../fields/Doc";
 import { TraceMobx } from "../../../fields/util";
 import { emptyFunction, OmitKeys, returnVal, returnOne } from "../../../Utils";
 import { DocumentView, DocumentViewProps } from "../nodes/DocumentView";
 import "./ContentFittingDocumentView.scss";
+import { StyleProp } from "../StyleProvider";
 
 interface ContentFittingDocumentViewProps {
-    dontCenter?: string; // "x" ,"y", "xy"
+    dontCenter?: "x" | "y" | "xy";
 }
 
 @observer
@@ -17,14 +17,9 @@ export class ContentFittingDocumentView extends React.Component<DocumentViewProp
     public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive
     public ContentRef = React.createRef<HTMLDivElement>();
     @observable public docView: DocumentView | undefined | null;
-    @computed get layoutDoc() {
-        return this.props.LayoutTemplate?.() ||
-            (this.props.layoutKey && Doc.Layout(this.props.Document, Cast(this.props.Document[this.props.layoutKey], Doc, null))) ||
-            Doc.Layout(this.props.Document);
-    }
-    @computed get freezeDimensions() { return this.props.freezeDimensions; }
-    @computed get nativeWidth() { return !this.layoutDoc._fitWidth && returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, this.freezeDimensions)); }
-    @computed get nativeHeight() { return returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.freezeDimensions) || 0); }
+    @computed get layoutDoc() { return Doc.Layout(this.props.Document, this.props.LayoutTemplate?.()); }
+    @computed get nativeWidth() { return !this.layoutDoc._fitWidth && returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions)); }
+    @computed get nativeHeight() { return returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions) || 0); }
     @computed get nativeScaling() {
         if (!this.nativeWidth || !this.nativeHeight) return 1;
         const wscale = this.props.PanelWidth() / this.nativeWidth;
@@ -38,7 +33,7 @@ export class ContentFittingDocumentView extends React.Component<DocumentViewProp
     @computed get panelWidth() { return this.nativeWidth ? this.nativeWidth * this.nativeScaling : this.props.PanelWidth(); }
     @computed get panelHeight() {
         if (this.nativeHeight) {
-            if (this.props.Document._fitWidth) return Math.min(this.props.PanelHeight(), this.panelWidth / Doc.NativeAspect(this.layoutDoc, this.props.DataDoc, this.freezeDimensions) || 1);
+            if (this.props.Document._fitWidth) return Math.min(this.props.PanelHeight(), this.panelWidth / Doc.NativeAspect(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions) || 1);
             return Math.min(this.props.PanelHeight(), this.nativeHeight * this.nativeScaling);
         }
         return this.props.PanelHeight();
@@ -49,8 +44,6 @@ export class ContentFittingDocumentView extends React.Component<DocumentViewProp
     private get centeringOffset() { return this.nativeWidth && !this.props.Document._fitWidth ? (this.props.PanelWidth() - this.nativeWidth * this.nativeScaling) / 2 : 0; }
     private get centeringYOffset() { return this.nativeWidth && Math.abs(this.centeringOffset) < 0.001 && this.nativeHeight ? (this.props.PanelHeight() - this.nativeHeight * this.nativeScaling) / 2 : 0; }
 
-    @computed get borderRounding() { return StrCast(this.props.Document?.borderRounding); }
-
     PanelWidth = () => this.panelWidth;
     PanelHeight = () => this.panelHeight;
 
@@ -61,7 +54,7 @@ export class ContentFittingDocumentView extends React.Component<DocumentViewProp
                 <div className="contentFittingDocumentView-previewDoc" ref={this.ContentRef}
                     style={{
                         transform: `translate(${this.props.dontCenter?.includes("x") ? 0 : this.centeringOffset}px, ${this.props.dontCenter?.includes("y") ? 0 : this.centeringYOffset}px)`,
-                        borderRadius: this.borderRounding,
+                        borderRadius: this.props.styleProvider?.(this.props.Document, this.props, StyleProp.PointerEvents),
                         height: Math.abs(this.centeringYOffset) > 0.001 && this.nativeWidth ? `${100 * this.nativeHeight / this.nativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%` : this.props.PanelHeight(),
                         width: Math.abs(this.centeringOffset) > 0.001 ? `${100 * (this.props.PanelWidth() - this.centeringOffset * 2) / this.props.PanelWidth()}%` : this.props.PanelWidth(),
                     }}>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 017c3cdea..65779088f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -86,7 +86,6 @@ export interface DocumentViewSharedProps {
 }
 export interface DocumentViewProps extends DocumentViewSharedProps {
     // properties specific to DocumentViews but not to FieldView
-    layoutKey?: string;
     freezeDimensions?: boolean;
     hideTitle?: boolean;
     fitToBox?: boolean;
@@ -124,7 +123,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     private get active() { return this.isSelected(true) || this.props.parentActive(true); }
     public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
     public get ContentDiv() { return this._mainCont.current; }
-    public get LayoutFieldKey() { return this.props.layoutKey || Doc.LayoutFieldKey(this.layoutDoc); }
+    public get LayoutFieldKey() { return Doc.LayoutFieldKey(this.layoutDoc); }
     @computed get ShowTitle() {
         return StrCast(this.layoutDoc._showTitle,
             !Doc.IsSystem(this.layoutDoc) && this.rootDoc.type === DocumentType.RTF && !this.rootDoc.presentationTargetDoc ?
@@ -892,11 +891,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     @computed get finalLayoutKey() {
-        if (typeof this.props.layoutKey === "string") {
-            return this.props.layoutKey;
-        }
-        const fallback = Cast(this.props.Document.layoutKey, "string");
-        return typeof fallback === "string" ? fallback : "layout";
+        return StrCast(this.props.Document.layoutKey, "layout");
     }
     rootSelected = (outsideReaction?: boolean) => {
         return this.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
@@ -980,11 +975,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
         switch (property.split(":")[0]) {
             case StyleProp.BackgroundColor: return "transparent";
-            case StyleProp.LinkBackgroundColor: return this.props.styleProvider?.(doc, props, StyleProp.BackgroundColor);
             case StyleProp.HideLinkButton: return true;
             case StyleProp.PointerEvents: return "none";
             case StyleProp.LinkSource: return this.props.Document;
         }
+        return this.props.styleProvider?.(doc, props, property);
     }
 
     @computed get directLinks() { TraceMobx(); return LinkManager.Instance.getAllDirectLinks(this.rootDoc); }
@@ -1012,6 +1007,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(d, this.props.Document)}`)} />
             </div >);
     }
+    captionStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
+        if (property === StyleProp.Color) return "white";
+        if (property === StyleProp.BackgroundColor) return "rgba(0,0,0 ,0.4)";
+        return this.props?.styleProvider?.(doc, props, property);
+    }
     @computed get innards() {
         TraceMobx();
         const showTitleHover = StrCast(this.layoutDoc._showTitleHover);
@@ -1022,6 +1022,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     yMargin={10}
                     xMargin={10}
                     hideOnLeave={true}
+                    styleProvider={this.captionStyleProvider}
                     dontRegisterView={true}
                     LayoutTemplateString={`<FormattedTextBox {...props} fieldKey={'${showCaption}'}/>`}
                     ContentScaling={returnOne}
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index 000c72e94..b979c9017 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -1,20 +1,19 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { AclPrivate, Doc, DocListCast } from '../../../fields/Doc';
 import { createSchema, makeInterface } from '../../../fields/Schema';
-import { DocComponent } from '../DocComponent';
-import './FontIconBox.scss';
-import { FieldView, FieldViewProps } from './FieldView';
-import { StrCast, Cast, ScriptCast } from '../../../fields/Types';
-import { Utils, setupMoveUpEvents, returnFalse, emptyFunction } from "../../../Utils";
-import { runInAction, observable, reaction, IReactionDisposer } from 'mobx';
-import { Doc, DocListCast, AclPrivate } from '../../../fields/Doc';
-import { ContextMenu } from '../ContextMenu';
 import { ScriptField } from '../../../fields/ScriptField';
-import { Tooltip } from '@material-ui/core';
-import { DragManager } from '../../util/DragManager';
+import { Cast, StrCast } from '../../../fields/Types';
 import { GetEffectiveAcl } from '../../../fields/util';
+import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils";
+import { DragManager } from '../../util/DragManager';
+import { ContextMenu } from '../ContextMenu';
+import { DocComponent } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
+import { FieldView, FieldViewProps } from './FieldView';
+import './FontIconBox.scss';
 const FontIconSchema = createSchema({
     icon: "string",
 });
@@ -24,21 +23,6 @@ const FontIconDocument = makeInterface(FontIconSchema);
 @observer
 export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(FontIconDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FontIconBox, fieldKey); }
-    @observable _foregroundColor = "white";
-    _ref: React.RefObject<HTMLButtonElement> = React.createRef();
-    _backgroundReaction: IReactionDisposer | undefined;
-    componentDidMount() {
-        this._backgroundReaction = reaction(() => this.layoutDoc.backgroundColor,
-            () => {
-                if (this._ref && this._ref.current) {
-                    const col = Utils.fromRGBAstr(getComputedStyle(this._ref.current).backgroundColor);
-                    const colsum = (col.r + col.g + col.b);
-                    if (colsum / col.a > 600 || col.a < 0.25) runInAction(() => this._foregroundColor = "black");
-                    else if (colsum / col.a <= 600 || col.a >= .25) runInAction(() => this._foregroundColor = "white");
-                }
-            }, { fireImmediately: true });
-    }
-
     showTemplate = (): void => {
         const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
         dragFactory && this.props.addDocTab(dragFactory, "add:right");
@@ -55,20 +39,16 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
         }
     }
 
-    componentWillUnmount() {
-        this._backgroundReaction?.();
-    }
-
     render() {
         const label = StrCast(this.rootDoc.label, StrCast(this.rootDoc.title));
-        const color = StrCast(this.layoutDoc.color, this._foregroundColor);
-        const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+        const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+        const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ItemBackgroundColor);
         const shape = StrCast(this.layoutDoc.iconShape, label ? "round" : "circle");
         const icon = StrCast(this.dataDoc.icon, "user") as any;
         const presSize = shape === 'round' ? 25 : 30;
         const presTrailsIcon = <img src={`/assets/${"presTrails.png"}`}
             style={{ width: presSize, height: presSize, filter: `invert(${color === "white" ? "100%" : "0%"})`, marginBottom: "5px" }} />;
-        const button = <button className={`menuButton-${shape}`} ref={this._ref} onContextMenu={this.specificContextMenu}
+        const button = <button className={`menuButton-${shape}`} onContextMenu={this.specificContextMenu}
             style={{
                 boxShadow: this.layoutDoc.ischecked ? `4px 4px 12px black` : undefined,
                 backgroundColor: this.layoutDoc.iconShape === "square" ? backgroundColor : "",
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index f4528fbe0..abefc6561 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -119,7 +119,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
         const x = NumCast(this.rootDoc[this.fieldKey + "_x"], 100);
         const y = NumCast(this.rootDoc[this.fieldKey + "_y"], 100);
         const linkSource = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkSource);
-        const background = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkBackgroundColor);
+        const background = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.ItemBackgroundColor);
         const anchor = this.fieldKey === "anchor1" ? "anchor2" : "anchor1";
         const anchorScale = !this.dataDoc[this.fieldKey + "-useLinkSmallAnchor"] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : .25;
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 0392566c0..4be49ca47 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1676,8 +1676,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         overflow: this.layoutDoc._autoHeight ? "hidden" : undefined,
                         width: "100%",
                         height: this.props.height || (this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : undefined),
-                        background: Doc.UserDoc().renderStyle === "comic" ? "transparent" : this.props.background ? this.props.background : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : ""),
-                        color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"),
+                        background: this.props.background ? this.props.background : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor)),
+                        color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color)),
                         pointerEvents: interactive ? undefined : "none",
                         fontSize: this.props.fontSize || Cast(this.layoutDoc._fontSize, "string", null),
                         fontWeight: Cast(this.layoutDoc._fontWeight, "number", null),
-- 
cgit v1.2.3-70-g09d2


From 890dd1c3d8d6a00cf3111972c20f7b5b9cb09085 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 12 Dec 2020 22:52:26 -0500
Subject: made conentScaling a DocumentView-only prop

---
 src/client/views/MainView.tsx                                |  4 ----
 src/client/views/StyleProvider.tsx                           | 12 ++++++++++--
 src/client/views/TemplateMenu.tsx                            |  1 -
 src/client/views/collections/CollectionStackingView.tsx      |  5 +++--
 src/client/views/collections/TabDocView.tsx                  |  4 ++--
 src/client/views/collections/TreeView.tsx                    |  9 +++++----
 .../collectionFreeForm/CollectionFreeFormView.tsx            |  7 ++++---
 src/client/views/nodes/AudioBox.tsx                          |  3 ++-
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx    |  3 ++-
 src/client/views/nodes/ColorBox.tsx                          |  2 +-
 src/client/views/nodes/ContentFittingDocumentView.tsx        |  9 ++++-----
 src/client/views/nodes/DocumentView.tsx                      |  7 ++++---
 src/client/views/nodes/ImageBox.tsx                          |  2 +-
 src/client/views/nodes/KeyValuePair.tsx                      |  1 -
 src/client/views/nodes/LinkAnchorBox.tsx                     |  2 +-
 src/client/views/nodes/PDFBox.tsx                            |  2 +-
 src/client/views/nodes/ScreenshotBox.tsx                     |  2 +-
 src/client/views/nodes/VideoBox.tsx                          |  2 +-
 src/client/views/nodes/WebBox.tsx                            | 10 ++++------
 src/client/views/nodes/formattedText/FormattedTextBox.tsx    |  8 ++++----
 src/client/views/pdf/PDFViewer.tsx                           |  5 ++---
 src/client/views/presentationview/PresElementBox.tsx         |  2 +-
 22 files changed, 53 insertions(+), 49 deletions(-)

(limited to 'src/client/views/nodes/LinkAnchorBox.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d55d43623..b8c7a1a73 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -465,7 +465,6 @@ export class MainView extends React.Component {
                     removeDocument={this.remButtonDoc}
                     onClick={undefined}
                     ScreenToLocalTransform={this.buttonBarXf}
-                    ContentScaling={returnOne}
                     PanelWidth={this.flyoutWidthFunc}
                     PanelHeight={this.getContentsHeight}
                     renderDepth={0}
@@ -529,7 +528,6 @@ export class MainView extends React.Component {
                 styleProvider={DefaultStyleProvider}
                 removeDocument={undefined}
                 ScreenToLocalTransform={Transform.Identity}
-                ContentScaling={returnOne}
                 PanelWidth={this.getPWidth}
                 PanelHeight={this.getPHeight}
                 renderDepth={0}
@@ -568,7 +566,6 @@ export class MainView extends React.Component {
                     focus={returnFalse}
                     PanelWidth={() => 500}
                     PanelHeight={() => 800}
-                    ContentScaling={returnOne}
                     docFilters={returnEmptyFilter}
                     docRangeFilters={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
@@ -634,7 +631,6 @@ export class MainView extends React.Component {
                             focus={returnFalse}
                             PanelWidth={() => 500}
                             PanelHeight={() => 800}
-                            ContentScaling={returnOne}
                             docFilters={returnEmptyFilter}
                             docRangeFilters={returnEmptyFilter}
                             searchFilterDocs={returnEmptyDoclist}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 6a534a979..128c3cb96 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -16,6 +16,7 @@ import React = require("react");
 import Color = require('color');
 import { listSpec } from '../../fields/Schema';
 import { MainView } from './MainView';
+import { FieldViewProps } from './nodes/FieldView';
 
 export enum StyleLayers {
     Background = "background"
@@ -59,10 +60,17 @@ function toggleBackground(doc: Doc) {
     }), "toggleBackground");
 }
 
+export function testDocProps(toBeDetermined: any): toBeDetermined is DocumentViewProps {
+    if (toBeDetermined.ContentScaling) {
+        return true;
+    }
+    return false;
+}
+
 //
 // a preliminary implementation of a dash style sheet for setting rendering properties of documents nested within a Tab
 // 
-export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any {
+export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string): any {
     switch (property.split(":")[0]) {
         case StyleProp.DocContents: return undefined;
         case StyleProp.WidgetColor: return darkScheme() ? "lightgrey" : "dimgrey";
@@ -121,7 +129,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                     `${darkScheme() ? "rgb(30, 32, 31) " : "#9c9396 "} ${StrCast(doc.boxShadow, "0.2vw 0.2vw 0.8vw")}`;
                 default:
                     return doc.z ? `#9c9396  ${StrCast(doc?.boxShadow, "10px 10px 0.9vw")}` :  // if it's a floating doc, give it a big shadow
-                        backgroundHalo(doc) && doc.type !== DocumentType.INK ? (`${props?.styleProvider?.(doc, props, StyleProp.BackgroundColor)} ${StrCast(doc.boxShadow, `0vw 0vw ${(isBackground() ? 100 : 50) / (props?.ContentScaling() || 1)}px`)}`) :  // if it's just in a cluster, make the shadown roughly match the cluster border extent
+                        backgroundHalo(doc) && doc.type !== DocumentType.INK ? (`${props?.styleProvider?.(doc, props, StyleProp.BackgroundColor)} ${StrCast(doc.boxShadow, `0vw 0vw ${(isBackground() ? 100 : 50) / ((testDocProps(props) && props?.ContentScaling()) || 1)}px`)}`) :  // if it's just in a cluster, make the shadown roughly match the cluster border extent
                             isBackground() ? undefined :  // if it's a background & has a cluster color, make the shadow spread really big
                                 StrCast(doc.boxShadow, "");
             }
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 203274681..ddba71dd2 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -139,7 +139,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                 dropAction={undefined}
                 active={returnTrue}
                 parentActive={returnFalse}
-                ContentScaling={returnOne}
                 bringToFront={emptyFunction}
                 focus={emptyFunction}
                 whenActiveChanged={emptyFunction}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index e8165f673..510e47b20 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -29,6 +29,7 @@ import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewField
 import { CollectionSubView } from "./CollectionSubView";
 import { CollectionViewType } from "./CollectionView";
 import { StyleProp } from "../StyleProvider";
+import { FieldViewProps } from "../nodes/FieldView";
 const _global = (window /* browser */ || global /* node */) as any;
 
 type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>;
@@ -61,7 +62,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
     @computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; }
     @computed get showAddAGroup() { return (this.pivotField && (this.chromeStatus !== 'view-mode' && this.chromeStatus !== 'disabled')); }
     @computed get columnWidth() {
-        return Math.min(this.props.PanelWidth() / this.props.ContentScaling() /* / NumCast(this.layoutDoc._viewScale, 1)*/ - 2 * this.xMargin,
+        return Math.min(this.props.PanelWidth() /* / NumCast(this.layoutDoc._viewScale, 1)*/ - 2 * this.xMargin,
             this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this.props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250));
     }
     @computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; }
@@ -189,7 +190,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
     getDisplayDoc(doc: Doc, dxf: () => Transform, width: () => number) {
         const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc;
         const height = () => this.getDocHeight(doc);
-        const styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
+        const styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => {
             if (property === StyleProp.Opacity && doc) {
                 if (this.props.childOpacity) {
                     return this.props.childOpacity();
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 8b5143013..fe929abc5 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -33,6 +33,7 @@ import { List } from '../../../fields/List';
 import { DocumentType } from '../../documents/DocumentTypes';
 import Color = require('color');
 import { StyleProp, DefaultStyleProvider, DefaultLayerProvider, StyleLayers } from '../StyleProvider';
+import { FieldViewProps } from '../nodes/FieldView';
 const _global = (window /* browser */ || global /* node */) as any;
 
 interface TabDocViewProps {
@@ -352,7 +353,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     addDocument={returnFalse}
                     moveDocument={returnFalse}
                     removeDocument={returnFalse}
-                    ContentScaling={returnOne}
                     PanelWidth={this.returnMiniSize}
                     PanelHeight={this.returnMiniSize}
                     ScreenToLocalTransform={this.ScreenToLocalTransform}
@@ -389,7 +389,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     active = () => this._isActive;
 
 
-    public static miniStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
+    public static miniStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string): any => {
         if (doc) {
             switch (property.split(":")[0]) {
                 default: return DefaultStyleProvider(doc, props, property);
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 843bec6de..11ff4ca3b 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -29,7 +29,8 @@ import { CollectionTreeView } from './CollectionTreeView';
 import { CollectionView, CollectionViewType } from './CollectionView';
 import "./TreeView.scss";
 import React = require("react");
-import { StyleProp } from '../StyleProvider';
+import { StyleProp, testDocProps } from '../StyleProvider';
+import { FieldViewProps } from '../nodes/FieldView';
 
 export interface TreeViewProps {
     document: Doc;
@@ -497,13 +498,13 @@ export class TreeView extends React.Component<TreeViewProps> {
             e.preventDefault();
         }
     }
-    titleStyleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => {
+    titleStyleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps | FieldViewProps>, property: string): any => {
         if (!doc || doc !== this.doc) return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
 
         switch (property.split(":")[0]) {
             case StyleProp.Opacity: return this.outlineMode ? undefined : 1;
             case StyleProp.BackgroundColor: return StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
-            case StyleProp.DocContents: return !props?.treeViewDoc ? (null) :
+            case StyleProp.DocContents: return testDocProps(props) && !props?.treeViewDoc ? (null) :
                 <div className="treeView-label" style={{    // just render a title for a tree view label (identified by treeViewDoc being set in 'props')
                     maxWidth: props?.PanelWidth() || undefined,
                     background: props?.styleProvider?.(doc, props, StyleProp.BackgroundColor),
@@ -513,7 +514,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             case StyleProp.Decorations: return (null);
         }
     }
-    embeddedStyleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => {
+    embeddedStyleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps | FieldViewProps>, property: string): any => {
         if (property.startsWith(StyleProp.Decorations)) return (null);
         return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b8576681d..3f7eb24f8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -49,6 +49,7 @@ import React = require("react");
 import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
 import { StyleProp, StyleLayers } from "../../StyleProvider";
 import { DocumentDecorations } from "../../DocumentDecorations";
+import { FieldViewProps } from "../../nodes/FieldView";
 
 export const panZoomSchema = createSchema({
     _panX: "number",
@@ -116,7 +117,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && (this.props.ContainingCollectionView?.active() || this.props.active()); }
     @computed get fitToContentScaling() { return this.fitToContent ? NumCast(this.layoutDoc.fitToContentScaling, 1) : 1; }
     @computed get fitToContent() { return (this.props.fitToBox || this.Document._fitToBox) && !this.isAnnotationOverlay; }
-    @computed get parentScaling() { return this.props.ContentScaling && this.fitToContent ? this.props.ContentScaling() : 1; }
+    @computed get parentScaling() { return 1; }
     @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); }
     @computed get nativeWidth() { return this.fitToContent ? 0 : returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.Document)); }
     @computed get nativeHeight() { return this.fitToContent ? 0 : returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.Document)); }
@@ -405,7 +406,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         }
     }
 
-    getClusterColor = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => {
+    getClusterColor = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => {
         let styleProp = this.props.styleProvider?.(doc, props, property);  // bcz: check 'props'  used to be renderDepth + 1
         if (property !== StyleProp.BackgroundColor) return styleProp;
         const cluster = NumCast(doc?.cluster);
@@ -1277,7 +1278,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @undoBatch
     @action
     toggleNativeDimensions = () => {
-        Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth?.() || 0, this.props.NativeHeight?.() || 0);
+        Doc.toggleNativeDimensions(this.layoutDoc, 1, this.props.NativeWidth?.() || 0, this.props.NativeHeight?.() || 0);
     }
 
     @undoBatch
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index b495cdd1b..7b721786f 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -539,7 +539,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
 
     rangeScript = () => AudioBox.RangeScript;
     labelScript = () => AudioBox.LabelScript;
-    static audioStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
+    static audioStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => {
         if (property === StyleProp.BackgroundColor) return "transparent";
         if (property === StyleProp.PointerEvents) return "none";
     }
@@ -554,6 +554,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
                 rootSelected={returnFalse}
                 LayoutTemplate={undefined}
                 ContainingCollectionDoc={this.props.Document}
+                ContentScaling={returnOne}
                 removeDocument={this.removeDocument}
                 parentActive={returnTrue}
                 onClick={this.layoutDoc.playOnClick ? script : undefined}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 1b47f4551..3f473460f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -17,6 +17,7 @@ import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
 import { DocumentView, DocumentViewProps } from "./DocumentView";
 import React = require("react");
 import { StyleProp } from "../StyleProvider";
+import { FieldViewProps } from "./FieldView";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined;
@@ -59,7 +60,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     @computed get nativeWidth() { return returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, undefined, this.freezeDimensions)); }
     @computed get nativeHeight() { return returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, undefined, this.freezeDimensions)); }
 
-    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
+    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => {
         if (property === StyleProp.Opacity && doc === this.layoutDoc) return this.Opacity; // only change the opacity for this specific document, not its children
         return this.props.styleProvider?.(doc, props, property);
     }
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 4fb350b55..52236a648 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -57,7 +57,7 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument
         const selDoc = SelectionManager.SelectedDocuments()?.[0]?.rootDoc;
         return <div className={`colorBox-container${this.active() ? "-interactive" : ""}`}
             onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()} onClick={e => { (e.nativeEvent as any).stuff = true; e.stopPropagation(); }}
-            style={{ transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} >
+            style={{ width: `${100}%`, height: `${100}%` }} >
 
             <SketchPicker onChange={ColorBox.switchColor} presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
                 color={StrCast(ActiveInkPen()?.backgroundColor,
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 122231f47..f2540dfa7 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -1,13 +1,12 @@
 import React = require("react");
-import { computed, observable, action } from "mobx";
+import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, WidthSym, HeightSym } from "../../../fields/Doc";
+import { Doc } from "../../../fields/Doc";
 import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, OmitKeys, returnVal, returnOne } from "../../../Utils";
+import { emptyFunction, OmitKeys, returnOne, returnVal } from "../../../Utils";
 import { DocumentView, DocumentViewProps } from "../nodes/DocumentView";
-import "./ContentFittingDocumentView.scss";
 import { StyleProp } from "../StyleProvider";
-import { StrCast } from "../../../fields/Types";
+import "./ContentFittingDocumentView.scss";
 
 interface ContentFittingDocumentViewProps {
     dontCenter?: "x" | "y" | "xy";
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c1ba1c73a..10b406fd4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -43,10 +43,11 @@ import { TaskCompletionBox } from './TaskCompletedBox';
 import React = require("react");
 import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView";
 import { StyleProp, StyleLayers } from "../StyleProvider";
+import { FieldViewProps } from "./FieldView";
 
 export type DocAfterFocusFunc = (notFocused: boolean) => boolean;
 export type DocFocusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, focused?: boolean) => void;
-export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => any;
+export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => any;
 export interface DocumentViewSharedProps {
     renderDepth: number;
     Document: Doc;
@@ -58,7 +59,6 @@ export interface DocumentViewSharedProps {
     PanelHeight: () => number;
     NativeWidth?: () => number;
     NativeHeight?: () => number;
-    ContentScaling: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal
     layerProvider?: (doc: Doc, assign?: boolean) => boolean;
     styleProvider?: StyleProviderFunc;
     focus: DocFocusFunc;
@@ -93,6 +93,7 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
     dragDivName?: string;
     contentPointerEvents?: string;
     radialMenu?: String[];
+    ContentScaling: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal
     contentFittingScaling?: () => number;// scaling done outside the document view (eg in ContentFittingDocumentView) to fit contents into panel (needed for ScreenToLocal but not needed by DocumentView to scale its content)
     LayoutTemplate?: () => Opt<Doc>;
     contextMenuItems?: () => { script: ScriptField, label: string }[];
@@ -972,7 +973,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     hideLinkAnchor = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && (doc.hidden = true), true)
     anchorPanelWidth = () => this.props.PanelWidth() || 1;
     anchorPanelHeight = () => this.props.PanelHeight() || 1;
-    anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
+    anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string): any => {
         switch (property.split(":")[0]) {
             case StyleProp.BackgroundColor: return "transparent";
             case StyleProp.HideLinkButton: return true;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 44df00709..374f964e3 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -67,7 +67,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
     @observable static _showControls: boolean;
     @observable uploadIcon = uploadIcons.idle;
 
-    @computed get contentScaling() { return this.props.ContentScaling(); }
+    @computed get contentScaling() { return 1; }
 
     protected createDropTarget = (ele: HTMLDivElement) => {
         this._dropDisposer?.();
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index aaf544c50..3c10cc5fe 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -76,7 +76,6 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             PanelHeight: this.props.PanelHeight,
             addDocTab: returnFalse,
             pinToPres: returnZero,
-            ContentScaling: returnOne
         };
         const contents = <FieldView {...props} />;
         // let fieldKey = Object.keys(props.Document).indexOf(props.fieldKey) !== -1 ? props.fieldKey : "(" + props.fieldKey + ")";
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index abefc6561..d0048c67b 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -146,7 +146,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
                 background,
                 left: `calc(${x}% - ${small ? 2.5 : 7.5}px)`,
                 top: `calc(${y}% - ${small ? 2.5 : 7.5}px)`,
-                transform: `scale(${anchorScale / this.props.ContentScaling()})`
+                transform: `scale(${anchorScale})`
             }} >
             {!this._editing && !this._forceOpen ? (null) :
                 <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout} open={this._forceOpen ? true : undefined} onOpen={() => this._isOpen = true} onClose={action(() => this._isOpen = this._forceOpen = this._editing = false)}>
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index a2f3fb2d1..b34c7966b 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -183,7 +183,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
         ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
     }
 
-    @computed get contentScaling() { return this.props.ContentScaling(); }
+    @computed get contentScaling() { return 1; }
     @computed get renderTitleBox() {
         const classname = "pdfBox" + (this.active() ? "-interactive" : "");
         return <div className={classname} >
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 713511a94..456de7ede 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -167,7 +167,7 @@ export class ScreenshotBox extends ViewBoxBaseComponent<FieldViewProps, Screensh
     contentFunc = () => [this.content];
     render() {
         return (<div className="videoBox" onContextMenu={this.specificContextMenu}
-            style={{ transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} >
+            style={{ width: `${100}%`, height: `${100}%` }} >
             <div className="videoBox-viewer" >
                 <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
                     PanelHeight={this.props.PanelHeight}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 90a5dee23..893236857 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -413,7 +413,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
         return this.addDocument(doc);
     }
 
-    @computed get contentScaling() { return this.props.ContentScaling(); }
+    @computed get contentScaling() { return 1; }
     contentFunc = () => [this.youtubeVideoId ? this.youtubeContent : this.content];
     render() {
         return (<div className="videoBox" onContextMenu={this.specificContextMenu}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 47464d794..511e8f537 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -457,7 +457,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
 
         return (<>
             <div className={"webBox-cont" + (this.props.isSelected() && Doc.GetSelectedTool() === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")}
-                style={{ width: NumCast(this.layoutDoc[this.fieldKey + "-contentWidth"]) || (Number.isFinite(this.props.ContentScaling()) ? `${Math.max(100, 100 / this.props.ContentScaling())}% ` : "100%") }}
+                style={{ width: "100%" }}
                 onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
                 {view}
             </div>
@@ -648,14 +648,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     marqueeing = () => this._marqueeing;
     scrollXf = () => this.props.ScreenToLocalTransform().translate(NumCast(this.layoutDoc._scrollLeft), NumCast(this.layoutDoc._scrollTop));
     render() {
-        const scaling = Number.isFinite(this.props.ContentScaling()) ? this.props.ContentScaling() || 1 : 1;
         return (<div className="webBox" ref={this._mainCont} >
             <div className={`webBox-container`}
                 style={{
                     position: undefined,
-                    transform: `scale(${scaling})`,
-                    width: `${100 / scaling}% `,
-                    height: `${100 / scaling}% `,
+                    width: `${100}% `,
+                    height: `${100}% `,
                     pointerEvents: this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined
                 }}
                 onContextMenu={this.specificContextMenu}>
@@ -663,7 +661,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                 {this.content}
                 <div className={"webBox-outerContent"} ref={this._outerRef}
                     style={{
-                        width: `${Math.max(100, 100 / scaling)}% `,
+                        width: `${100}% `,
                         pointerEvents: this.layoutDoc.isAnnotating && this.props.layerProvider?.(this.layoutDoc) !== false ? "all" : "none"
                     }}
                     onWheel={e => {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 0beea2bcd..feb5e63b2 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -581,7 +581,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     @undoBatch
     @action
     toggleNativeDimensions = () => {
-        Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth?.() || 0, this.props.NativeHeight?.() || 0);
+        Doc.toggleNativeDimensions(this.layoutDoc, 1, this.props.NativeWidth?.() || 0, this.props.NativeHeight?.() || 0);
     }
 
     public static get DefaultLayout(): Doc | string | undefined {
@@ -1609,7 +1609,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             />;
     }
 
-    sidebarContentScaling = () => this.props.ContentScaling() * NumCast(this.layoutDoc._viewScale, 1);
+    sidebarContentScaling = () => NumCast(this.layoutDoc._viewScale, 1);
     @computed get sidebarCollection() {
         const fitToBox = this.props.Document._fitToBox;
         const collectionProps: SubCollectionViewProps & collectionFreeformViewProps = {
@@ -1647,13 +1647,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); }
     sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth();
-    sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()) / this.props.ContentScaling(), 0).scale(1 / NumCast(this.layoutDoc._viewScale, 1));
+    sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()), 0).scale(1 / NumCast(this.layoutDoc._viewScale, 1));
     @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); }
     render() {
         TraceMobx();
         const selected = this.props.isSelected();
         const active = this.active();
-        const scale = this.props.hideOnLeave ? 1 : this.props.ContentScaling() * NumCast(this.layoutDoc._viewScale, 1);
+        const scale = this.props.hideOnLeave ? 1 : NumCast(this.layoutDoc._viewScale, 1);
         const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
         const interactive = (Doc.GetSelectedTool() === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || this.props.layerProvider?.(this.layoutDoc) !== false);
         if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(() => FormattedTextBoxComment.Hide());
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 6b7a93747..fc547c60f 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -370,7 +370,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
     @action
     scrollToAnnotation = (scrollToAnnotation: Doc) => {
         if (scrollToAnnotation) {
-            const offset = (this.props.PanelHeight() / this.props.ContentScaling()) / 2;
+            const offset = this.props.PanelHeight() / 2;
             this._mainCont.current && smoothScroll(500, this._mainCont.current, NumCast(scrollToAnnotation.y) - offset);
             Doc.linkFollowHighlight(scrollToAnnotation);
         }
@@ -737,7 +737,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
     @computed get pdfViewerDiv() {
         return <div className={"pdfViewerDash-text" + ((this.props.isSelected() || this.props.isChildActive()) ? "-selected" : "")} ref={this._viewer} />;
     }
-    @computed get contentScaling() { return this.props.ContentScaling(); }
+    @computed get contentScaling() { return 1; }
     @computed get standinViews() {
         return <>
             {this._showCover ? this.getCoverImage() : (null)}
@@ -758,7 +758,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
                 overflowX: this._zoomed !== 1 ? "scroll" : undefined,
                 width: !this.props.Document._fitWidth && (window.screen.width > 600) ? Doc.NativeWidth(this.props.Document) : `${100 / this.contentScaling}%`,
                 height: !this.props.Document._fitWidth && (window.screen.width > 600) ? Doc.NativeHeight(this.props.Document) : `${100 / this.contentScaling}%`,
-                transform: `scale(${this.props.ContentScaling()})`
             }}  >
             {this.pdfViewerDiv}
             {this.annotationLayer}
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 3b6b0259d..8615ba81d 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -78,7 +78,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
     // embedWidth = () => this.props.PanelWidth();
     // embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight);
     embedWidth = (): number => this.props.PanelWidth() - 35;
-    styleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => {
+    styleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps | FieldViewProps>, property: string): any => {
         if (property === StyleProp.Opacity) return 1;
         return this.props.styleProvider?.(doc, props, property);
     }
-- 
cgit v1.2.3-70-g09d2


From 043fe3cb67003ddd0bc309b85e2b0dad353ed629 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 13 Dec 2020 13:58:56 -0500
Subject: more props cleanup.  Got rid of ItemBackgroundColor in favor of
 having StyleProvider choose style based on whether it's givena DocViewProp or
 a FieldViewProp.

---
 src/client/views/DocComponent.tsx                  |  3 +-
 src/client/views/PropertiesView.tsx                |  2 +-
 src/client/views/StyleProvider.tsx                 | 41 ++++++++++------------
 .../views/collections/CollectionSchemaView.tsx     |  2 +-
 .../views/collections/CollectionStackingView.tsx   |  2 +-
 .../views/collections/CollectionTimeView.tsx       |  2 +-
 src/client/views/collections/SchemaTable.tsx       |  2 +-
 src/client/views/collections/TabDocView.tsx        |  2 +-
 src/client/views/collections/TreeView.tsx          |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  7 ++--
 .../collectionGrid/CollectionGridView.tsx          |  2 +-
 .../CollectionMulticolumnView.tsx                  |  2 +-
 .../CollectionMultirowView.tsx                     |  2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  6 ++--
 src/client/views/nodes/ComparisonBox.tsx           |  9 +++--
 src/client/views/nodes/DocHolderBox.tsx            |  4 +--
 src/client/views/nodes/DocumentContentsView.tsx    | 19 +++++++++-
 src/client/views/nodes/DocumentView.tsx            | 27 ++++++++------
 src/client/views/nodes/FieldView.tsx               |  1 -
 src/client/views/nodes/FilterBox.tsx               |  1 -
 src/client/views/nodes/FontIconBox.tsx             |  2 +-
 src/client/views/nodes/LinkAnchorBox.scss          |  1 -
 src/client/views/nodes/LinkAnchorBox.tsx           |  5 +--
 src/client/views/nodes/LinkDocPreview.tsx          |  3 +-
 .../views/nodes/formattedText/DashDocView.tsx      |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  4 +--
 .../formattedText/FormattedTextBoxComment.tsx      |  2 +-
 .../views/nodes/formattedText/RichTextSchema.tsx   |  2 +-
 .../views/presentationview/PresElementBox.tsx      |  2 +-
 29 files changed, 92 insertions(+), 69 deletions(-)

(limited to 'src/client/views/nodes/LinkAnchorBox.tsx')

diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index d6116fd23..2c7d15ae0 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -14,6 +14,7 @@ import { GetEffectiveAcl, SharingPermissions, distributeAcls, denormalizeEmail }
 interface DocComponentProps {
     Document: Doc;
     LayoutTemplate?: () => Opt<Doc>;
+    LayoutTemplateString?: string;
 }
 export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: Doc) => T) {
     class Component extends Touchable<P> {
@@ -22,7 +23,7 @@ export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: D
         // This is the "The Document" -- it encapsulates, data, layout, and any templates
         @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; }
         // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
-        @computed get layoutDoc() { return Doc.Layout(this.props.Document, this.props.LayoutTemplate?.()); }
+        @computed get layoutDoc() { return this.props.LayoutTemplateString ? this.props.Document : Doc.Layout(this.props.Document, this.props.LayoutTemplate?.()); }
         // This is the data part of a document -- ie, the data that is constant across all views of the document
         @computed get dataDoc() { return this.props.Document[DataSym] as Doc; }
 
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index eb7df0248..24c6c6d71 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -271,7 +271,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                     renderDepth={1}
                     rootSelected={returnFalse}
                     styleProvider={DefaultStyleProvider}
-                    fitToBox={true}
+                    fitDocToPanel={true}
                     freezeDimensions={true}
                     dontCenter={"y"}
                     NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 128c3cb96..28373952a 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -30,13 +30,12 @@ export enum StyleProp {
     BorderRounding = "borderRounding",    // border radius of the document view
     Color = "color",                      // foreground color of Document view items
     BackgroundColor = "backgroundColor",  // background color of a document view
-    ItemBackgroundColor = "itemBackgroundColor", // background color for <item>Box inside DocumentView
-    WidgetColor = "widgetColor",                 // color to display UI widgets on a document view -- used for the sidebar divider dragger on a text note
-    HideLinkButton = "hideLinkButton",  // hides the blue-dot link button.  used when a document acts like a button
-    LinkSource = "linkSource",      // source document of a link -- used by LinkAnchorBox
-    PointerEvents = "pointerEvents",// pointer events for DocumentView -- inherits pointer events if not specified
-    Decorations = "decorations",    // additional decoration to display above a DocumentView -- currently only used to display a Lock for making things background
-    HeaderMargin = "headerMargin",  // margin at top of documentview, typically for displaying a title -- doc contents will start below that
+    WidgetColor = "widgetColor",          // color to display UI widgets on a document view -- used for the sidebar divider dragger on a text note
+    HideLinkButton = "hideLinkButton",    // hides the blue-dot link button.  used when a document acts like a button
+    LinkSource = "linkSource",            // source document of a link -- used by LinkAnchorBox
+    PointerEvents = "pointerEvents",      // pointer events for DocumentView -- inherits pointer events if not specified
+    Decorations = "decorations",          // additional decoration to display above a DocumentView -- currently only used to display a Lock for making things background
+    HeaderMargin = "headerMargin",        // margin at top of documentview, typically for displaying a title -- doc contents will start below that
 }
 
 function darkScheme() { return BoolCast(CurrentUserUtils.ActiveDashboard?.darkScheme); }
@@ -61,22 +60,20 @@ function toggleBackground(doc: Doc) {
 }
 
 export function testDocProps(toBeDetermined: any): toBeDetermined is DocumentViewProps {
-    if (toBeDetermined.ContentScaling) {
-        return true;
-    }
-    return false;
+    return (toBeDetermined?.ContentScaling) ? true : false;
 }
 
 //
 // a preliminary implementation of a dash style sheet for setting rendering properties of documents nested within a Tab
 // 
 export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string): any {
+    const docProps = testDocProps(props);
     switch (property.split(":")[0]) {
         case StyleProp.DocContents: return undefined;
         case StyleProp.WidgetColor: return darkScheme() ? "lightgrey" : "dimgrey";
         case StyleProp.Opacity: return Cast(doc?._opacity, "number", Cast(doc?.opacity, "number", null));
         case StyleProp.Color:
-            const backColor = props?.styleProvider?.(doc, props, StyleProp.ItemBackgroundColor) || "black";
+            const backColor = props?.styleProvider?.(doc, props, StyleProp.BackgroundColor) || "black";
             const col = Color(backColor).rgb();
             const colsum = (col.red() + col.green() + col.blue());
             if (colsum / col.alpha() > 600 || col.alpha() < 0.25) return "black";
@@ -84,17 +81,17 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps |
         case StyleProp.Hidden: return BoolCast(doc?._hidden, BoolCast(doc?.hidden));
         case StyleProp.BorderRounding: return !doc ? undefined : StrCast(doc._borderRounding, StrCast(doc.borderRounding));
         case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.Masonry].includes(doc?._viewType as any) || doc?.type === DocumentType.RTF) && doc?._showTitle && !doc?._showTitleHover ? 15 : 0;
-        case StyleProp.ItemBackgroundColor:
-            const docColor: Opt<string> = StrCast(doc?._backgroundColor, StrCast(doc?.backgroundColor));
-            if (docColor) return docColor;
-            if (MainView.Instance.LastButton === doc) return darkScheme() ? "dimgrey" : "lightgrey";
-            switch (doc?.type) {
-                case DocumentType.FONTICON: return "black";
-                case DocumentType.LINK: return "lightblue";
-            }
         case StyleProp.BackgroundColor: {
             if (Doc.UserDoc().renderStyle === "comic") return "transparent";
             let docColor: Opt<string> = StrCast(doc?._backgroundColor, StrCast(doc?.backgroundColor));
+            if (!docProps) {
+                if (MainView.Instance.LastButton === doc) return darkScheme() ? "dimgrey" : "lightgrey";
+                switch (doc?.type) {
+                    case DocumentType.FONTICON: return docColor || "black";
+                    case DocumentType.LINK: return docColor || "lightblue";
+                    default: undefined;
+                }
+            }
             switch (doc?.type) {
                 case DocumentType.PRESELEMENT: docColor = docColor || (darkScheme() ? "" : ""); break;
                 case DocumentType.PRES: docColor = docColor || (darkScheme() ? "#3e3e3e" : "white"); break;
@@ -103,7 +100,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps |
                 case DocumentType.LABEL:
                 case DocumentType.INK: docColor = undefined; break;
                 case DocumentType.BUTTON: docColor = docColor || (darkScheme() ? "#2d2d2d" : "lightgray"); break;
-                case DocumentType.LINK:
+                case DocumentType.LINK: return "transparent";
                 case DocumentType.COL:
                     docColor = docColor ||
                         (Doc.IsSystem(doc) ? (darkScheme() ? "rgb(62,62,62)" : "lightgrey") :
@@ -129,7 +126,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps |
                     `${darkScheme() ? "rgb(30, 32, 31) " : "#9c9396 "} ${StrCast(doc.boxShadow, "0.2vw 0.2vw 0.8vw")}`;
                 default:
                     return doc.z ? `#9c9396  ${StrCast(doc?.boxShadow, "10px 10px 0.9vw")}` :  // if it's a floating doc, give it a big shadow
-                        backgroundHalo(doc) && doc.type !== DocumentType.INK ? (`${props?.styleProvider?.(doc, props, StyleProp.BackgroundColor)} ${StrCast(doc.boxShadow, `0vw 0vw ${(isBackground() ? 100 : 50) / ((testDocProps(props) && props?.ContentScaling()) || 1)}px`)}`) :  // if it's just in a cluster, make the shadown roughly match the cluster border extent
+                        backgroundHalo(doc) && doc.type !== DocumentType.INK ? (`${props?.styleProvider?.(doc, props, StyleProp.BackgroundColor)} ${StrCast(doc.boxShadow, `0vw 0vw ${(isBackground() ? 100 : 50) / ((docProps && (props as DocumentViewProps).ContentScaling()) || 1)}px`)}`) :  // if it's just in a cluster, make the shadown roughly match the cluster border extent
                             isBackground() ? undefined :  // if it's a background & has a cluster color, make the shadow spread really big
                                 StrCast(doc.boxShadow, "");
             }
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index c64048345..973cd5d3c 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -400,7 +400,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
                 <ContentFittingDocumentView
                     Document={this.previewDocument}
                     DataDoc={undefined}
-                    fitToBox={true}
+                    fitDocToPanel={true}
                     freezeDimensions={true}
                     dontCenter={"y"}
                     focus={emptyFunction}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 510e47b20..f64186728 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -214,7 +214,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
             NativeWidth={this.props.childIgnoreNativeSize ? returnZero : undefined}  // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
             NativeHeight={this.props.childIgnoreNativeSize ? returnZero : undefined}
             dontCenter={this.props.childIgnoreNativeSize ? "xy" : undefined}
-            fitToBox={false}
+            fitDocToPanel={false}
             dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.dontRegisterChildViews, this.props.dontRegisterView)}
             rootSelected={this.rootSelected}
             dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index c7a826669..cc625e12e 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -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}
-                fitToBox={true}
+                fitContentsToDoc={true}
                 childClickScript={this._childClickedScript}
                 viewDefDivClick={this._viewDefDivClick}
                 childFreezeDimensions={true}
diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx
index 24a67faaf..58adff6b9 100644
--- a/src/client/views/collections/SchemaTable.tsx
+++ b/src/client/views/collections/SchemaTable.tsx
@@ -570,7 +570,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
                     ref="overlay"><ContentFittingDocumentView
                         Document={this._showDoc}
                         DataDoc={this._showDataDoc}
-                        fitToBox={true}
+                        fitDocToPanel={true}
                         freezeDimensions={true}
                         focus={emptyFunction}
                         renderDepth={this.props.renderDepth}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index fe929abc5..fc2f7c522 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -365,7 +365,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     docFilters={CollectionDockingView.Instance.docFilters}
                     docRangeFilters={CollectionDockingView.Instance.docRangeFilters}
                     searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs}
-                    fitToBox={true}
+                    fitContentsToDoc={true}
                 />
                 <div className="miniOverlay" onPointerDown={this.miniDown} >
                     <div className="miniThumb" style={{ width: `${miniWidth}% `, height: `${miniHeight}% `, left: `${miniLeft}% `, top: `${miniTop}% `, }} />
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 11ff4ca3b..c03041214 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -617,7 +617,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             PanelHeight={panelHeight}
             NativeWidth={!asText && this.layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined}
             NativeHeight={!asText && this.layoutDoc.type === DocumentType.RTF ? this.rtfHeight : undefined}
-            fitToBox={!asText && this.isCollectionDoc !== undefined}
+            fitDocToPanel={!asText && this.isCollectionDoc !== undefined}
             hideTitle={asText}
             LayoutTemplateString={asText ? FormattedTextBox.LayoutString("text") : undefined}
             focus={asText ? this.refocus : returnFalse}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3f7eb24f8..403c20ba2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -76,6 +76,7 @@ export type collectionFreeformViewProps = {
     forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox)
     viewDefDivClick?: ScriptField;
     childPointerEvents?: boolean;
+    fitContentsToDoc?: boolean;
     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)
@@ -116,7 +117,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && (this.props.ContainingCollectionView?.active() || this.props.active()); }
     @computed get fitToContentScaling() { return this.fitToContent ? NumCast(this.layoutDoc.fitToContentScaling, 1) : 1; }
-    @computed get fitToContent() { return (this.props.fitToBox || this.Document._fitToBox) && !this.isAnnotationOverlay; }
+    @computed get fitToContent() { return (this.props.fitContentsToDoc || this.Document._fitToBox) && !this.isAnnotationOverlay; }
     @computed get parentScaling() { return 1; }
     @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); }
     @computed get nativeWidth() { return this.fitToContent ? 0 : returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.Document)); }
@@ -998,7 +999,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             pinToPres: this.props.pinToPres,
             whenActiveChanged: this.props.whenActiveChanged,
             parentActive: this.parentActive,
-            fitToBox: false,
+            fitDocToPanel: false,
             DataDoc: childData,
             Document: childLayout,
             ContainingCollectionView: this.props.CollectionView,
@@ -1175,7 +1176,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                         (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : undefined}
                     jitterRotation={NumCast(this.props.Document._jitterRotation) || ((Doc.UserDoc().renderStyle === "comic" ? 10 : 0))}
                     //fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
-                    fitToBox={BoolCast(this.props.childFreezeDimensions)} // bcz: check this
+                    fitDocToPanel={BoolCast(this.props.childFreezeDimensions)} // bcz: check this
                     freezeDimensions={BoolCast(this.props.childFreezeDimensions)}
                 />,
                 bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica)
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 1a4eb8b7b..36ed72596 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -169,7 +169,7 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
             PanelHeight={height}
             ContentScaling={returnOne}
             freezeDimensions={true}
-            fitToBox={true}
+            fitDocToPanel={true}
             ScreenToLocalTransform={dxf}
             onClick={this.onChildClickHandler}
             renderDepth={this.props.renderDepth + 1}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index d4913a5ed..6ed7d1dd3 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -223,7 +223,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
             renderDepth={this.props.renderDepth + 1}
             PanelWidth={width}
             PanelHeight={height}
-            fitToBox={false}
+            fitDocToPanel={false}
             rootSelected={this.rootSelected}
             dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
             onClick={this.onChildClickHandler}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 9039090d1..a4a569cc9 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -223,7 +223,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
             renderDepth={this.props.renderDepth + 1}
             PanelWidth={width}
             PanelHeight={height}
-            fitToBox={false}
+            fitDocToPanel={false}
             rootSelected={this.rootSelected}
             dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
             onClick={this.onChildClickHandler}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 3f473460f..6eb8dc8c9 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -27,7 +27,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     highlight?: boolean;
     jitterRotation: number;
     dataTransition?: string;
-    fitToBox?: boolean;
+    fitDocToPanel?: boolean;
     replica: string;
 }
 
@@ -139,7 +139,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         this.props.Document.x = NumCast(this.props.Document.x) + x;
         this.props.Document.y = NumCast(this.props.Document.y) + y;
     }
-    contentScaling = () => this.nativeWidth > 0 && !this.props.fitToBox && !this.freezeDimensions ? this.width / this.nativeWidth : 1;
+    contentScaling = () => this.nativeWidth > 0 && !this.props.fitDocToPanel && !this.freezeDimensions ? this.width / this.nativeWidth : 1;
     panelWidth = () => (this.sizeProvider?.width || this.props.PanelWidth?.());
     panelHeight = () => (this.sizeProvider?.height || this.props.PanelHeight?.());
     getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.X, -this.Y).scale(1 / this.contentScaling());
@@ -190,7 +190,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     </svg>
                 </div>}
 
-            {this.props.fitToBox ?
+            {this.props.fitDocToPanel ?
                 <ContentFittingDocumentView {...divProps} ref={action((r: ContentFittingDocumentView | null) => this._contentView = r)} /> :
                 <DocumentView {...divProps} ContentScaling={this.contentScaling} />}
         </div>;
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 0ba53dee6..1b7084ffa 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -12,7 +12,7 @@ import "./ComparisonBox.scss";
 import React = require("react");
 import { ContentFittingDocumentView } from './ContentFittingDocumentView';
 import { undoBatch } from '../../util/UndoManager';
-import { setupMoveUpEvents, emptyFunction } from '../../../Utils';
+import { setupMoveUpEvents, emptyFunction, returnOne } from '../../../Utils';
 import { SnappingManager } from '../../util/SnappingManager';
 import { DocumentViewProps } from './DocumentView';
 
@@ -74,7 +74,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C
 
     render() {
         const clipWidth = NumCast(this.layoutDoc._clipWidth) + "%";
-        const childProps: DocumentViewProps = { ...this.props, pointerEvents: "none", parentActive: this.props.active };
         const clearButton = (which: string) => {
             return <div className={`clear-button ${which}`}
                 onPointerDown={e => e.stopPropagation()} // prevent triggering slider movement in registerSliding 
@@ -85,7 +84,11 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C
         const displayDoc = (which: string) => {
             const whichDoc = Cast(this.dataDoc[`compareBox-${which}`], Doc, null);
             return whichDoc ? <>
-                <ContentFittingDocumentView {...childProps} Document={whichDoc} />
+                <ContentFittingDocumentView {...this.props}
+                    ContentScaling={returnOne}
+                    pointerEvents={"none"}
+                    parentActive={this.props.active}
+                    Document={whichDoc} />
                 {clearButton(which)}
             </> :  // placeholder image if doc is missing
                 <div className="placeholder">
diff --git a/src/client/views/nodes/DocHolderBox.tsx b/src/client/views/nodes/DocHolderBox.tsx
index 1bc7bc8d7..5aab0a4c8 100644
--- a/src/client/views/nodes/DocHolderBox.tsx
+++ b/src/client/views/nodes/DocHolderBox.tsx
@@ -124,7 +124,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
                     searchFilterDocs={this.props.searchFilterDocs}
                     ContainingCollectionView={this as any} // bcz: hack!  need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked.  currently, the up selector allows only a containing collection to be selected
                     ContainingCollectionDoc={undefined}
-                    fitToBox={true}
+                    fitDocToPanel={true}
                     styleProvider={this.props.styleProvider}
                     LayoutTemplateString={layoutTemplate}
                     LayoutTemplate={this.layoutTemplateDoc}
@@ -152,7 +152,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
                     searchFilterDocs={this.props.searchFilterDocs}
                     ContainingCollectionView={this as any} // bcz: hack!  need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked.  currently, the up selector allows only a containing collection to be selected
                     ContainingCollectionDoc={undefined}
-                    fitToBox={true}
+                    fitDocToPanel={true}
                     styleProvider={this.props.styleProvider}
                     ignoreAutoHeight={true}
                     LayoutTemplateString={layoutTemplate}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 98a1b026d..2c3e9bc88 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -138,8 +138,25 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
     }
 
     CreateBindings(onClick: Opt<ScriptField>, onInput: Opt<ScriptField>): JsxBindings {
+        const docOnlyProps = [  // these are the properties in  DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews
+            "freezeDimensions",
+            "hideTitle",
+            "fitDocToPanel",
+            "treeViewDoc",
+            "dragDivName",
+            "contentPointerEvents",
+            "radialMenu",
+            "LayoutTemplateString",
+            "LayoutTemplate",
+            "ContentScaling",
+            "contentFittingScaling",
+            "contextMenuItems",
+            "onDoubleClick",
+            "onPointerDown",
+            "onPointerUp",
+        ];
         const list = {
-            ...OmitKeys(this.props, ['NativeWidth', 'NativeHeight'], "", (obj: any) => obj.active = this.props.parentActive).omit,
+            ...OmitKeys(this.props, [...docOnlyProps, 'NativeWidth', 'NativeHeight'], "", (obj: any) => obj.active = this.props.parentActive).omit,
             RootDoc: Cast(this.layoutDoc?.rootDocument, Doc, null) || this.layoutDoc,
             Document: this.layoutDoc,
             DataDoc: this.dataDoc,
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 10b406fd4..f766976d0 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -42,7 +42,7 @@ import { RadialMenu } from './RadialMenu';
 import { TaskCompletionBox } from './TaskCompletedBox';
 import React = require("react");
 import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView";
-import { StyleProp, StyleLayers } from "../StyleProvider";
+import { StyleProp, StyleLayers, testDocProps } from "../StyleProvider";
 import { FieldViewProps } from "./FieldView";
 
 export type DocAfterFocusFunc = (notFocused: boolean) => boolean;
@@ -78,7 +78,6 @@ export interface DocumentViewSharedProps {
     bringToFront: (doc: Doc, sendToBack?: boolean) => void;
     onClick?: () => ScriptField;
     dropAction?: dropActionType;
-    LayoutTemplateString?: string;
     dontRegisterView?: boolean;
     ignoreAutoHeight?: boolean;
     pointerEvents?: string;
@@ -88,14 +87,15 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
     // properties specific to DocumentViews but not to FieldView
     freezeDimensions?: boolean;
     hideTitle?: boolean;  // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
-    fitToBox?: boolean;
+    fitDocToPanel?: boolean;
     treeViewDoc?: Doc;
     dragDivName?: string;
-    contentPointerEvents?: string;
+    contentPointerEvents?: string; // pointer events allowed for content of a document view.  eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
     radialMenu?: String[];
+    LayoutTemplateString?: string;
+    LayoutTemplate?: () => Opt<Doc>;
     ContentScaling: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal
     contentFittingScaling?: () => number;// scaling done outside the document view (eg in ContentFittingDocumentView) to fit contents into panel (needed for ScreenToLocal but not needed by DocumentView to scale its content)
-    LayoutTemplate?: () => Opt<Doc>;
     contextMenuItems?: () => { script: ScriptField, label: string }[];
     onDoubleClick?: () => ScriptField;
     onPointerDown?: () => ScriptField;
@@ -934,7 +934,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 makeLink={this.makeLink}
                 focus={this.props.focus}
                 dontRegisterView={this.props.dontRegisterView}
-                fitToBox={this.props.fitToBox}
+                fitDocToPanel={this.props.fitDocToPanel}
                 addDocument={this.props.addDocument}
                 removeDocument={this.props.removeDocument}
                 moveDocument={this.props.moveDocument}
@@ -974,11 +974,16 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     anchorPanelWidth = () => this.props.PanelWidth() || 1;
     anchorPanelHeight = () => this.props.PanelHeight() || 1;
     anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string): any => {
-        switch (property.split(":")[0]) {
-            case StyleProp.BackgroundColor: return "transparent";
-            case StyleProp.HideLinkButton: return true;
-            case StyleProp.PointerEvents: return "none";
-            case StyleProp.LinkSource: return this.props.Document;
+        if (testDocProps(props)) {
+            switch (property.split(":")[0]) {
+                case StyleProp.BackgroundColor: return "transparent"; // background of linkanchor documentView is transparent since it covers the whole document
+                case StyleProp.HideLinkButton: return true; // don't want linkAnchor documentview to show its own link button
+                case StyleProp.PointerEvents: return "none"; // don't want linkAnchor documentView to handle events (since it covers the whole document).  However, the linkAnchorBox itself is set to pointerEvent all
+            }
+        } else {
+            switch (property.split(":")[0]) {
+                case StyleProp.LinkSource: return this.props.Document; // pass the LinkSource to the LinkAnchorBox
+            }
         }
         return this.props.styleProvider?.(doc, props, property);
     }
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 057c7afae..fd2193bd8 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -16,7 +16,6 @@ import { VideoBox } from "./VideoBox";
 export interface FieldViewProps extends DocumentViewSharedProps {
     // FieldView specific props that are not part of DocumentView props
     fieldKey: string;
-    fitToBox?: boolean;
     overflow?: boolean; // bcz: would like to think this can be avoided -- need to look at further
     active: (outsideReaction?: boolean) => boolean;
     select: (isCtrlPressed: boolean) => void;
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index ffe1684d4..730ae8f10 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -204,7 +204,6 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
                     parentActive={returnFalse}
                     whenActiveChanged={returnFalse}
                     treeViewHideTitle={true}
-                    ContentScaling={returnOne}
                     focus={returnFalse}
                     treeViewHideHeaderFields={true}
                     onCheckedClick={this.scriptField}
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index b979c9017..121b9f26c 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -42,7 +42,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
     render() {
         const label = StrCast(this.rootDoc.label, StrCast(this.rootDoc.title));
         const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
-        const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ItemBackgroundColor);
+        const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
         const shape = StrCast(this.layoutDoc.iconShape, label ? "round" : "circle");
         const icon = StrCast(this.dataDoc.icon, "user") as any;
         const presSize = shape === 'round' ? 25 : 30;
diff --git a/src/client/views/nodes/LinkAnchorBox.scss b/src/client/views/nodes/LinkAnchorBox.scss
index 62ee9513c..caff369df 100644
--- a/src/client/views/nodes/LinkAnchorBox.scss
+++ b/src/client/views/nodes/LinkAnchorBox.scss
@@ -23,7 +23,6 @@
         padding-top: 1px;
     }
     .linkAnchorBox-button {
-        pointer-events: all;
         position: relative;
         display: inline-block;
     }
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index d0048c67b..f035fba33 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -119,7 +119,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
         const x = NumCast(this.rootDoc[this.fieldKey + "_x"], 100);
         const y = NumCast(this.rootDoc[this.fieldKey + "_y"], 100);
         const linkSource = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkSource);
-        const background = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.ItemBackgroundColor);
+        const background = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.BackgroundColor);
         const anchor = this.fieldKey === "anchor1" ? "anchor2" : "anchor1";
         const anchorScale = !this.dataDoc[this.fieldKey + "-useLinkSmallAnchor"] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : .25;
 
@@ -142,7 +142,8 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
                 Location: [e.clientX, e.clientY + 20]
             })}
             onPointerDown={this.onPointerDown} onClick={this.onClick} title={targetTitle} onContextMenu={this.specificContextMenu}
-            ref={this._ref} style={{
+            ref={this._ref}
+            style={{
                 background,
                 left: `calc(${x}% - ${small ? 2.5 : 7.5}px)`,
                 top: `calc(${y}% - ${small ? 2.5 : 7.5}px)`,
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index e0c8c032f..508d85e14 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -94,7 +94,7 @@ export class LinkDocPreview extends React.Component<Props> {
             :
             <ContentFittingDocumentView
                 Document={this._targetDoc}
-                fitToBox={true}
+                fitDocToPanel={true}
                 moveDocument={returnFalse}
                 rootSelected={returnFalse}
                 ScreenToLocalTransform={Transform.Identity}
@@ -125,6 +125,7 @@ export class LinkDocPreview extends React.Component<Props> {
                 position: "absolute", left: this.props.location[0],
                 top: this.props.location[1], width: this.width() + 16, height: this.height() + 16,
                 zIndex: 1000,
+                backgroundColor: "lightblue",
                 border: "8px solid white", borderRadius: "7px",
                 boxShadow: "3px 3px 1.5px grey",
                 borderBottom: "8px solid white", borderRight: "8px solid white"
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 450f0b6bc..04e94391f 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -228,7 +228,7 @@ export class DashDocView extends React.Component<IDashDocView> {
                         <DocumentView
                             Document={finalLayout}
                             DataDoc={resolvedDataDoc}
-                            fitToBox={BoolCast(dashDoc._fitToBox)}
+                            fitDocToPanel={BoolCast(dashDoc._fitToBox)}
                             addDocument={returnFalse}
                             rootSelected={this._textBox.props.isSelected}
                             removeDocument={this.removeDoc}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index feb5e63b2..c6036330d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1576,7 +1576,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight, 0);
             const dh = NumCast(this.rootDoc._height, 0);
             const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + this.titleHeight);
-            if (!this.props.LayoutTemplateString && this.rootDoc !== this.layoutDoc.doc && !this.layoutDoc.resolvedDataDoc) {
+            if (this.rootDoc !== this.layoutDoc.doc && !this.layoutDoc.resolvedDataDoc) {
                 // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template.  So set a timeout and hope its arrived...
                 console.log("Delayed height adjustment...");
                 setTimeout(() => {
@@ -1624,7 +1624,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             scaleField: this.annotationKey + "-scale",
             isAnnotationOverlay: true,
             fieldKey: this.annotationKey,
-            fitToBox: fitToBox,
+            fitContentsToDoc: fitToBox,
             select: emptyFunction,
             active: this.annotationsActive,
             ContentScaling: this.sidebarContentScaling,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 1d7b7ec91..1ee217d03 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -297,7 +297,7 @@ export class FormattedTextBoxComment {
                                     <div className="FormattedTextBoxComment-preview-wrapper">
                                         <ContentFittingDocumentView
                                             Document={target}
-                                            fitToBox={true}
+                                            fitDocToPanel={true}
                                             moveDocument={returnFalse}
                                             rootSelected={returnFalse}
                                             ScreenToLocalTransform={Transform.Identity}
diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx
index e94829769..683fa1029 100644
--- a/src/client/views/nodes/formattedText/RichTextSchema.tsx
+++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx
@@ -137,7 +137,7 @@ export class DashDocView {
                 ReactDOM.render(<DocumentView
                     Document={finalLayout}
                     DataDoc={resolvedDataDoc}
-                    fitToBox={BoolCast(dashDoc._fitToBox)}
+                    fitDocToPanel={BoolCast(dashDoc._fitToBox)}
                     addDocument={returnFalse}
                     rootSelected={this._textBox.props.isSelected}
                     removeDocument={removeDoc}
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 8615ba81d..6d51df01d 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -92,7 +92,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
                 <ContentFittingDocumentView
                     Document={this.targetDoc}
                     DataDoc={this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
-                    fitToBox={true}
+                    fitDocToPanel={true}
                     styleProvider={this.styleProvider}
                     rootSelected={returnTrue}
                     addDocument={returnFalse}
-- 
cgit v1.2.3-70-g09d2


From 3484b7d92dfedf6319a0047f16bb0ce9cd59a625 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 16 Dec 2020 16:53:05 -0500
Subject: cleaned up some link following code.  fixed problem following links
 from LinkAnchorBox's

---
 src/client/util/DocumentManager.ts                 | 36 +----------
 src/client/util/LinkManager.ts                     | 73 +++++++++++++++++++++-
 src/client/views/MainView.tsx                      |  5 +-
 src/client/views/StyleProvider.tsx                 |  1 -
 src/client/views/collections/CollectionMapView.tsx | 12 ++--
 src/client/views/linking/LinkMenuItem.tsx          | 46 ++------------
 src/client/views/nodes/DocumentView.tsx            | 44 +++----------
 src/client/views/nodes/LinkAnchorBox.tsx           | 30 ++++-----
 src/client/views/nodes/LinkDocPreview.tsx          | 18 +++---
 .../views/nodes/formattedText/FormattedTextBox.tsx |  5 +-
 .../formattedText/FormattedTextBoxComment.tsx      |  2 +-
 src/client/views/pdf/Annotation.tsx                | 12 ++--
 12 files changed, 123 insertions(+), 161 deletions(-)

(limited to 'src/client/views/nodes/LinkAnchorBox.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 1c81cf26c..1609e0af7 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,14 +1,12 @@
 import { action, observable } from 'mobx';
 import { Doc, DocListCast, DocListCastAsync, Opt } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
-import { Cast, NumCast, StrCast } from '../../fields/Types';
+import { Cast, NumCast } from '../../fields/Types';
 import { returnFalse } from '../../Utils';
 import { DocumentType } from '../documents/DocumentTypes';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
 import { CollectionView } from '../views/collections/CollectionView';
 import { DocumentView } from '../views/nodes/DocumentView';
-import { FormattedTextBoxComment } from '../views/nodes/formattedText/FormattedTextBoxComment';
-import { LinkDocPreview } from '../views/nodes/LinkDocPreview';
 import { LinkManager } from './LinkManager';
 import { Scripting } from './Scripting';
 
@@ -223,37 +221,5 @@ export class DocumentManager {
         }
     }
 
-    public async FollowLink(link: Opt<Doc>, doc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) {
-        LinkDocPreview.TargetDoc = undefined;
-        FormattedTextBoxComment.linkDoc = undefined;
-        const linkDocs = link ? [link] : DocListCast(doc.links);
-        const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor1
-        const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor2
-        const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0);
-        const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0);
-        const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView;
-        const linkDocList = linkWithoutTargetDoc ? [linkWithoutTargetDoc] : (traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs);
-        const followLinks = linkDocList.length ? (doc.isPushpin ? linkDocList : [linkDocList[0]]) : [];
-        followLinks.forEach(async linkDoc => {
-            if (linkDoc) {
-                const target = (doc === linkDoc.anchor1 ? linkDoc.anchor2 : doc === linkDoc.anchor2 ? linkDoc.anchor1 :
-                    (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc;
-                const targetTimecode = (doc === linkDoc.anchor1 ? Cast(linkDoc.anchor2_timecode, "number") :
-                    doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number") :
-                        (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number")));
-                if (target) {
-                    const containerDoc = (await Cast(target.annotationOn, Doc)) || target;
-                    containerDoc._currentTimecode = targetTimecode;
-                    const targetContext = await target?.context as Doc;
-                    const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
-                    DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "add:right"), finished), targetNavContext, linkDoc, undefined, doc, finished);
-                } else {
-                    finished?.();
-                }
-            } else {
-                finished?.();
-            }
-        });
-    }
 }
 Scripting.addGlobal(function DocFocus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, true)); });
\ No newline at end of file
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 38e81cf99..accf53676 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,7 +1,12 @@
+import { computedFn } from "mobx-utils";
 import { Doc, DocListCast, Opt } from "../../fields/Doc";
-import { Cast, StrCast } from "../../fields/Types";
+import { BoolCast, Cast, StrCast } from "../../fields/Types";
+import { DocFocusFunc, DocumentViewSharedProps } from "../views/nodes/DocumentView";
+import { FormattedTextBoxComment } from "../views/nodes/formattedText/FormattedTextBoxComment";
+import { LinkDocPreview } from "../views/nodes/LinkDocPreview";
+import { CreateViewFunc, DocumentManager } from "./DocumentManager";
 import { SharingManager } from "./SharingManager";
-import { computedFn } from "mobx-utils";
+import { UndoManager } from "./UndoManager";
 
 /* 
  * link doc: 
@@ -89,4 +94,68 @@ export class LinkManager {
         if (Doc.AreProtosEqual(anchor, a2.annotationOn as Doc)) return a1;
         if (Doc.AreProtosEqual(anchor, linkDoc)) return linkDoc;
     }
+
+
+    // 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 a tab, on the right, or in place
+    // depending on the followLinkLocation property of the source (or the link itself as a fallback);
+    public static FollowLink = async (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>) => {
+            const targetFocusAfterDocFocus = () => {
+                const where = StrCast(sourceDoc.followLinkLocation) || followLoc;
+                const hackToCallFinishAfterFocus = () => {
+                    finished && setTimeout(finished, 0); // finished() needs to be called right after hackToCallFinishAfterFocus(), but there's no callback for that so we use the hacky timeout.
+                    return false; // we must return false here so that the zoom to the document is not reversed.  If it weren't for needing to call finished(), we wouldn't need this function at all since not having it is equivalent to returning false
+                };
+                const addTab = docViewProps.addDocTab(doc, where);
+                addTab && setTimeout(() => {
+                    const targDocView = DocumentManager.Instance.getFirstDocumentView(doc);
+                    targDocView?.props.focus(doc, BoolCast(sourceDoc.followLinkZoom, false), undefined, hackToCallFinishAfterFocus);
+                }); //  add the target and focus on it.
+                return where !== "inPlace" || addTab; // return true to reset the initial focus&zoom (return false for 'inPlace' since resetting the initial focus&zoom will negate the zoom into the target)
+            };
+            if (!sourceDoc.followLinkZoom) {
+                targetFocusAfterDocFocus();
+            } else {
+                // first focus & zoom onto this (the clicked document).  Then execute the function to focus on the target
+                docViewProps.focus(sourceDoc, BoolCast(sourceDoc.followLinkZoom, true), 1, targetFocusAfterDocFocus);
+            }
+        };
+        await LinkManager.traverseLink(linkDoc, sourceDoc, createViewFunc, BoolCast(sourceDoc.followLinkZoom, false), docViewProps.ContainingCollectionDoc, batch.end, altKey ? true : undefined);
+    }
+    public static async traverseLink(link: Opt<Doc>, doc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) {
+        LinkDocPreview.TargetDoc = undefined;
+        FormattedTextBoxComment.linkDoc = undefined;
+        const linkDocs = link ? [link] : DocListCast(doc.links);
+        const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor1
+        const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor2
+        const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0);
+        const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0);
+        const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView;
+        const linkDocList = linkWithoutTargetDoc ? [linkWithoutTargetDoc] : (traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs);
+        const followLinks = linkDocList.length ? (doc.isPushpin ? linkDocList : [linkDocList[0]]) : [];
+        followLinks.forEach(async linkDoc => {
+            if (linkDoc) {
+                const target = (doc === linkDoc.anchor1 ? linkDoc.anchor2 : doc === linkDoc.anchor2 ? linkDoc.anchor1 :
+                    (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc;
+                const targetTimecode = (doc === linkDoc.anchor1 ? Cast(linkDoc.anchor2_timecode, "number") :
+                    doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number") :
+                        (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number")));
+                if (target) {
+                    const containerDoc = (await Cast(target.annotationOn, Doc)) || target;
+                    containerDoc._currentTimecode = targetTimecode;
+                    const targetContext = await target?.context as Doc;
+                    const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
+                    DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "add:right"), finished), targetNavContext, linkDoc, undefined, doc, finished);
+                } else {
+                    finished?.();
+                }
+            } else {
+                finished?.();
+            }
+        });
+    }
+
 }
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 23c65fa53..0390bdf52 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -578,9 +578,8 @@ export class MainView extends React.Component {
             <CollectionMenu />
             {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
             {DocumentLinksButton.EditLink ? <LinkMenu docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (null)}
-            {LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} styleProvider={DefaultStyleProvider}
-                linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href}
-                addDocTab={LinkDocPreview.LinkInfo.addDocTab} /> : (null)}
+            {LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} docprops={LinkDocPreview.LinkInfo.docprops}
+                linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href} /> : (null)}
             <GestureOverlay >
                 {this.mainContent}
             </GestureOverlay>
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 83192164f..dd406abed 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -71,7 +71,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps |
 
     switch (property.split(":")[0]) {
         case StyleProp.DocContents: return undefined;
-        case StyleProp.LinkSource: return isAnchor && docProps?.Document; // pass the LinkSource to the LinkAnchorBox
         case StyleProp.WidgetColor: return darkScheme() ? "lightgrey" : "dimgrey";
         case StyleProp.Opacity: return Cast(doc?._opacity, "number", Cast(doc?.opacity, "number", null));
         case StyleProp.HideLinkButton: return isAnchor || props?.dontRegisterView || (!selected && (doc?.isLinkButton || doc?.hideLinkButton));
diff --git a/src/client/views/collections/CollectionMapView.tsx b/src/client/views/collections/CollectionMapView.tsx
index 1af1a05aa..92288c1f2 100644
--- a/src/client/views/collections/CollectionMapView.tsx
+++ b/src/client/views/collections/CollectionMapView.tsx
@@ -1,16 +1,16 @@
-import { GoogleApiWrapper, Map as GeoMap, IMapProps, Marker } from "google-maps-react";
+import { GoogleApiWrapper, IMapProps, Map as GeoMap, Marker } from "google-maps-react";
+import { action, computed, Lambda, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, Opt, DocListCast, FieldResult, Field } from "../../../fields/Doc";
+import { Doc, DocListCast, Field, FieldResult, Opt } from "../../../fields/Doc";
 import { documentSchema } from "../../../fields/documentSchemas";
 import { Id } from "../../../fields/FieldSymbols";
 import { makeInterface } from "../../../fields/Schema";
 import { Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
+import { LinkManager } from "../../util/LinkManager";
+import { undoBatch, UndoManager } from "../../util/UndoManager";
 import "./CollectionMapView.scss";
 import { CollectionSubView } from "./CollectionSubView";
 import React = require("react");
-import { DocumentManager } from "../../util/DocumentManager";
-import { UndoManager, undoBatch } from "../../util/UndoManager";
-import { computed, runInAction, Lambda, action } from "mobx";
 import requestPromise = require("request-promise");
 
 type MapSchema = makeInterface<[typeof documentSchema]>;
@@ -88,7 +88,7 @@ export class CollectionMapView extends CollectionSubView<MapSchema, Partial<IMap
             zoom && (this.layoutDoc[`${fieldKey}-mapCenter-zoom`] = zoom);
         });
         if (layout.isLinkButton && DocListCast(layout.links).length) {
-            await DocumentManager.Instance.FollowLink(undefined, layout, (doc: Doc, where: string, finished?: () => void) => {
+            await LinkManager.traverseLink(undefined, layout, (doc: Doc, where: string, finished?: () => void) => {
                 this.props.addDocTab(doc, where);
                 finished?.();
             }, false, this.props.ContainingCollectionDoc, batch.end, undefined);
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 8e0004c87..50cb064b9 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -13,9 +13,8 @@ import { DragManager } from '../../util/DragManager';
 import { Hypothesis } from '../../util/HypothesisUtils';
 import { LinkManager } from '../../util/LinkManager';
 import { undoBatch } from '../../util/UndoManager';
-import { ContextMenu } from '../ContextMenu';
 import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
-import { DocumentView } from '../nodes/DocumentView';
+import { DocumentView, DocumentViewSharedProps } from '../nodes/DocumentView';
 import { LinkDocPreview } from '../nodes/LinkDocPreview';
 import './LinkMenuItem.scss';
 import React = require("react");
@@ -28,7 +27,7 @@ interface LinkMenuItemProps {
     sourceDoc: Doc;
     destinationDoc: Doc;
     showEditor: (linkDoc: Doc) => void;
-    addDocTab: (document: Doc, where: string) => boolean;
+    docprops: DocumentViewSharedProps,
     menuRef: React.Ref<HTMLDivElement>;
 }
 
@@ -110,7 +109,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
     onLinkButtonUp = (e: PointerEvent): void => {
         document.removeEventListener("pointermove", this.onLinkButtonMoved);
         document.removeEventListener("pointerup", this.onLinkButtonUp);
-        DocumentView.followLinkClick(this.props.linkDoc, this.props.sourceDoc, this.props.docView.props, false);
+        LinkManager.FollowLink(this.props.linkDoc, this.props.sourceDoc, this.props.docView.props, false);
 
         e.stopPropagation();
     }
@@ -126,41 +125,6 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
         e.stopPropagation();
     }
 
-    @action
-    onContextMenu = (e: React.MouseEvent) => {
-        DocumentLinksButton.EditLink = undefined;
-        LinkDocPreview.LinkInfo = undefined;
-        e.preventDefault();
-        ContextMenu.Instance.addItem({ description: "Follow Default Link", event: () => LinkMenuItem.followDefault(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc, this.props.addDocTab), icon: "arrow-right" });
-        ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
-    }
-
-
-    @action
-    public static followDefault(linkDoc: Doc, sourceDoc: Doc, destinationDoc: Doc, addDocTab: (doc: Doc, where: string) => void) {
-        DocumentLinksButton.EditLink = undefined;
-        LinkDocPreview.LinkInfo = undefined;
-
-        if (linkDoc.followLinkLocation === "openExternal" && destinationDoc.type === DocumentType.WEB) {
-            window.open(`${StrCast(linkDoc.annotationUri)}#annotations:${StrCast(linkDoc.annotationId)}`, '_blank');
-        }
-
-        if (linkDoc.followLinkLocation && linkDoc.followLinkLocation !== "default") {
-            const annotationOn = destinationDoc.annotationOn as Doc;
-            addDocTab(annotationOn instanceof Doc ? annotationOn : destinationDoc, StrCast(linkDoc.followLinkLocation));
-            if (annotationOn) {
-                setTimeout(() => {
-                    const dv = DocumentManager.Instance.getFirstDocumentView(destinationDoc);
-                    dv?.props.focus(destinationDoc, false);
-                });
-            }
-        } else {
-            DocumentManager.Instance.FollowLink(linkDoc, sourceDoc, addDocTab, false);
-        }
-
-        linkDoc.linksToAnnotation && Hypothesis.scrollToAnnotation(StrCast(linkDoc.annotationId), destinationDoc);
-    }
-
     @undoBatch
     @action
     deleteLink = (): void => {
@@ -215,7 +179,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                     <div ref={this._drag} className="linkMenu-name" //title="drag to view target. click to customize."
                         onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
                         onPointerEnter={action(e => this.props.linkDoc && (LinkDocPreview.LinkInfo = {
-                            addDocTab: this.props.addDocTab,
+                            docprops: this.props.docprops,
                             linkSrc: this.props.sourceDoc,
                             linkDoc: this.props.linkDoc,
                             Location: [e.clientX, e.clientY + 20]
@@ -260,8 +224,6 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                                 <div className="button" onPointerDown={this.deleteLink}>
                                     <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
                             </Tooltip>
-                            {/* <div title="Follow link" className="button" onPointerDown={this.followDefault} onContextMenu={this.onContextMenu}>
-                                <FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /></div> */}
                         </div>
                     </div>
                 </div>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 56982bd89..15ba62e23 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -425,7 +425,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             } else if (this.Document["onClick-rawScript"] && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) {// bcz: hack? don't edit a script if you're clicking on a scripting box itself
                 this.props.addDocTab(DocUtils.makeCustomViewClicked(Doc.MakeAlias(this.props.Document), undefined, "onClick"), "add:right");
             } else if (this.allLinks && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
-                this.allLinks.length && DocumentView.followLinkClick(undefined, this.props.Document, this.props, e.altKey);
+                this.allLinks.length && LinkManager.FollowLink(undefined, this.props.Document, this.props, e.altKey);
             } else {
                 if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) {  // onDragStart implies a button doc that we don't want to select when clicking.   RootDocument & isTemplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
                     stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
@@ -785,7 +785,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     hideLinkAnchor = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && (doc.hidden = true), true)
     anchorPanelWidth = () => this.props.PanelWidth() || 1;
     anchorPanelHeight = () => this.props.PanelHeight() || 1;
-    anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string): any => this.props.styleProvider?.(doc, props, property + ":anchor");
+    anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string): any => {
+        switch (property.split(":")[0]) {
+            case StyleProp.LinkSource: return this.props.Document; // pass the LinkSource to the LinkAnchorBox
+        }
+        return this.props.styleProvider?.(doc, props, property + ":anchor");
+    }
 
     @computed get directLinks() { TraceMobx(); return LinkManager.Instance.getAllDirectLinks(this.rootDoc); }
     @computed get allLinks() { TraceMobx(); return LinkManager.Instance.getAllRelatedLinks(this.rootDoc); }
@@ -930,40 +935,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     toggleNativeDimensions = () => this.docView?.toggleNativeDimensions();
     contentsActive = () => this.docView?.contentsActive();
 
-    // 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 a tab, on the right, or in place
-    // depending on the followLinkLocation property of the source (or the link itself as a fallback);
-    public static followLinkClick = async (linkDoc: Opt<Doc>, sourceDoc: Doc, docView: {
-        focus: DocFocusFunc,
-        addDocTab: (doc: Doc, where: string) => boolean,
-        ContainingCollectionDoc?: Doc
-    }, 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>) => {
-            const targetFocusAfterDocFocus = () => {
-                const where = StrCast(sourceDoc.followLinkLocation) || followLoc;
-                const hackToCallFinishAfterFocus = () => {
-                    finished && setTimeout(finished, 0); // finished() needs to be called right after hackToCallFinishAfterFocus(), but there's no callback for that so we use the hacky timeout.
-                    return false; // we must return false here so that the zoom to the document is not reversed.  If it weren't for needing to call finished(), we wouldn't need this function at all since not having it is equivalent to returning false
-                };
-                const addTab = docView.addDocTab(doc, where);
-                addTab && setTimeout(() => {
-                    const targDocView = DocumentManager.Instance.getFirstDocumentView(doc);
-                    targDocView?.props.focus(doc, BoolCast(sourceDoc.followLinkZoom, false), undefined, hackToCallFinishAfterFocus);
-                }); //  add the target and focus on it.
-                return where !== "inPlace" || addTab; // return true to reset the initial focus&zoom (return false for 'inPlace' since resetting the initial focus&zoom will negate the zoom into the target)
-            };
-            if (!sourceDoc.followLinkZoom) {
-                targetFocusAfterDocFocus();
-            } else {
-                // first focus & zoom onto this (the clicked document).  Then execute the function to focus on the target
-                docView.focus(sourceDoc, BoolCast(sourceDoc.followLinkZoom, true), 1, targetFocusAfterDocFocus);
-            }
-        };
-        await DocumentManager.Instance.FollowLink(linkDoc, sourceDoc, createViewFunc, BoolCast(sourceDoc.followLinkZoom, false), docView.ContainingCollectionDoc, batch.end, altKey ? true : undefined);
-    }
-
     @action public float = () => {
         const { Document: topDoc, ContainingCollectionView: container } = this.props;
         const screenXf = container?.screenToLocalTransform();
@@ -1106,7 +1077,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     }
 }
 
-
 Scripting.addGlobal(function toggleDetail(doc: any, layoutKey: string, otherKey: string = "layout") {
     const dv = DocumentManager.Instance.getDocumentView(doc);
     if (dv?.props.Document.layoutKey === layoutKey) dv?.switchViews(otherKey !== "layout", otherKey.replace("layout_", ""));
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index f035fba33..e347d1304 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -1,25 +1,25 @@
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast } from "../../../fields/Doc";
+import { Doc } from "../../../fields/Doc";
 import { documentSchema } from "../../../fields/documentSchemas";
+import { Id } from "../../../fields/FieldSymbols";
 import { makeInterface } from "../../../fields/Schema";
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
-import { Utils, setupMoveUpEvents, emptyFunction, OmitKeys } from '../../../Utils';
-import { DocumentManager } from "../../util/DocumentManager";
+import { TraceMobx } from "../../../fields/util";
+import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils';
 import { DragManager } from "../../util/DragManager";
-import { ViewBoxBaseComponent } from "../DocComponent";
-import "./LinkAnchorBox.scss";
-import { FieldView, FieldViewProps } from "./FieldView";
-import React = require("react");
-import { ContextMenuProps } from "../ContextMenuItem";
+import { LinkManager } from "../../util/LinkManager";
+import { SelectionManager } from "../../util/SelectionManager";
 import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
+import { ViewBoxBaseComponent } from "../DocComponent";
 import { LinkEditor } from "../linking/LinkEditor";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { SelectionManager } from "../../util/SelectionManager";
-import { TraceMobx } from "../../../fields/util";
-import { Id } from "../../../fields/FieldSymbols";
-import { LinkDocPreview } from "./LinkDocPreview";
 import { StyleProp } from "../StyleProvider";
+import { FieldView, FieldViewProps } from "./FieldView";
+import "./LinkAnchorBox.scss";
+import { LinkDocPreview } from "./LinkDocPreview";
+import React = require("react");
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -74,7 +74,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
             anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false);
             if (anchorContainerDoc && !this.layoutDoc.onClick && !this._isOpen) {
                 this._timeout = setTimeout(action(() => {
-                    DocumentManager.Instance.FollowLink(this.rootDoc, anchorContainerDoc, (doc, where) => this.props.addDocTab(doc, where), false);
+                    LinkManager.FollowLink(this.rootDoc, anchorContainerDoc, this.props, false);
                     this._editing = false;
                 }), 300 - (Date.now() - this._lastTap));
             }
@@ -136,7 +136,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
         return <div className={`linkAnchorBox-cont${small ? "-small" : ""} ${this.rootDoc[Id]}`}
             onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
             onPointerEnter={action(e => LinkDocPreview.LinkInfo = {
-                addDocTab: this.props.addDocTab,
+                docprops: this.props,
                 linkSrc: linkSource,
                 linkDoc: this.rootDoc,
                 Location: [e.clientX, e.clientY + 20]
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index c69fb5b33..07b2b6338 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -6,25 +6,24 @@ import { Id } from '../../../fields/FieldSymbols';
 import { Cast, FieldValue, NumCast } from "../../../fields/Types";
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse } from "../../../Utils";
 import { Docs } from "../../documents/Documents";
-import { DocumentManager } from "../../util/DocumentManager";
+import { LinkManager } from '../../util/LinkManager';
 import { Transform } from "../../util/Transform";
 import { ContextMenu } from '../ContextMenu';
 import { DocumentLinksButton } from './DocumentLinksButton';
-import { DocumentView, StyleProviderFunc } from "./DocumentView";
+import { DocumentView, StyleProviderFunc, DocumentViewSharedProps } from "./DocumentView";
 import React = require("react");
 
 interface Props {
     linkDoc?: Doc;
     linkSrc?: Doc;
     href?: string;
-    styleProvider?: StyleProviderFunc;
-    addDocTab: (document: Doc, where: string) => boolean;
+    docprops: DocumentViewSharedProps;
     location: number[];
 }
 @observer
 export class LinkDocPreview extends React.Component<Props> {
     static TargetDoc: Doc | undefined;
-    @observable public static LinkInfo: Opt<{ linkDoc?: Doc; addDocTab: (document: Doc, where: string) => boolean, linkSrc: Doc; href?: string; Location: number[] }>;
+    @observable public static LinkInfo: Opt<{ linkDoc?: Doc; linkSrc: Doc; href?: string; Location: number[], docprops: DocumentViewSharedProps }>;
     @observable _targetDoc: Opt<Doc>;
     @observable _toolTipText = "";
     _editRef = React.createRef<HTMLDivElement>();
@@ -42,7 +41,7 @@ export class LinkDocPreview extends React.Component<Props> {
     async followDefault() {
         DocumentLinksButton.EditLink = undefined;
         LinkDocPreview.LinkInfo = undefined;
-        this._targetDoc ? DocumentManager.Instance.FollowLink(this.props.linkDoc, this._targetDoc, (doc, where) => this.props.addDocTab(doc, where), false) : null;
+        this._targetDoc && LinkManager.FollowLink(this.props.linkDoc, this._targetDoc, this.props.docprops, false);
     }
     componentWillUnmount() { LinkDocPreview.TargetDoc = undefined; }
 
@@ -75,10 +74,9 @@ export class LinkDocPreview extends React.Component<Props> {
     }
     pointerDown = (e: React.PointerEvent) => {
         if (this.props.linkDoc && this.props.linkSrc) {
-            DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.linkSrc,
-                (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "add" : followLinkLocation));
+            LinkManager.FollowLink(this.props.linkDoc, this.props.linkSrc, this.props.docprops, false);
         } else if (this.props.href) {
-            this.props.addDocTab(Docs.Create.WebDocument(this.props.href, { _fitWidth: true, title: this.props.href, _width: 200, _height: 400, useCors: true }), "add:right");
+            this.props.docprops?.addDocTab(Docs.Create.WebDocument(this.props.href, { _fitWidth: true, title: this.props.href, _width: 200, _height: 400, useCors: true }), "add:right");
         }
     }
     width = () => Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225));
@@ -113,7 +111,7 @@ export class LinkDocPreview extends React.Component<Props> {
                 focus={emptyFunction}
                 whenActiveChanged={returnFalse}
                 bringToFront={returnFalse}
-                styleProvider={this.props.styleProvider} />;
+                styleProvider={this.props.docprops?.styleProvider} />;
     }
 
     render() {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 0bb483b41..2b9910dfb 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -57,7 +57,7 @@ import { FieldView, FieldViewProps } from "../FieldView";
 import "./FormattedTextBox.scss";
 import { FormattedTextBoxComment, formattedTextBoxCommentPlugin, findLinkMark } from './FormattedTextBoxComment';
 import React = require("react");
-import { DocumentManager } from '../../../util/DocumentManager';
+import { LinkManager } from '../../../util/LinkManager';
 import { CollectionStackingView } from '../../collections/CollectionStackingView';
 import { CollectionViewType, CollectionViewProps } from '../../collections/CollectionView';
 import { SnappingManager } from '../../../util/SnappingManager';
@@ -1331,8 +1331,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
                 this.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "add" : "add:right");
             } else {
-                DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, this.props.Document,
-                    (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "add" : followLinkLocation));
+                LinkManager.FollowLink(FormattedTextBoxComment.linkDoc, this.props.Document, this.props, false);
             }
         }
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index c6be25dc2..038a91aa3 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -116,7 +116,7 @@ export class FormattedTextBoxComment {
                                 textBox.props.addDocTab(linkDoc, e.ctrlKey ? "add" : "add:right");
                             } else {
                                 const target = LinkManager.getOppositeAnchor(linkDoc, textBox.dataDoc);
-                                target && DocumentView.followLinkClick(linkDoc, textBox.dataDoc, textBox.props, e.altKey);
+                                target && LinkManager.FollowLink(linkDoc, textBox.dataDoc, textBox.props, e.altKey);
                             }
                         }
                     }
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 20ea7bfe4..5ef57f986 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -1,14 +1,14 @@
 import React = require("react");
 import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, WidthSym, Field, Opt } from "../../../fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
 import { Id } from "../../../fields/FieldSymbols";
 import { List } from "../../../fields/List";
-import { Cast, FieldValue, BoolCast, NumCast, StrCast, PromiseValue } from "../../../fields/Types";
-import { DocumentManager } from "../../util/DocumentManager";
-import { PDFMenu } from "./PDFMenu";
-import "./Annotation.scss";
+import { BoolCast, Cast, FieldValue, NumCast, PromiseValue, StrCast } from "../../../fields/Types";
+import { LinkManager } from "../../util/LinkManager";
 import { undoBatch } from "../../util/UndoManager";
+import "./Annotation.scss";
+import { PDFMenu } from "./PDFMenu";
 
 interface IAnnotationProps {
     anno: Doc;
@@ -119,7 +119,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
             e.persist();
             e.stopPropagation();
             PromiseValue(this.props.document.group).then(annoGroup => annoGroup instanceof Doc &&
-                DocumentManager.Instance.FollowLink(undefined, annoGroup, (doc, followLinkLocation) => this.props.addDocTab(doc, e.ctrlKey ? "add" : followLinkLocation), false, undefined,
+                LinkManager.traverseLink(undefined, annoGroup, (doc, followLinkLocation) => this.props.addDocTab(doc, e.ctrlKey ? "add" : followLinkLocation), false, undefined,
                     () => this.props.select(false))
             );
         }
-- 
cgit v1.2.3-70-g09d2

