From 661c1367d27fa23c3aeb62369e92cd36eb5edabd Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 21 Oct 2023 00:41:23 -0400
Subject: change to doc decorations to be more "lightweight". made linkBox
 render links in a freeform view as a DocView.  added an auto-reset view
 option for freeforms.  fixed highlighting ink strokes.  Made groups behave
 better for selecting things 'inside' the group bounding box that aren't in
 the group.   Added vertically centered text option.

---
 src/client/views/nodes/DocumentView.tsx | 23 ++++++++++++++++-------
 1 file changed, 16 insertions(+), 7 deletions(-)

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

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index da665a502..c2355e4f7 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,5 +1,5 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
@@ -53,6 +53,7 @@ import { PresEffect, PresEffectDirection } from './trails';
 import { PinProps, PresBox } from './trails/PresBox';
 import React = require('react');
 import { KeyValueBox } from './KeyValueBox';
+import { LinkBox } from './LinkBox';
 const { Howl } = require('howler');
 
 interface Window {
@@ -142,6 +143,7 @@ export interface DocComponentView {
     annotationKey?: string;
     getTitle?: () => string;
     getCenter?: (xf: Transform) => { X: number; Y: number };
+    screenBounds?: () => { left: number; top: number; right: number; bottom: number; center?:{X:number, Y:number} };
     ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
     ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
     snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number };
@@ -154,7 +156,6 @@ export interface DocumentViewSharedProps {
     renderDepth: number;
     Document: Doc;
     DataDoc?: Doc;
-    contentBounds?: () => undefined | { x: number; y: number; r: number; b: number };
     fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
     suppressSetHeight?: boolean;
     setContentView?: (view: DocComponentView) => any;
@@ -219,7 +220,7 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
     hideLinkAnchors?: boolean;
     isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
     isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
-    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
+    contentPointerEvents?: 'none' | 'all' | undefined; // 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;
     dontCenter?: 'x' | 'y' | 'xy';
@@ -425,7 +426,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     public static addDocTabFunc: (doc: Doc, location: OpenWhere) => boolean = returnFalse;
 
     onClick = action((e: React.MouseEvent | React.PointerEvent) => {
-        if (!this.Document.ignoreClick && this.pointerEvents !== 'none' && this.props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
+        if (!this.Document.ignoreClick && this.props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
             let stopPropagate = true;
             let preventDefault = true;
             !this.rootDoc._keepZWhenDragged && this.props.bringToFront(this.rootDoc);
@@ -536,7 +537,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 !this.props.onBrowseClick?.() &&
                 !this.Document.ignoreClick &&
                 e.button === 0 &&
-                this.pointerEvents !== 'none' &&
                 !Doc.IsInMyOverlay(this.layoutDoc)
             ) {
                 e.stopPropagation();
@@ -635,6 +635,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 if (de.complete.annoDragData || this.rootDoc !== linkdrag.linkSourceDoc.embedContainer) {
                     const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.rootDoc;
                     de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]);
+                    if (de.complete.linkDocument) {
+                        de.complete.linkDocument.layout_isSvg = true;
+                        this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.addDocument(de.complete.linkDocument);
+                    }
                 }
                 e.stopPropagation();
                 return true;
@@ -889,12 +893,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     /// disable pointer events on content when there's an enabled onClick script (but not the browse script) and the contents aren't forced active, or if contents are marked inactive
     @computed get _contentPointerEvents() {
+        if (this.props.contentPointerEvents) return this.props.contentPointerEvents;
         return (!this.disableClickScriptFunc && this.onClickHandler && !this.props.onBrowseClick?.() && this.isContentActive() !== true) || this.isContentActive() === false ? 'none' : this.pointerEvents;
     }
     contentPointerEvents = () => this._contentPointerEvents;
     @computed get contents() {
         TraceMobx();
-        const isInk = StrCast(this.layoutDoc.layout).includes(InkingStroke.name) && !this.props.LayoutTemplateString;
+        const isInk = this.layoutDoc._layout_isSvg && !this.props.LayoutTemplateString;
         return (
             <div
                 className="documentView-contentsView"
@@ -1414,16 +1419,20 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return this.props.dontCenter?.includes('y') ? 0 : this.Yshift;
     }
 
-    public toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this.props.PanelWidth(), this.props.PanelHeight());
+    public toggleNativeDimensions = () => this.docView && this.rootDoc.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this.props.PanelWidth(), this.props.PanelHeight());
     public getBounds = () => {
         if (!this.docView?.ContentDiv || this.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
             return undefined;
         }
+        if (this.docView._componentView?.screenBounds) {
+            return this.docView._componentView.screenBounds();
+        }
         const xf = this.docView.props
             .ScreenToLocalTransform()
             .scale(this.trueNativeWidth() ? this.nativeScaling : 1)
             .inverse();
         const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
+
         if (this.docView.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
             const docuBox = this.docView.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
             if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), center: undefined };
-- 
cgit v1.2.3-70-g09d2


From 51cad21a358e17c1f8e609d1d3f077960922fc38 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 26 Oct 2023 11:31:15 -0400
Subject: enabled different title colors per doc, not just per user. added
 support for screen space doc titles, and for proper title clipping when
 borderRadius is set.  added dropdown for setting title field to display and
 tweaked editableView to enable ellipsis for overfow

---
 src/client/documents/Documents.ts                  |   2 +
 src/client/util/CurrentUserUtils.ts                |   2 +-
 src/client/util/SettingsManager.tsx                |  15 ++-
 src/client/views/DocumentDecorations.tsx           |   1 +
 src/client/views/EditableView.scss                 |   2 +-
 src/client/views/EditableView.tsx                  |  17 ++-
 src/client/views/FilterPanel.tsx                   | 110 ++++++++---------
 src/client/views/StyleProvider.tsx                 |   5 +-
 src/client/views/global/globalScripts.ts           |  15 ++-
 src/client/views/nodes/DocumentView.scss           |   6 +-
 src/client/views/nodes/DocumentView.tsx            | 131 ++++++++++++++++-----
 .../nodes/formattedText/FormattedTextBox.scss      |   1 -
 12 files changed, 201 insertions(+), 106 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 161aba6e1..5c913513a 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -444,6 +444,8 @@ export class DocumentOptions {
     userBackgroundColor?: STRt = new StrInfo('background color associated with a Dash user (seen in header fields of shared documents)');
     userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)');
 }
+
+export const DocOptions = new DocumentOptions();
 export namespace Docs {
     export let newAccount: boolean = false;
 
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index dc988b04d..591bc7430 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -707,7 +707,7 @@ export class CurrentUserUtils {
                     CollectionViewType.Grid, CollectionViewType.NoteTaking]),
               title: "Perspective",                    toolTip: "View",                 btnType: ButtonType.DropdownList,                                                                                                                                    ignoreClick: true, width: 100, scripts: { script:  'setView(value, _readOnly_)'}},
             { title: "Pin",     icon: "map-pin",       toolTip: "Pin View to Trail",    btnType: ButtonType.ClickButton,         expertMode: false,                                                                                                                             width: 30,  scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}},
-            { title: "Header",  icon: "heading",       toolTip: "Header Color",         btnType: ButtonType.ColorButton,         expertMode: true,                                                                                                           ignoreClick: true,             scripts: { script:  'return setHeaderColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}},
+            { title: "Header",  icon: "heading",       toolTip: "Doc Titlebar Color",   btnType: ButtonType.ColorButton,         expertMode: true,                                                                                                           ignoreClick: true,             scripts: { script:  'return setHeaderColor(value, _readOnly_)'} },
             { title: "Fill",    icon: "fill-drip",     toolTip: "Background Fill Color",btnType: ButtonType.ColorButton,         expertMode: false,                                                                                                          ignoreClick: true, width: 30,  scripts: { script:  'return setBackgroundColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}}, // Only when a document is selected
             { title: "Overlay", icon: "layer-group",   toolTip: "Overlay",              btnType: ButtonType.ToggleButton,        expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'},       scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform
             { title: "Back",    icon: "chevron-left",  toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30,  scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index dc852596f..f75322905 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -7,7 +7,7 @@ import { BsGoogle } from 'react-icons/bs';
 import { FaFillDrip, FaPalette } from 'react-icons/fa';
 import { Doc } from '../../fields/Doc';
 import { DashVersion } from '../../fields/DocSymbols';
-import { BoolCast, Cast, StrCast } from '../../fields/Types';
+import { BoolCast, Cast, NumCast, StrCast } from '../../fields/Types';
 import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { DocServer } from '../DocServer';
@@ -269,6 +269,19 @@ export class SettingsManager extends React.Component<{}> {
                     size={Size.XSMALL}
                     color={SettingsManager.userColor}
                 />
+                <Group formLabel="Title Height">
+                    <NumberDropdown
+                        number={NumCast(Doc.UserDoc().headerHeight, 30)}
+                        color={SettingsManager.userColor}
+                        numberDropdownType={'slider'}
+                        min={6}
+                        max={60}
+                        step={2}
+                        type={Type.TERT}
+                        unit={'px'}
+                        setNumber={val => console.log('GOT: ' + (Doc.UserDoc().headerHeight = val))}
+                    />
+                </Group>
             </div>
         );
     }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index f40d2ae8b..9dafb12fb 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -944,6 +944,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                                     key="rad"
                                     className="documentDecorations-borderRadius"
                                     style={{
+                                        opacity: 0.5,
                                         background: `${this._isRounding ? Colors.MEDIUM_BLUE : SettingsManager.userColor}`,
                                         transform: `translate(${radiusHandleLocation ?? 0}px, ${(radiusHandleLocation ?? 0) + (this._showNothing ? 0 : this._titleHeight)}px)`,
                                     }}
diff --git a/src/client/views/EditableView.scss b/src/client/views/EditableView.scss
index f7c03caf9..27b260450 100644
--- a/src/client/views/EditableView.scss
+++ b/src/client/views/EditableView.scss
@@ -3,7 +3,7 @@
     overflow-wrap: break-word;
     word-wrap: break-word;
     hyphens: auto;
-    overflow: auto;
+    overflow: hidden;
     height: 100%;
     min-width: 20;
     text-overflow: ellipsis;
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index ca4ffaf3a..ed7c544fc 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -283,11 +283,24 @@ export class EditableView extends React.Component<EditableProps> {
             <div
                 className={`editableView-container-editing${this.props.oneLine ? '-oneLine' : ''}`}
                 ref={this._ref}
-                style={{ display: this.props.display, textOverflow: this.props.overflow, minHeight: '10px', whiteSpace: this.props.oneLine ? 'nowrap' : 'pre-line', height: this.props.height, maxHeight: this.props.maxHeight }}
+                style={{
+                    display: this.props.display, //
+                    textOverflow: this.props.overflow,
+                    minHeight: '10px',
+                    whiteSpace: this.props.oneLine ? 'nowrap' : 'pre-line',
+                    height: this.props.height,
+                    maxHeight: this.props.maxHeight,
+                }}
                 //onPointerDown={this.stopPropagation}
                 onClick={this.onClick}
                 placeholder={this.props.placeholder}>
-                <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize }}>{this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span>
+                <span
+                    style={{
+                        fontStyle: this.props.fontStyle,
+                        fontSize: this.props.fontSize,
+                    }}>
+                    {this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}
+                </span>
             </div>
         );
     }
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index 0df88f970..cb5c9b085 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -7,7 +7,7 @@ import { CiCircleRemove } from 'react-icons/ci';
 import Select from 'react-select';
 import { Doc, DocListCast, Field, LinkedTo, StrListCast } from '../../fields/Doc';
 import { RichTextField } from '../../fields/RichTextField';
-import { DocumentOptions, FInfo } from '../documents/Documents';
+import { DocOptions, DocumentOptions, FInfo } from '../documents/Documents';
 import { DocumentManager } from '../util/DocumentManager';
 import { UserOptions } from '../util/GroupManager';
 import { SearchUtil } from '../util/SearchUtil';
@@ -18,6 +18,7 @@ import { Handle, Tick, TooltipRail, Track } from './nodes/SliderBox-components';
 import { SettingsManager } from '../util/SettingsManager';
 import { Id } from '../../fields/FieldSymbols';
 import { List } from '../../fields/List';
+import { emptyFunction } from '../../Utils';
 
 interface filterProps {
     rootDoc: Doc;
@@ -25,8 +26,6 @@ interface filterProps {
 
 @observer
 export class FilterPanel extends React.Component<filterProps> {
-    private _documentOptions: DocumentOptions = new DocumentOptions();
-
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(FilterPanel, fieldKey);
     }
@@ -51,12 +50,10 @@ export class FilterPanel extends React.Component<filterProps> {
         if (targetDoc) {
             SearchUtil.foreachRecursiveDoc([this.targetDoc], (depth, doc) => allDocs.add(doc));
         }
-        console.log('this is all Docs' + Array.from(allDocs));
         return Array.from(allDocs);
     }
 
     @computed get _allFacets() {
-        // trace();
         const noviceReqFields = ['author', 'tags', 'text', 'type', LinkedTo];
         const noviceLayoutFields: string[] = []; //["_layout_curPage"];
         const noviceFields = [...noviceReqFields, ...noviceLayoutFields];
@@ -68,11 +65,8 @@ export class FilterPanel extends React.Component<filterProps> {
             .filter(key => key.indexOf('modificationDate') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('_')) || noviceFields.includes(key) || !Doc.noviceMode)
             .sort();
 
-        // console.log('THIS IS HERE ' + Doc.UserDoc().color + 'space ' + Doc.UserDoc().color);
         noviceFields.forEach(key => sortedKeys.splice(sortedKeys.indexOf(key), 1));
 
-        console.log('this is novice fields ' + noviceFields + 'and this is sorted Keys ' + sortedKeys);
-
         return [...noviceFields, ...sortedKeys];
     }
 
@@ -206,13 +200,7 @@ export class FilterPanel extends React.Component<filterProps> {
      */
 
     @action
-    facetClick = (facetHeader: string) => {
-        // just when someone chooses a facet
-
-        this._selectedFacetHeaders.add(facetHeader);
-
-        return;
-    };
+    facetClick = (facetHeader: string) => this._selectedFacetHeaders.add(facetHeader);
 
     @action
     sortingCurrentFacetValues = (facetHeader: string) => {
@@ -260,57 +248,59 @@ export class FilterPanel extends React.Component<filterProps> {
         return nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2));
     };
 
-    render() {
-        let filteredOptions: string[] = ['author', 'tags', 'text', 'acl-Guest', ...this._allFacets.filter(facet => facet[0] === facet.charAt(0).toUpperCase())];
+    @computed get fieldsDropdown() {
+        const filteredOptions = ['author', 'tags', 'text', 'acl-Guest', ...this._allFacets.filter(facet => facet[0] === facet.charAt(0).toUpperCase())];
 
-        Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => {
-            if (pair[1].filterable) {
-                filteredOptions.push(pair[0]);
-            }
-        });
+        Object.entries(DocOptions)
+            .filter(opts => opts[1].filterable)
+            .forEach((pair: [string, FInfo]) => filteredOptions.push(pair[0]));
+        const options = filteredOptions.map(facet => ({ value: facet, label: facet }));
 
-        let options = filteredOptions.map(facet => ({ value: facet, label: facet }));
+        return (
+            <Select
+                styles={{
+                    control: (baseStyles, state) => ({
+                        ...baseStyles,
+                        color: SettingsManager.userColor,
+                        background: SettingsManager.userBackgroundColor,
+                    }),
+                    placeholder: (baseStyles, state) => ({
+                        ...baseStyles,
+                        color: SettingsManager.userColor,
+                        background: SettingsManager.userBackgroundColor,
+                    }),
+                    input: (baseStyles, state) => ({
+                        ...baseStyles,
+                        color: SettingsManager.userColor,
+                        background: SettingsManager.userBackgroundColor,
+                    }),
+                    option: (baseStyles, state) => ({
+                        ...baseStyles,
+                        color: SettingsManager.userColor,
+                        background: !state.isFocused ? SettingsManager.userBackgroundColor : SettingsManager.userVariantColor,
+                    }),
+                    menuList: (baseStyles, state) => ({
+                        ...baseStyles,
+                        backgroundColor: SettingsManager.userBackgroundColor,
+                    }),
+                }}
+                placeholder={'add a filter'}
+                options={options}
+                isMulti={false}
+                onChange={val => this.facetClick((val as UserOptions).value)}
+                onKeyDown={e => e.stopPropagation()}
+                //onMenuClose={onClose}
+                value={null}
+                closeMenuOnSelect={true}
+            />
+        );
+    }
 
+    render() {
         return (
             <div className="filterBox-treeView">
                 <div className="filterBox-select">
-                    <div style={{ width: '100%' }}>
-                        <Select
-                            styles={{
-                                control: (baseStyles, state) => ({
-                                    ...baseStyles,
-                                    color: SettingsManager.userColor,
-                                    background: SettingsManager.userBackgroundColor,
-                                }),
-                                placeholder: (baseStyles, state) => ({
-                                    ...baseStyles,
-                                    color: SettingsManager.userColor,
-                                    background: SettingsManager.userBackgroundColor,
-                                }),
-                                input: (baseStyles, state) => ({
-                                    ...baseStyles,
-                                    color: SettingsManager.userColor,
-                                    background: SettingsManager.userBackgroundColor,
-                                }),
-                                option: (baseStyles, state) => ({
-                                    ...baseStyles,
-                                    color: SettingsManager.userColor,
-                                    background: !state.isFocused ? SettingsManager.userBackgroundColor : SettingsManager.userVariantColor,
-                                }),
-                                menuList: (baseStyles, state) => ({
-                                    ...baseStyles,
-                                    backgroundColor: SettingsManager.userBackgroundColor,
-                                }),
-                            }}
-                            placeholder="Add a filter..."
-                            options={options}
-                            isMulti={false}
-                            onChange={val => this.facetClick((val as UserOptions).value)}
-                            onKeyDown={e => e.stopPropagation()}
-                            value={null}
-                            closeMenuOnSelect={true}
-                        />
-                    </div>
+                    <div style={{ width: '100%' }}>{this.fieldsDropdown}</div>
                     {/* THE FOLLOWING CODE SHOULD BE DEVELOPER FOR BOOLEAN EXPRESSION (AND / OR) */}
                     {/* <div className="filterBox-select-bool"> 
                         <select className="filterBox-selection" onChange={action(e => this.targetDoc && (this.targetDoc._childFilters_boolean = (e.target as any).value))} defaultValue={StrCast(this.targetDoc?.childFilters_boolean)}>
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 48c96d064..5ff743a30 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -90,6 +90,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
     const boxBackground = property.includes(':box');
     const fieldKey = props?.fieldKey ? props.fieldKey + '_' : isCaption ? 'caption_' : '';
     const lockedPosition = () => doc && BoolCast(doc._lockedPosition);
+    const titleHeight = () => props?.styleProvider?.(doc, props, StyleProp.TitleHeight);
     const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor);
     const color = () => props?.styleProvider?.(doc, props, StyleProp.Color);
     const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity);
@@ -137,7 +138,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(doc?._text_fontWeight, StrCast(Doc.UserDoc().fontWeight)));
         case StyleProp.FillColor:  return StrCast(doc?._fillColor, StrCast(doc?.fillColor, 'transparent'));
         case StyleProp.ShowCaption:return doc?._type_collection === CollectionViewType.Carousel || props?.hideCaptions ? undefined : StrCast(doc?._layout_showCaption);
-        case StyleProp.TitleHeight:return 15;
+        case StyleProp.TitleHeight:return (props?.ScreenToLocalTransform().Scale ?? 1)*(props?.NativeDimScaling?.()??1) * NumCast(Doc.UserDoc().headerHeight,30)
         case StyleProp.ShowTitle:
             return (
                 (doc &&
@@ -189,7 +190,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                 doc?.type === DocumentType.LABEL) &&
                 layout_showTitle() &&
                 !StrCast(doc?.layout_showTitle).includes(':hover')
-                ? 15
+                ? titleHeight()
                 : 0;
         case StyleProp.BackgroundColor: {
             if (DocumentView.LastPressedSidebarBtn === doc) return SettingsManager.userColor; // hack to indicate active menu panel item
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index d8ed93bd7..ced11447c 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -68,11 +68,18 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
 // toggle: Set overlay status of selected document
 ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boolean) {
     if (checkResult) {
-        return Doc.SharingDoc().headingColor;
+        return SelectionManager.Views().length ? StrCast(SelectionManager.Docs().lastElement().layout_headingColor) : Doc.SharingDoc().headingColor;
+    }
+    if (SelectionManager.Views().length) {
+        SelectionManager.Docs().forEach(doc => {
+            Doc.GetProto(doc).layout_headingColor = color;
+            doc.layout_showTitle = color === 'transparent' ? undefined : StrCast(doc.layout_showTitle, 'title');
+        });
+    } else {
+        Doc.SharingDoc().headingColor = undefined;
+        Doc.GetProto(Doc.SharingDoc()).headingColor = color;
+        Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'author_date');
     }
-    Doc.SharingDoc().headingColor = undefined;
-    Doc.GetProto(Doc.SharingDoc()).headingColor = color;
-    Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'author_date');
 });
 
 // toggle: Set overlay status of selected document
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index b25540dd3..874723895 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -24,6 +24,7 @@
     width: 100%;
     height: 100%;
     border-radius: inherit;
+    overflow: hidden; // need this so that title will be clipped when borderRadius is set
     //  transition: outline 0.3s linear;
 
     // background: $white; //overflow: hidden;
@@ -118,7 +119,7 @@
         display: flex;
         justify-content: center;
         align-items: center;
-        
+
         .sharingIndicator {
             height: 30px;
             width: 30px;
@@ -185,6 +186,7 @@
             text-overflow: ellipsis;
             white-space: pre;
             position: absolute;
+            display: flex; // this allows title field dropdown to be inline with editable title
         }
 
         .documentView-titleWrapper-hover {
@@ -214,7 +216,7 @@
 .documentView-node:hover {
     > .documentView-styleWrapper {
         > .documentView-titleWrapper-hover {
-            display: inline-block;
+            display: flex;
         }
         // > .documentView-contentsView {
         //     opacity: 0.5;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c2355e4f7..4a43ffe3e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -17,7 +17,7 @@ import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
 import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils';
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { DocServer } from '../../DocServer';
-import { Docs, DocUtils } from '../../documents/Documents';
+import { DocOptions, Docs, DocUtils, FInfo } from '../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { Networking } from '../../Network';
 import { DictationManager } from '../../util/DictationManager';
@@ -54,6 +54,8 @@ import { PinProps, PresBox } from './trails/PresBox';
 import React = require('react');
 import { KeyValueBox } from './KeyValueBox';
 import { LinkBox } from './LinkBox';
+import { FilterPanel } from '../FilterPanel';
+import { Dropdown, DropdownType, Type } from 'browndash-components';
 const { Howl } = require('howler');
 
 interface Window {
@@ -143,7 +145,7 @@ export interface DocComponentView {
     annotationKey?: string;
     getTitle?: () => string;
     getCenter?: (xf: Transform) => { X: number; Y: number };
-    screenBounds?: () => { left: number; top: number; right: number; bottom: number; center?:{X:number, Y:number} };
+    screenBounds?: () => { left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } };
     ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
     ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
     snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number };
@@ -622,7 +624,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (this.props.Document === Doc.ActiveDashboard) {
             e.stopPropagation();
             e.preventDefault();
-            alert((e.target as any)?.closest?.('*.lm_content') ? "You can't perform this move most likely because you don't have permission to modify the destination." : 'Linking to document tabs not yet supported. Drop link on document content.');
+            alert(
+                (e.target as any)?.closest?.('*.lm_content')
+                    ? "You can't perform this move most likely because you didn't drag the document's title bar to enable embedding in a different document."
+                    : 'Linking to document tabs not yet supported. Drop link on document content.'
+            );
             return true;
         }
         const linkdrag = de.complete.annoDragData ?? de.complete.linkDragData;
@@ -1068,6 +1074,41 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     };
 
     captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => this.props?.styleProvider?.(doc, props, property + ':caption');
+    @observable _changingTitleField = false;
+    @observable _dropDownInnerWidth = 0;
+    fieldsDropdown = (inputOptions: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => {
+        const filteredOptions = new Set(inputOptions);
+        const scaling = this.titleHeight / 30; /* height of Dropdown */
+        Object.entries(DocOptions)
+            .filter(opts => opts[1].filterable)
+            .forEach((pair: [string, FInfo]) => filteredOptions.add(pair[0]));
+        filteredOptions.add(StrCast(this.layoutDoc.layout_showTitle));
+        const options = Array.from(filteredOptions)
+            .filter(f => f)
+            .map(facet => ({ val: facet, text: facet }));
+        return (
+            <div style={{ width: dropdownWidth }}>
+                <div
+                    ref={action((r: any) => r && (this._dropDownInnerWidth = Number(getComputedStyle(r).width.replace('px', ''))))}
+                    onPointerDown={action(e => (this._changingTitleField = true))}
+                    style={{ width: 'max-content', transformOrigin: 'left', transform: `scale(${scaling})` }}>
+                    <Dropdown
+                        activeChanged={action(isOpen => !isOpen && (this._changingTitleField = false))}
+                        selectedVal={placeholder}
+                        setSelectedVal={onChange}
+                        color={SettingsManager.userColor}
+                        background={SettingsManager.userVariantColor}
+                        type={Type.TERT}
+                        closeOnSelect={true}
+                        dropdownType={DropdownType.SELECT}
+                        items={options}
+                        width={100}
+                        fillWidth
+                    />
+                </div>
+            </div>
+        );
+    };
     @computed get innards() {
         TraceMobx();
         const showTitle = this.layout_showTitle?.split(':')[0];
@@ -1095,8 +1136,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             </div>
         );
         const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.rootDoc;
-        const background = StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SettingsManager.userBackgroundColor));
+        const background = StrCast(
+            this.layoutDoc.layout_headingColor,
+            StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(this.layoutDoc.layout_headingColor, StrCast(Doc.SharingDoc().headingColor, SettingsManager.userBackgroundColor)))
+        );
+        const dropdownWidth = this._titleRef.current?._editing || this._changingTitleField ? Math.max(10, (this._dropDownInnerWidth * this.titleHeight) / 30) : 0;
         const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', '');
+        // displays a 'title' at the top of a document. The title contents default to the 'title' field, but can be changed to one or more fields by
+        // setting layout_showTitle using the format:   field1[;field2[...][:hover]]
+        // from the UI, this is done by clicking the title field and prefixin the format with '#'.  eg.,  #field1[;field2;...][:hover]
         const titleView = !showTitle ? null : (
             <div
                 className={`documentView-titleWrapper${showTitleHover ? '-hover' : ''}`}
@@ -1104,39 +1152,58 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 style={{
                     position: this.headerMargin ? 'relative' : 'absolute',
                     height: this.titleHeight,
-                    width: !this.headerMargin ? `calc(${sidebarWidthPercent || 100}% - 18px)` : (sidebarWidthPercent || 100) + '%', // leave room for annotation button
+                    width: 100 - sidebarWidthPercent + '%',
                     color: background === 'transparent' ? SettingsManager.userColor : lightOrDark(background),
                     background,
                     pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
                 }}>
-                <EditableView
-                    ref={this._titleRef}
-                    contents={showTitle
-                        .split(';')
-                        .map(field => field.trim())
-                        .map(field => targetDoc[field]?.toString())
-                        .join('\\')}
-                    display={'block'}
-                    fontSize={10}
-                    GetValue={() => {
-                        return showTitle.split(';').length === 1 ? showTitle + '=' + Field.toString(targetDoc[showTitle.split(';')[0]] as any as Field) : '#' + showTitle;
-                    }}
-                    SetValue={undoBatch((input: string) => {
-                        if (input?.startsWith('#')) {
-                            if (this.rootDoc.layout_showTitle) {
-                                this.rootDoc._layout_showTitle = input?.substring(1) ? input.substring(1) : undefined;
-                            } else if (!this.props.layout_showTitle) {
-                                Doc.UserDoc().layout_showTitle = input?.substring(1) ? input.substring(1) : 'author_date';
+                {!dropdownWidth
+                    ? null
+                    : this.fieldsDropdown(
+                          [],
+                          dropdownWidth,
+                          StrCast(this.layoutDoc.layout_showTitle).split(':')[0],
+                          action((field: string | number) => {
+                              if (this.rootDoc.layout_showTitle) {
+                                  this.rootDoc._layout_showTitle = field;
+                              } else if (!this.props.layout_showTitle) {
+                                  Doc.UserDoc().layout_showTitle = field;
+                              }
+                              this._changingTitleField = false;
+                          }),
+                          action(() => (this._changingTitleField = false))
+                      )}
+                <div
+                    style={{
+                        width: `calc(100% - ${dropdownWidth}px)`,
+                        minWidth: '100px',
+                        color: this._titleRef.current?._editing || this._changingTitleField ? 'black' : undefined,
+                        background: this._titleRef.current?._editing || this._changingTitleField ? 'yellow' : undefined,
+                    }}>
+                    <EditableView
+                        ref={this._titleRef}
+                        contents={showTitle
+                            .split(';')
+                            .map(field => targetDoc[field.trim()]?.toString())
+                            .join(' \\ ')}
+                        display="block"
+                        oneLine={true}
+                        fontSize={(this.titleHeight / 15) * 10}
+                        GetValue={() => (showTitle.split(';').length !== 1 ? '#' + showTitle : Field.toKeyValueString(this.rootDoc, showTitle.split(';')[0]))}
+                        SetValue={undoBatch((input: string) => {
+                            if (input?.startsWith('#')) {
+                                if (this.rootDoc.layout_showTitle) {
+                                    this.rootDoc._layout_showTitle = input?.substring(1);
+                                } else if (!this.props.layout_showTitle) {
+                                    Doc.UserDoc().layout_showTitle = input?.substring(1) ?? 'author_date';
+                                }
+                            } else if (showTitle && !showTitle.includes('Date') && showTitle !== 'author') {
+                                KeyValueBox.SetField(targetDoc, showTitle, input);
                             }
-                        } else {
-                            var value = input.replace(new RegExp(showTitle + '='), '') as string | number;
-                            if (showTitle !== 'title' && Number(value).toString() === value) value = Number(value);
-                            if (showTitle.includes('Date') || showTitle === 'author') return true;
-                            Doc.SetInPlace(targetDoc, showTitle, value, true);
-                        }
-                        return true;
-                    })}
-                />
+                            return true;
+                        })}
+                    />
+                </div>
             </div>
         );
         return this.props.hideTitle || (!showTitle && !this.layout_showCaption) ? (
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 6765e1dea..818c0cbe7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -37,7 +37,6 @@ audiotag:hover {
     background: inherit;
     padding: 0;
     border-width: 0px;
-    border-radius: inherit;
     border-color: $medium-gray;
     box-sizing: border-box;
     background-color: inherit;
-- 
cgit v1.2.3-70-g09d2


From a866baea5fdbf30650cbfbf4aa383019ef61ec3d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 27 Oct 2023 13:39:04 -0400
Subject: added collection itself to timeline

---
 src/client/views/animationtimeline/Timeline.tsx | 14 +++-----------
 src/client/views/animationtimeline/Track.tsx    |  2 +-
 src/client/views/nodes/DocumentView.tsx         |  2 +-
 3 files changed, 5 insertions(+), 13 deletions(-)

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

diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index 71517d4fe..4be3b05ab 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -543,7 +543,7 @@ export class Timeline extends React.Component<FieldViewProps> {
                                 <div key="timeline_scrubberhead" className="scrubberhead" onPointerDown={this.onScrubberDown}></div>
                             </div>
                             <div key="timeline_trackbox" className="trackbox" ref={this._trackbox} style={{ width: `${this._totalLength}px` }}>
-                                {this.children.map(doc => (
+                                {[...this.children, this.props.Document].map(doc => (
                                     <Track
                                         ref={ref => this.mapOfTracks.push(ref)}
                                         timeline={this}
@@ -562,16 +562,8 @@ export class Timeline extends React.Component<FieldViewProps> {
                         </div>
                         <div className="currentTime">Current: {this.getCurrentTime()}</div>
                         <div key="timeline_title" className="title-container" ref={this._titleContainer}>
-                            {this.children.map(doc => (
-                                <div
-                                    style={{ height: `${this._titleHeight}px` }}
-                                    className="datapane"
-                                    onPointerOver={() => {
-                                        Doc.BrushDoc(doc);
-                                    }}
-                                    onPointerOut={() => {
-                                        Doc.UnBrushDoc(doc);
-                                    }}>
+                            {[...this.children, this.props.Document].map(doc => (
+                                <div style={{ height: `${this._titleHeight}px` }} className="datapane" onPointerOver={() => Doc.BrushDoc(doc)} onPointerOut={() => Doc.UnBrushDoc(doc)}>
                                     <p>{StrCast(doc.title)}</p>
                                 </div>
                             ))}
diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx
index b90ff5eaf..dfab849a4 100644
--- a/src/client/views/animationtimeline/Track.tsx
+++ b/src/client/views/animationtimeline/Track.tsx
@@ -34,7 +34,7 @@ export class Track extends React.Component<IProps> {
     @observable private _newKeyframe: boolean = false;
     private readonly MAX_TITLE_HEIGHT = 75;
     @observable private _trackHeight = 0;
-    private primitiveWhitelist = ['x', 'y', '_width', '_height', '_rotation', 'opacity', '_layout_scrollTop'];
+    private primitiveWhitelist = ['x', 'y', '_freeform_panX', '_freeform_panY', '_width', '_height', '_rotation', 'opacity', '_layout_scrollTop'];
     private objectWhitelist = ['data'];
 
     @computed private get regions() {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4a43ffe3e..43be4b724 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1312,7 +1312,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     pointerEvents: this.pointerEvents === 'visiblePainted' ? 'none' : this.pointerEvents,
                 }}>
                 <>
-                    {DocumentViewInternal.AnimationEffect(renderDoc, this.rootDoc[Animation], this.rootDoc)}
+                    {this._componentView instanceof KeyValueBox ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.rootDoc[Animation], this.rootDoc)}
                     {borderPath?.jsx}
                 </>
             </div>
-- 
cgit v1.2.3-70-g09d2


From a091c6142db5c1da94807abf14e78ed69e62f794 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 31 Oct 2023 12:21:30 -0400
Subject: fixed groups to use actual document boundaries for ink or link lines,
 instead of boundingbox.  fixed doc title css.  started to make link lines
 more compatible with ink.

---
 src/client/util/InteractionUtils.tsx                  |  6 +++---
 src/client/views/DocumentDecorations.tsx              | 11 ++++-------
 src/client/views/EditableView.tsx                     |  2 ++
 src/client/views/InkingStroke.tsx                     |  1 -
 src/client/views/PropertiesButtons.tsx                |  2 +-
 src/client/views/PropertiesView.tsx                   |  7 +------
 src/client/views/StyleProvider.scss                   |  2 +-
 src/client/views/StyleProvider.tsx                    | 19 ++++++++++---------
 .../collectionFreeForm/CollectionFreeFormView.tsx     |  5 +----
 .../views/nodes/CollectionFreeFormDocumentView.tsx    | 17 +++++++++++++----
 src/client/views/nodes/DocumentView.tsx               | 17 +++++++++--------
 src/client/views/nodes/LinkBox.tsx                    | 12 ++++++++++--
 src/client/views/nodes/trails/PresBox.tsx             |  2 +-
 src/client/views/pdf/PDFViewer.tsx                    |  2 +-
 14 files changed, 57 insertions(+), 48 deletions(-)

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

diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 7caeee588..be885312d 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -94,7 +94,7 @@ export namespace InteractionUtils {
         width: number,
         strokeWidth: number,
         lineJoin: string,
-        lineCap: string,
+        strokeLineCap: string,
         bezier: string,
         fill: string,
         arrowStart: string,
@@ -181,8 +181,8 @@ export namespace InteractionUtils {
                         // opacity: strokeWidth !== width ? 0.5 : undefined,
                         pointerEvents: (pevents as any) === 'all' ? 'visiblepainted' : (pevents as any),
                         stroke: color ?? 'rgb(0, 0, 0)',
-                        strokeWidth: strokeWidth,
-                        strokeLinecap: lineCap as any,
+                        strokeWidth,
+                        strokeLinecap: strokeLineCap as any,
                         strokeDasharray: dashArray,
                         transition: 'inherit',
                     }}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 9dafb12fb..65f7c7a70 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -475,6 +475,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
+        this._isResizing = true;
         setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
         DocumentView.Interacting = true; // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
         this._resizeHdlId = e.currentTarget.className;
@@ -685,6 +686,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
 
     @action
     onPointerUp = (e: PointerEvent): void => {
+        this._isResizing = false;
         this._resizeHdlId = '';
         DocumentView.Interacting = false;
         this._resizeUndo?.end();
@@ -772,7 +774,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         var shareSymbolIcon = ReverseHierarchyMap.get(shareMode)?.image;
 
         // hide the decorations if the parent chooses to hide it or if the document itself hides it
-        const hideDecorations = seldocview.props.hideDecorations || seldocview.rootDoc.hideDecorations;
+        const hideDecorations = this._isResizing || seldocview.props.hideDecorations || seldocview.rootDoc.layout_hideDecorations;
         const hideResizers =
             ![AclAdmin, AclEdit, AclAugment].includes(GetEffectiveAcl(seldocview.rootDoc)) || hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.layout_hideResizeHandles || this._isRounding || this._isRotating;
         const hideTitle = this._showNothing || hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.layout_hideDecorationTitle || this._isRounding || this._isRotating;
@@ -861,12 +863,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 onPointerDown={e => e.stopPropagation()}
             />
         ) : (
-            <div
-                className="documentDecorations-title"
-                key="title"
-                onPointerDown={e => {
-                    e.stopPropagation;
-                }}>
+            <div className="documentDecorations-title" key="title" onPointerDown={e => e.stopPropagation}>
                 {hideTitle ? null : (
                     <span className="documentDecorations-titleSpan" onPointerDown={this.onTitleDown}>
                         {this.selectionTitle}
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index ed7c544fc..abb7ed7ee 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -290,6 +290,8 @@ export class EditableView extends React.Component<EditableProps> {
                     whiteSpace: this.props.oneLine ? 'nowrap' : 'pre-line',
                     height: this.props.height,
                     maxHeight: this.props.maxHeight,
+                    fontStyle: this.props.fontStyle,
+                    fontSize: this.props.fontSize,
                 }}
                 //onPointerDown={this.stopPropagation}
                 onClick={this.onClick}
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index d619f5123..c3a6b1607 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -372,7 +372,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
     }
     render() {
         TraceMobx();
-        if (!this.rootDoc._layout_isSvg) setTimeout(action(() => (this.rootDoc._layout_isSvg = true)));
         const { inkData, inkStrokeWidth, inkLeft, inkTop, inkScaleX, inkScaleY, inkWidth, inkHeight } = this.inkScaledData();
 
         const startMarker = StrCast(this.layoutDoc.stroke_startMarker);
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index ff79a8390..18f53b8e7 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -492,7 +492,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     render() {
         const layoutField = this.selectedDoc?.[Doc.LayoutFieldKey(this.selectedDoc)];
         const isText = layoutField instanceof RichTextField;
-        const isInk = layoutField instanceof InkField;
+        const isInk = this.selectedDoc?.layout_isSvg;
         const isImage = layoutField instanceof ImageField;
         const isMap = this.selectedDoc?.type === DocumentType.MAP;
         const isCollection = this.selectedDoc?.type === DocumentType.COL;
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 04a948a27..d37971517 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -73,9 +73,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     @computed get isPres(): boolean {
         return this.selectedDoc?.type === DocumentType.PRES;
     }
-    @computed get isLink(): boolean {
-        return this.selectedDoc?.type === DocumentType.LINK;
-    }
     @computed get dataDoc() {
         return this.selectedDoc?.[DocData];
     }
@@ -1174,12 +1171,10 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     }
 
     @computed get inkSubMenu() {
-        let isDouble = false;
-
         return (
             <>
                 <PropertiesSection title="Appearance" isOpen={this.openAppearance} setIsOpen={bool => (this.openAppearance = bool)} onDoubleClick={() => this.CloseAll()}>
-                    {this.isInk ? this.appearanceEditor : null}
+                    {this.selectedDoc?.layout_isSvg ? this.appearanceEditor : null}
                 </PropertiesSection>
                 <PropertiesSection title="Transform" isOpen={this.openTransform} setIsOpen={bool => (this.openTransform = bool)} onDoubleClick={() => this.CloseAll()}>
                     {this.transformEditor}
diff --git a/src/client/views/StyleProvider.scss b/src/client/views/StyleProvider.scss
index f069e7e1b..4d3096f71 100644
--- a/src/client/views/StyleProvider.scss
+++ b/src/client/views/StyleProvider.scss
@@ -12,7 +12,7 @@
     opacity: 0.3;
     display: flex;
     flex-direction: column;
-    color: gold;
+    color: red;
     border-radius: 3px;
     justify-content: center;
     cursor: default;
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 5ff743a30..c6d3efd0c 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -83,8 +83,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
     const selected = property.includes(':selected');
     const isCaption = property.includes(':caption');
     const isAnchor = property.includes(':anchor');
+    const isContent = property.includes(':content');
     const isAnnotated = property.includes(':annotated');
-    const isInk = () => doc && doc._layout_isSvg && !props?.LayoutTemplateString;
+    const isInk = () => doc?._layout_isSvg && !props?.LayoutTemplateString;
     const isOpen = property.includes(':open');
     const isEmpty = property.includes(':empty');
     const boxBackground = property.includes(':box');
@@ -121,14 +122,14 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                 const highlightStyle = ['solid', 'dashed', 'solid', 'solid', 'solid'][highlightIndex];
                 if (highlightIndex) {
                     return {
-                        highlightStyle,
+                        highlightStyle: doc._isGroup ? "dotted": highlightStyle,
                         highlightColor,
                         highlightIndex,
-                        highlightStroke: doc.type === DocumentType.INK || doc.type === DocumentType.LINK,
+                        highlightStroke: doc.layout_isSvg,
                     };
                 }
             }
-            return undefined;
+            return  undefined;
         case StyleProp.DocContents:return undefined;
         case StyleProp.WidgetColor:return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey';
         case StyleProp.Opacity:    return props?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 :  Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
@@ -171,7 +172,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.BorderPath:
             const borderPath = Doc.IsComicStyle(doc) &&
                 props?.renderDepth &&
-                ![DocumentType.INK, DocumentType.LINK].includes(doc?.type as any) && { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, 0.08), width: 3 };
+                !doc?.layout_isSvg && { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, 0.08), width: 3 };
             return !borderPath
                 ? null
                 : {
@@ -231,7 +232,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.BoxShadow: {
             if (!doc || opacity() === 0 || doc.noShadow) return undefined; // if it's not visible, then no shadow)
             if (doc.layout_boxShadow === 'standard') return Shadows.STANDARD_SHADOW;
-            if (IsFollowLinkScript(doc?.onClick) && LinkManager.Links(doc).length && ![DocumentType.LINK, DocumentType.INK].includes(doc.type as any)) return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
+            if (IsFollowLinkScript(doc?.onClick) && LinkManager.Links(doc).length && !doc.layout_isSvg) return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
             switch (doc?.type) {
                 case DocumentType.COL:
                     return StrCast(
@@ -240,7 +241,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                             ? '4px 4px 10px 2px'
                             : lockedPosition() || doc?._isGroup || docProps?.LayoutTemplateString
                             ? undefined // groups have no drop shadow -- they're supposed to be "invisible".  LayoutString's imply collection is being rendered as something else (e.g., title of a Slide)
-                            : `${Colors.MEDIUM_GRAY} ${StrCast(doc.layout_boxShadow, '0.2vw 0.2vw 0.8vw')}`
+                            : `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0.2vw 0.2vw 0.8vw')}`
                     );
 
                 case DocumentType.LABEL:
@@ -258,12 +259,12 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             }
         }
         case StyleProp.PointerEvents:
-            if (!props?.treeViewDoc && doc?._isGroup && (!doc.backgroundColor || doc.backgroundColor === 'transparent') && !props?.DocumentView?.().docView?.isContentActive?.()) return 'none'; // groups with no backgrounds that are not active (eg selected, or child selected), don't directly respond to pointer events (but their children can to allow the group to be selected or dragged)
             if (StrCast(doc?.pointerEvents) && !props?.LayoutTemplateString?.includes(KeyValueBox.name)) return StrCast(doc!.pointerEvents); // honor pointerEvents field (set by lock button usually) if it's not a keyValue view of the Doc
             if (docProps?.DocumentView?.().ComponentView?.overridePointerEvents?.() !== undefined) return docProps?.DocumentView?.().ComponentView?.overridePointerEvents?.();
             if (DocumentView.ExploreMode || doc?.layout_unrendered) return isInk() ? 'visiblePainted' : 'all';
             if (props?.pointerEvents?.() === 'none') return 'none';
             if (opacity() === 0) return 'none';
+            if (props?.isGroupActive?.() ) return isInk()  ? 'visiblePainted':  (doc?._isGroup )? undefined: 'all' 
             if (props?.isDocumentActive?.() && !props.treeViewDoc) return isInk() ? 'visiblePainted' : 'all';
             return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active
         case StyleProp.Decorations:
@@ -271,7 +272,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                 if (props?.docViewPath().lastElement()?.rootDoc?._type_collection === CollectionViewType.Freeform) {
                     return doc?.pointerEvents !== 'none' ? null : (
                         <div className="styleProvider-lock" onClick={() => toggleLockedPosition(doc)}>
-                            <FontAwesomeIcon icon='lock' style={{ color: 'red' }} size="lg" />
+                            <FontAwesomeIcon icon='lock' size="lg" />
                         </div>
                     );
                 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9639ec73c..0d43d6ab8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1300,16 +1300,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
     childPointerEventsFunc = () => this.childPointerEvents;
     childContentsActive = () => (this.props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)();
-    groupChildPointerEvents = () => (this.props.isDocumentActive?.() && !this.isContentActive() ? 'all' : 'none');
     getChildDocView(entry: PoolData) {
         const childLayout = entry.pair.layout;
         const childData = entry.pair.data;
         return (
             <CollectionFreeFormDocumentView
                 key={childLayout[Id] + (entry.replica || '')}
-                GroupPointerEvents={this.rootDoc._isGroup ? this.groupChildPointerEvents : (childLayout._isGroup && !this.childContentsActive()) || childLayout.pointerEvents === 'none' ? returnNone : undefined}
                 DataDoc={childData}
                 Document={childLayout}
+                isGroupActive={this.props.isGroupActive}
                 renderDepth={this.props.renderDepth + 1}
                 replica={entry.replica}
                 hideDecorations={BoolCast(childLayout._layout_isSvg && childLayout.type === DocumentType.LINK)}
@@ -1831,8 +1830,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
         this.props.Document._isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: () => this.transcribeStrokes(false), icon: 'font' });
 
-        // this.props.Document._isGroup && this.childDocs.filter(s => s.type === DocumentType.INK).length > 0 && appearanceItems.push({ description: "Ink to math", event: () => this.transcribeStrokes(true), icon: "square-root-alt" });
-
         !Doc.noviceMode ? appearanceItems.push({ description: 'Arrange contents in grid', event: this.layoutDocsInGrid, icon: 'table' }) : null;
         !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
 
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 199835dd9..9f7ebc5d9 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -25,7 +25,6 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     dataTransition?: string;
     replica: string;
     CollectionFreeFormView: CollectionFreeFormView;
-    GroupPointerEvents?: () => 'none' | 'all' | undefined; // pointer events for this freeform doc view wrapper that are not passed to the docView.  This allows items in a group to trigger the group to be selected, without selecting the items themselves
 }
 
 @observer
@@ -192,17 +191,27 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     screenToLocalTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.X, -this.Y);
     returnThis = () => this;
 
+    /// this indicates whether the doc view is activated because of its relationshop to a group
+    //  'group' - this is a group that is activated because it's on an active canvas, but is not part of some other group
+    //  'child' - this is a group child that is activated because its containing group is activated
+    //  'inactive' - this is a group child but it is not active
+    //  undefined - this is not activated by a group
+    isGroupActive = () => {
+        if (this.props.CollectionFreeFormView.isAnyChildContentActive()) return undefined;
+        const isGroup = this.rootDoc._isGroup && (!this.rootDoc.backgroundColor || this.rootDoc.backgroundColor === 'transparent');
+        return isGroup ? (this.props.isDocumentActive?.() ? 'group' : this.props.isGroupActive?.() ? 'child' : 'inactive') : this.props.isGroupActive?.() ? 'child' : undefined;
+    };
     render() {
         TraceMobx();
         const divProps: DocumentViewProps = {
-            ...OmitKeys(this.props, ['GroupPointerEvents']).omit,
+            ...this.props,
             CollectionFreeFormDocumentView: this.returnThis,
             styleProvider: this.styleProvider,
             ScreenToLocalTransform: this.screenToLocalTransform,
             PanelWidth: this.panelWidth,
             PanelHeight: this.panelHeight,
+            isGroupActive: this.isGroupActive,
         };
-        const isInk = this.layoutDoc._layout_isSvg && !this.props.LayoutTemplateString && !this.layoutDoc._stroke_isInkMask;
         return (
             <div
                 className="collectionFreeFormDocumentView-container"
@@ -214,7 +223,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     transition: this.dataProvider?.transition ?? (this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition)),
                     zIndex: this.ZInd,
                     display: this.sizeProvider?.width ? undefined : 'none',
-                    pointerEvents: this.props.GroupPointerEvents?.() ?? (isInk ? 'none' : undefined),
+                    pointerEvents: 'none',
                 }}>
                 {this.props.renderCutoffProvider(this.props.Document) ? (
                     <div style={{ position: 'absolute', width: this.panelWidth(), height: this.panelHeight(), background: 'lightGreen' }} />
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 43be4b724..a2a084200 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,5 +1,6 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
+import { Dropdown, DropdownType, Type } from 'browndash-components';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
@@ -38,7 +39,6 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { DocComponent } from '../DocComponent';
 import { EditableView } from '../EditableView';
 import { GestureOverlay } from '../GestureOverlay';
-import { InkingStroke } from '../InkingStroke';
 import { LightboxView } from '../LightboxView';
 import { StyleProp } from '../StyleProvider';
 import { UndoStack } from '../UndoStack';
@@ -48,14 +48,11 @@ import { DocumentLinksButton } from './DocumentLinksButton';
 import './DocumentView.scss';
 import { FieldViewProps } from './FieldView';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
+import { KeyValueBox } from './KeyValueBox';
 import { LinkAnchorBox } from './LinkAnchorBox';
 import { PresEffect, PresEffectDirection } from './trails';
 import { PinProps, PresBox } from './trails/PresBox';
 import React = require('react');
-import { KeyValueBox } from './KeyValueBox';
-import { LinkBox } from './LinkBox';
-import { FilterPanel } from '../FilterPanel';
-import { Dropdown, DropdownType, Type } from 'browndash-components';
 const { Howl } = require('howler');
 
 interface Window {
@@ -159,6 +156,7 @@ export interface DocumentViewSharedProps {
     Document: Doc;
     DataDoc?: Doc;
     fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
+    isGroupActive?: () => string | undefined; // is this document part of a group that is active
     suppressSetHeight?: boolean;
     setContentView?: (view: DocComponentView) => any;
     CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
@@ -428,6 +426,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     public static addDocTabFunc: (doc: Doc, location: OpenWhere) => boolean = returnFalse;
 
     onClick = action((e: React.MouseEvent | React.PointerEvent) => {
+        if (this.props.isGroupActive?.() === 'child' && !this.props.isDocumentActive?.()) return;
         if (!this.Document.ignoreClick && this.props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
             let stopPropagate = true;
             let preventDefault = true;
@@ -516,6 +515,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
+        if (this.props.isGroupActive?.() === 'child' && !this.props.isDocumentActive?.()) return;
         this._longPressSelector = setTimeout(() => {
             if (DocumentView.LongPress) {
                 if (this.rootDoc.undoIgnoreFields) {
@@ -906,11 +906,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @computed get contents() {
         TraceMobx();
         const isInk = this.layoutDoc._layout_isSvg && !this.props.LayoutTemplateString;
+        const noBackground = this.rootDoc._isGroup && (!this.rootDoc.backgroundColor || this.rootDoc.backgroundColor === 'transparent');
         return (
             <div
                 className="documentView-contentsView"
                 style={{
-                    pointerEvents: (isInk ? 'none' : this.contentPointerEvents()) ?? 'all',
+                    pointerEvents: (isInk || noBackground ? 'none' : this.contentPointerEvents()) ?? 'all',
                     height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
                 }}>
                 <DocumentContentsView
@@ -1309,7 +1310,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 onPointerLeave={e => !isParentOf(this.ContentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.rootDoc)}
                 style={{
                     borderRadius: this.borderRounding,
-                    pointerEvents: this.pointerEvents === 'visiblePainted' ? 'none' : this.pointerEvents,
+                    pointerEvents: this.pointerEvents === 'visiblePainted' ? 'none' : this.pointerEvents, // visible painted means that the underlying doc contents are irregular and will process their own pointer events (otherwise, the contents are expected to fill the entire doc view box so we can handle pointer events here)
                 }}>
                 <>
                     {this._componentView instanceof KeyValueBox ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.rootDoc[Animation], this.rootDoc)}
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index d871c88ba..682267ef1 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -4,7 +4,7 @@ import { computed, action } from 'mobx';
 import { observer } from 'mobx-react';
 import { Height, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
-import { DocCast, StrCast } from '../../../fields/Types';
+import { DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { aggregateBounds, emptyFunction, returnAlways, returnFalse, Utils } from '../../../Utils';
 import { DocumentManager } from '../../util/DocumentManager';
 import { ViewBoxBaseComponent } from '../DocComponent';
@@ -92,6 +92,9 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const bez = new Bezier(pts.map(p => ({ x: p[0], y: p[1] })));
             const text = bez.get(0.5);
             const linkDesc = StrCast(this.rootDoc.link_description) || 'description';
+            const strokeWidth = NumCast(this.rootDoc.stroke_width, 4);
+            const dash = StrCast(this.rootDoc.stroke_dash);
+            const strokeDasharray = dash && Number(dash) ? String(strokeWidth * Number(dash)) : undefined;
             return (
                 <div style={{ pointerEvents: 'none', position: 'absolute', width: '100%', height: '100%' }}>
                     <svg width={Math.max(100, rx - lx)} height={Math.max(100, by - ty)} style={{ overflow: 'visible' }}>
@@ -106,7 +109,12 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         </defs>
                         <path
                             className="collectionfreeformlinkview-linkLine"
-                            style={{ pointerEvents: this.props.pointerEvents?.() === 'none' ? 'none' : 'visibleStroke', stroke: highlightColor ?? 'lightblue', strokeWidth: 4 }}
+                            style={{
+                                pointerEvents: this.props.pointerEvents?.() === 'none' ? 'none' : 'visibleStroke', //
+                                stroke: highlightColor ?? 'lightblue',
+                                strokeDasharray,
+                                strokeWidth,
+                            }}
                             d={`M ${pts[1][0] - lx} ${pts[1][1] - ty} C ${pts[1][0] + pts[1][0] - pts[0][0] - lx} ${pts[1][1] + pts[1][1] - pts[0][1] - ty}, 
                             ${pts[2][0] + pts[2][0] - pts[3][0] - lx} ${pts[2][1] + pts[2][1] - pts[3][1] - ty}, ${pts[2][0] - lx} ${pts[2][1] - ty}`}
                         />
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index ceeb10d93..70d9443a2 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -792,7 +792,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             willPan: activeItem.presentation_movement !== PresMovement.None,
             willZoomCentered: activeItem.presentation_movement === PresMovement.Zoom || activeItem.presentation_movement === PresMovement.Jump || activeItem.presentation_movement === PresMovement.Center,
             zoomScale: activeItem.presentation_movement === PresMovement.Center ? 0 : NumCast(activeItem.config_zoom, 1),
-            zoomTime: activeItem.presentation_movement === PresMovement.Jump ? 0 : Math.min(Math.max(effect ? 750 : 500, (effect ? 1 : 1) * presTime), presTime),
+            zoomTime: activeItem.presentation_movement === PresMovement.Jump ? 0 : Math.min(Math.max(effect ? 750 : 500, (effect ? 0.2 : 1) * presTime), presTime),
             effect: activeItem,
             noSelect: true,
             openLocation: OpenWhere.addLeft,
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 58a54764d..939928c1c 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -519,7 +519,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
             if (this.inlineTextAnnotations.includes(doc) || this.props.isContentActive() === false) return 'none';
-            const isInk = doc && StrCast(Doc.Layout(doc).layout).includes(InkingStroke.name) && !props?.LayoutTemplateString;
+            const isInk = doc.layout_isSvg && !props?.LayoutTemplateString;
             return isInk ? 'visiblePainted' : 'all';
         }
         return this.props.styleProvider?.(doc, props, property);
-- 
cgit v1.2.3-70-g09d2


From 58213b0201ea0191f06f42beac9c3a17ebfc98ea Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 1 Nov 2023 15:38:22 -0400
Subject: fixed so that ink properties can be set before drawing a stroke.
 fixed turning off ink when menu is collapsed.  added meta-click to fit all
 for collections, or zoom in on a document.

---
 src/client/util/CurrentUserUtils.ts                |  8 ++---
 .../views/collections/CollectionTreeView.tsx       |  2 --
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 15 ++++------
 .../collectionLinear/CollectionLinearView.tsx      |  1 +
 src/client/views/global/globalScripts.ts           | 35 ++++++++++++----------
 src/client/views/nodes/DocumentView.tsx            | 14 +++++++--
 .../views/nodes/generativeFill/GenerativeFill.tsx  | 32 +++++++++++---------
 src/client/views/nodes/trails/PresBox.tsx          |  4 +--
 8 files changed, 61 insertions(+), 50 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 591bc7430..87ee1b252 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -635,9 +635,9 @@ export class CurrentUserUtils {
         return [
             { title: "Snap",      icon: "th",                  toolTip: "Show Snap Lines",     btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines",  funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
             { title: "Grid",      icon: "border-all",          toolTip: "Show Grid",           btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid",       funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
-            { title: "View All",  icon: "object-group",        toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"viewAll",    funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+            { title: "View All",  icon: "object-group",        toolTip: "Keep all Docs in View",btnType: ButtonType.ToggleButton, ignoreClick:true, expertMode: false, toolType:"viewAll",    funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
             // want the same style as toggle button, but don't want it to act as an actual toggle, so set disableToggle to true,
-            { title: "Fit All",   icon: "arrows-left-right",   toolTip: "Fit all Docs to View (persistent)", btnType: ButtonType.ClickButton, ignoreClick: false, expertMode: false, toolType:"viewAllPersist",    funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+            { title: "Fit All",   icon: "arrows-left-right",   toolTip: "Fit Docs to View (once)",btnType: ButtonType.ClickButton,ignoreClick:false,expertMode: false, toolType:"fitOnce",    funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
             { title: "Clusters",  icon: "braille",             toolTip: "Show Doc Clusters",   btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters",   funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
             { title: "Cards",     icon: "brain",               toolTip: "Flashcards",          btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
             { title: "Arrange",   icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange",  btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"arrange",    funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
@@ -673,7 +673,7 @@ export class CurrentUserUtils {
     static inkTools():Button[] {
         return [
             { title: "Pen",    toolTip: "Pen (Ctrl+P)",                     btnType: ButtonType.ToggleButton, icon: "pen-nib",    toolType: "pen",                            scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }},
-            { title: "Write",  toolTip: "Write (Ctrl+Shift+P)",             btnType: ButtonType.ToggleButton, icon: "pen",        toolType: "write",                          scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}'} },
+            { title: "Write",  toolTip: "Write (Ctrl+Shift+P)",             btnType: ButtonType.ToggleButton, icon: "pen",        toolType: "write",                          scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }},
             { title: "Eraser", toolTip: "Eraser (Ctrl+E)",                  btnType: ButtonType.ToggleButton, icon: "eraser",     toolType: "eraser",                         scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }},
             { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle",     toolType:GestureUtils.Gestures.Circle,      scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} },
             { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square",     toolType:GestureUtils.Gestures.Rectangle,   scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} },
@@ -708,7 +708,7 @@ export class CurrentUserUtils {
               title: "Perspective",                    toolTip: "View",                 btnType: ButtonType.DropdownList,                                                                                                                                    ignoreClick: true, width: 100, scripts: { script:  'setView(value, _readOnly_)'}},
             { title: "Pin",     icon: "map-pin",       toolTip: "Pin View to Trail",    btnType: ButtonType.ClickButton,         expertMode: false,                                                                                                                             width: 30,  scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}},
             { title: "Header",  icon: "heading",       toolTip: "Doc Titlebar Color",   btnType: ButtonType.ColorButton,         expertMode: true,                                                                                                           ignoreClick: true,             scripts: { script:  'return setHeaderColor(value, _readOnly_)'} },
-            { title: "Fill",    icon: "fill-drip",     toolTip: "Background Fill Color",btnType: ButtonType.ColorButton,         expertMode: false,                                                                                                          ignoreClick: true, width: 30,  scripts: { script:  'return setBackgroundColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}}, // Only when a document is selected
+            { title: "Fill",    icon: "fill-drip",     toolTip: "Fill/Background Color",btnType: ButtonType.ColorButton,         expertMode: false,                                                                                                          ignoreClick: true, width: 30,  scripts: { script:  'return setBackgroundColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}}, // Only when a document is selected
             { title: "Overlay", icon: "layer-group",   toolTip: "Overlay",              btnType: ButtonType.ToggleButton,        expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'},       scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform
             { title: "Back",    icon: "chevron-left",  toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30,  scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
             { title: "Num",     icon:"",               toolTip: "Frame Number (click to toggle edit mode)",    btnType: ButtonType.TextButton,          expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 9e5ac77d9..e408c193a 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -98,8 +98,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         Object.values(this._disposers).forEach(disposer => disposer?.());
     }
 
-    shrinkWrap = () => {}; // placeholder to allow setContentView to work
-
     componentDidMount() {
         //this.props.setContentView?.(this);
         this._disposers.autoheight = reaction(
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 1e341f60d..a8b743896 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -312,6 +312,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     focus = (anchor: Doc, options: DocFocusOptions) => {
         if (this._lightboxDoc) return;
+        if (anchor === this.rootDoc) {
+            if (options.willZoomCentered && options.zoomScale) {
+                this.fitContentOnce();
+                options.didMove = true;
+            }
+        }
         if (anchor.type !== DocumentType.CONFIG && !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor)) return;
         const xfToCollection = options?.docTransform ?? Transform.Identity();
         const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
@@ -1620,15 +1626,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     () => this.isContentActive(), // if autoreset is on, then whenever the view is selected, it will be restored to it default pan/zoom positions
                     active => !SnappingManager.GetIsDragging() && this.rootDoc[this.autoResetFieldKey] && active && this.resetView()
                 );
-
-                this._disposers.fitContent = reaction(
-                    () => this.rootDoc.fitContentOnce,
-                    fitContentOnce => {
-                        if (fitContentOnce) this.fitContentOnce();
-                        this.rootDoc.fitContentOnce = undefined;
-                    },
-                    { fireImmediately: true, name: 'fitContent' }
-                );
             })
         );
     }
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index faf7501c4..9a2c79a22 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -215,6 +215,7 @@ export class CollectionLinearView extends CollectionSubView() {
                 toggleStatus={BoolCast(this.layoutDoc.linearView_IsOpen)}
                 onClick={() => {
                     this.layoutDoc.linearView_IsOpen = !isExpanded;
+                    ScriptCast(this.rootDoc.onClick)?.script.run({ this: this.rootDoc }, console.log);
                 }}
                 tooltip={isExpanded ? 'Close' : 'Open'}
                 fillWidth={true}
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index ced11447c..894afebfd 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -13,12 +13,13 @@ import { SelectionManager } from '../../util/SelectionManager';
 import { undoable, UndoManager } from '../../util/UndoManager';
 import { GestureOverlay } from '../GestureOverlay';
 import { InkTranscription } from '../InkTranscription';
-import { ActiveFillColor, SetActiveFillColor, ActiveIsInkMask, SetActiveIsInkMask, ActiveInkWidth, SetActiveInkWidth, ActiveInkColor, SetActiveInkColor } from '../InkingStroke';
+import { ActiveFillColor, SetActiveFillColor, ActiveIsInkMask, SetActiveIsInkMask, ActiveInkWidth, SetActiveInkWidth, ActiveInkColor, SetActiveInkColor, InkingStroke } from '../InkingStroke';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm';
 import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
 import { WebBox } from '../nodes/WebBox';
 import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
 import { DocumentType } from '../../documents/DocumentTypes';
+import { DocumentView } from '../nodes/DocumentView';
 
 ScriptingGlobals.add(function IsNoneSelected() {
     return SelectionManager.Views().length <= 0;
@@ -46,6 +47,7 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
             const contentFrameNumber = Cast(selView.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
             return CollectionFreeFormDocumentView.getStringValues(selView?.rootDoc, contentFrameNumber)[fieldKey] ?? 'transparent';
         }
+        selectedViews.some(dv => dv.ComponentView instanceof InkingStroke) && SetActiveFillColor(color ?? 'transparent');
         selectedViews.forEach(dv => {
             const fieldKey = dv.rootDoc.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
             const layoutFrameNumber = Cast(dv.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
@@ -61,6 +63,7 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
         if (checkResult) {
             return selected.lastElement()?._backgroundColor ?? 'transparent';
         }
+        SetActiveFillColor(color ?? 'transparent');
         selected.forEach(doc => (doc._backgroundColor = color));
     }
 });
@@ -92,43 +95,43 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
     selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
 });
 
-ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'viewAllPersist', checkResult?: boolean) {
+ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce', checkResult?: boolean) {
     const selected = SelectionManager.Docs().lastElement();
     // prettier-ignore
-    const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'viewAllPersist', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([
+    const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([
         ['grid', {
             checkResult: (doc:Doc) => BoolCast(doc._freeform_backgroundGrid, false),
-            setDoc: (doc:Doc) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid,
+            setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid,
         }],
         ['snaplines', {
             checkResult: (doc:Doc) => BoolCast(doc._freeform_snapLines, false),
-            setDoc: (doc:Doc) => doc._freeform_snapLines = !doc._freeform_snapLines,
+            setDoc: (doc:Doc, dv:DocumentView) => doc._freeform_snapLines = !doc._freeform_snapLines,
         }],
         ['viewAll', {
             checkResult: (doc:Doc) => BoolCast(doc._freeform_fitContentsToBox, false),
-            setDoc: (doc:Doc) => doc._freeform_fitContentsToBox = !doc._freeform_fitContentsToBox,
+            setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_fitContentsToBox = !doc._freeform_fitContentsToBox,
         }],
         ['center', {
             checkResult: (doc:Doc) => BoolCast(doc._stacking_alignCenter, false),
-            setDoc: (doc:Doc) => doc._stacking_alignCenter = !doc._stacking_alignCenter,
+            setDoc: (doc:Doc,dv:DocumentView) => doc._stacking_alignCenter = !doc._stacking_alignCenter,
         }],
-        ['viewAllPersist', {
+        ['fitOnce', {
             checkResult: (doc:Doc) => false,
-            setDoc: (doc:Doc) => doc.fitContentOnce = true
+            setDoc: (doc:Doc, dv:DocumentView) => (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce()
         }],
         ['clusters', {
             waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire
             checkResult: (doc:Doc) => BoolCast(doc._freeform_useClusters, false),
-            setDoc: (doc:Doc) => doc._freeform_useClusters = !doc._freeform_useClusters,
+            setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_useClusters = !doc._freeform_useClusters,
         }],
         ['arrange', {
             waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire
             checkResult: (doc:Doc) => BoolCast(doc._autoArrange, false),
-            setDoc: (doc:Doc) => doc._autoArrange = !doc._autoArrange,
+            setDoc: (doc:Doc,dv:DocumentView) => doc._autoArrange = !doc._autoArrange,
         }],
         ['flashcards', {
             checkResult: (doc:Doc) => BoolCast(Doc.UserDoc().defaultToFlashcards, false),
-            setDoc: (doc:Doc) => Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards,
+            setDoc: (doc:Doc,dv:DocumentView) => Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards,
         }],
    ]);
 
@@ -136,7 +139,7 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid
         return map.get(attr)?.checkResult(selected);
     }
     const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
-    SelectionManager.Docs().map(dv => map.get(attr)?.setDoc(dv));
+    SelectionManager.Views().map(dv => map.get(attr)?.setDoc(dv.rootDoc, dv));
     setTimeout(() => batch.end(), 100);
 });
 
@@ -328,7 +331,7 @@ ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode');
 
 // toggle: Set overlay status of selected document
 ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', value: any, checkResult?: boolean) {
-    const selected = SelectionManager.Docs().lastElement();
+    const selected = SelectionManager.Docs().lastElement() ?? Doc.UserDoc();
     // prettier-ignore
     const map: Map<'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([
         ['inkMask', {
@@ -344,12 +347,12 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | '
         [ 'strokeWidth', {
             checkResult: () => (selected?.type === DocumentType.INK ? NumCast(selected.stroke_width) : ActiveInkWidth()),
             setInk: (doc: Doc) => (doc.stroke_width = NumCast(value)),
-            setMode: () => SetActiveInkWidth(value.toString()),
+            setMode: () => { SetActiveInkWidth(value.toString()); setActiveTool( GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);},
         }],
         ['strokeColor', {
             checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.color) : ActiveInkColor()),
             setInk: (doc: Doc) => (doc.color = String(value)),
-            setMode: () => SetActiveInkColor(StrCast(value)),
+            setMode: () => { SetActiveInkColor(StrCast(value));  setActiveTool(GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);},
         }],
     ]);
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a2a084200..3d6b53ccc 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -120,7 +120,6 @@ export interface DocComponentView {
     addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox
     addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections)
     reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height.  However, some content views (e.g., FreeFormView w/ fitContentsToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling.
-    shrinkWrap?: () => void; // requests a document to display all of its contents with no white space.  currently only implemented (needed?) for freeform views
     select?: (ctrlKey: boolean, shiftKey: boolean) => void;
     focus?: (textAnchor: Doc, options: DocFocusOptions) => Opt<number>;
     menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected.
@@ -498,7 +497,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     // prettier-ignore
                     clickFunc ?? (() => (sendToBack ? this.props.DocumentView().props.bringToFront(this.rootDoc, true) : 
                                                       this._componentView?.select?.(e.ctrlKey || e.metaKey, e.shiftKey) ?? 
-                                        this.props.select(e.ctrlKey || e.metaKey || e.shiftKey)));
+                                        this.props.select(e.ctrlKey||e.shiftKey, e.metaKey)));
                 const waitFordblclick = this.props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick;
                 if ((clickFunc && waitFordblclick !== 'never') || waitFordblclick === 'always') {
                     this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
@@ -1559,7 +1558,16 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     scaleToScreenSpace = () => (1 / (this.props.NativeDimScaling?.() || 1)) * this.screenToLocalTransform().Scale;
     docViewPathFunc = () => this.docViewPath;
     isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction);
-    select = (extendSelection: boolean) => SelectionManager.SelectView(this, extendSelection);
+    select = (extendSelection: boolean, focusSelection?: boolean) => {
+        SelectionManager.SelectView(this, extendSelection);
+        if (focusSelection) {
+            DocumentManager.Instance.showDocument(this.rootDoc, {
+                willZoomCentered: true,
+                zoomScale: 0.9,
+                zoomTime: 500,
+            });
+        }
+    };
     NativeWidth = () => this.effectiveNativeWidth;
     NativeHeight = () => this.effectiveNativeHeight;
     PanelWidth = () => this.panelWidth;
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 1ec6d6e3f..3093287e9 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -20,6 +20,8 @@ import { CursorData, ImageDimensions, Point } from './generativeFillUtils/genera
 import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler';
 import { PointerHandler } from './generativeFillUtils/PointerHandler';
 import React = require('react');
+import { DocumentManager } from '../../../util/DocumentManager';
+import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
 
 enum BrushStyle {
     ADD,
@@ -303,19 +305,21 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             originalImg.current = currImg.current;
             originalDoc.current = parentDoc.current;
             const { urls } = res as APISuccess;
-            const imgUrls = await Promise.all(urls.map(url => ImageUtility.convertImgToCanvasUrl(url, canvasDims.width, canvasDims.height)));
-            const imgRes = await Promise.all(
-                imgUrls.map(async url => {
-                    const saveRes = await onSave(url);
-                    return [url, saveRes as Doc];
-                })
-            );
-            setEdits(imgRes);
-            const image = new Image();
-            image.src = imgUrls[0];
-            ImageUtility.drawImgToCanvas(image, canvasRef, canvasDims.width, canvasDims.height);
-            currImg.current = image;
-            parentDoc.current = imgRes[0][1] as Doc;
+            if (res.status !== 'error') {
+                const imgUrls = await Promise.all(urls.map(url => ImageUtility.convertImgToCanvasUrl(url, canvasDims.width, canvasDims.height)));
+                const imgRes = await Promise.all(
+                    imgUrls.map(async url => {
+                        const saveRes = await onSave(url);
+                        return [url, saveRes as Doc];
+                    })
+                );
+                setEdits(imgRes);
+                const image = new Image();
+                image.src = imgUrls[0];
+                ImageUtility.drawImgToCanvas(image, canvasRef, canvasDims.width, canvasDims.height);
+                currImg.current = image;
+                parentDoc.current = imgRes[0][1] as Doc;
+            }
         } catch (err) {
             console.log(err);
         }
@@ -418,7 +422,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         ImageBox.setImageEditorOpen(false);
         ImageBox.setImageEditorSource('');
         if (newCollectionRef.current) {
-            newCollectionRef.current.fitContentOnce = true;
+            DocumentManager.Instance.AddViewRenderedCb(newCollectionRef.current, dv => (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce());
         }
         setEdits([]);
     };
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 6493117b0..54249a975 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -526,11 +526,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
         if (pinDataTypes?.inkable || (!pinDataTypes && (activeItem.config_fillColor !== undefined || activeItem.color !== undefined))) {
             if (bestTarget.fillColor !== activeItem.config_fillColor) {
-                Doc.GetProto(bestTarget).fillColor = activeItem.config_fillColor;
+                Doc.GetProto(bestTarget).fillColor = StrCast(activeItem.config_fillColor, StrCast(bestTarget.fillColor));
                 changed = true;
             }
             if (bestTarget.color !== activeItem.config_color) {
-                Doc.GetProto(bestTarget).color = activeItem.config_color;
+                Doc.GetProto(bestTarget).color = StrCast(activeItem.config_color, StrCast(bestTarget.color));
                 changed = true;
             }
             if (bestTarget.width !== activeItem.width) {
-- 
cgit v1.2.3-70-g09d2

