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.

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 65 ++++++++++++----------
 1 file changed, 35 insertions(+), 30 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bfc61f601..5eff6b8e0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -16,17 +16,18 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } fro
 import { ImageField } from '../../../../fields/URLField';
 import { TraceMobx } from '../../../../fields/util';
 import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
-import { aggregateBounds, emptyFunction, intersectRect, lightOrDark, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { aggregateBounds, emptyFunction, intersectRect, lightOrDark, returnFalse, returnNone, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager, dropActionType } from '../../../util/DragManager';
 import { InteractionUtils } from '../../../util/InteractionUtils';
+import { FollowLinkScript } from '../../../util/LinkFollower';
 import { ReplayMovements } from '../../../util/ReplayMovements';
 import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
 import { SelectionManager } from '../../../util/SelectionManager';
-import { ColorScheme, freeformScrollMode } from '../../../util/SettingsManager';
+import { freeformScrollMode } from '../../../util/SettingsManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch, UndoManager } from '../../../util/UndoManager';
@@ -51,7 +52,6 @@ import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCurso
 import './CollectionFreeFormView.scss';
 import { MarqueeView } from './MarqueeView';
 import React = require('react');
-import { FollowLinkScript } from '../../../util/LinkFollower';
 
 export type collectionFreeformViewProps = {
     NativeWidth?: () => number;
@@ -156,12 +156,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const cb = Cast(this.rootDoc.contentBounds, listSpec('number'));
         return cb
             ? { x: cb[0], y: cb[1], r: cb[2], b: cb[3] }
-            : this.props.contentBounds?.() ??
-                  aggregateBounds(
-                      this._layoutElements.filter(e => e.bounds?.width && !e.bounds.z).map(e => e.bounds!),
-                      NumCast(this.layoutDoc._xPadding, 10),
-                      NumCast(this.layoutDoc._yPadding, 10)
-                  );
+            : aggregateBounds(
+                  this._layoutElements.filter(e => e.bounds?.width && !e.bounds.z).map(e => e.bounds!),
+                  NumCast(this.layoutDoc._xPadding, 10),
+                  NumCast(this.layoutDoc._yPadding, 10)
+              );
     }
     @computed get nativeWidth() {
         return this.props.NativeWidth?.() || (this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)));
@@ -1183,23 +1182,24 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @action
     bringToFront = (doc: Doc, sendToBack?: boolean) => {
-        if (sendToBack) {
-            const docs = this.childLayoutPairs.map(pair => pair.layout).slice();
-            docs.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
-            let zfirst = docs.length ? NumCast(docs[0].zIndex) : 0;
-            doc.zIndex = zfirst - 1;
-        } else if (doc.stroke_isInkMask) {
+        if (doc.stroke_isInkMask) {
             doc.zIndex = 5000;
         } else {
-            const docs = this.childLayoutPairs.map(pair => pair.layout).slice();
-            docs.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
-            let zlast = docs.length ? Math.max(docs.length, NumCast(docs.lastElement().zIndex)) : 1;
-            if (docs.lastElement() !== doc) {
-                if (zlast - docs.length > 100) {
-                    for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1;
-                    zlast = docs.length + 1;
+            // prettier-ignore
+            const docs = this.childLayoutPairs.map(pair => pair.layout)
+                  .sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
+            if (sendToBack) {
+                const zfirst = docs.length ? NumCast(docs[0].zIndex) : 0;
+                doc.zIndex = zfirst - 1;
+            } else {
+                let zlast = docs.length ? Math.max(docs.length, NumCast(docs.lastElement().zIndex)) : 1;
+                if (docs.lastElement() !== doc) {
+                    if (zlast - docs.length > 100) {
+                        for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1;
+                        zlast = docs.length + 1;
+                    }
+                    doc.zIndex = zlast + 1;
                 }
-                doc.zIndex = zlast + 1;
             }
         }
     };
@@ -1291,25 +1291,28 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             return this.addDocument?.(newDoc);
         }
     };
-    @computed get _pointerEvents() {
+    @computed get childPointerEvents() {
         const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
         const pointerEvents = DocumentView.Interacting
             ? 'none'
             : this.props.childPointerEvents?.() ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) || this.isContentActive() === false ? 'none' : this.props.pointerEvents?.());
         return pointerEvents;
     }
-    pointerEvents = () => this._pointerEvents;
+    childPointerEventsFunc = () => this.childPointerEvents;
     childContentsActive = () => (this.props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)();
+    groupChildPointerEvents = () => (this.props.isDocumentActive?.() ? 'all' : 'none');
     getChildDocView(entry: PoolData) {
         const childLayout = entry.pair.layout;
         const childData = entry.pair.data;
         return (
             <CollectionFreeFormDocumentView
                 key={childLayout[Id] + (entry.replica || '')}
+                GroupPointerEvents={(childLayout._isGroup && !this.childContentsActive()) || childLayout.pointerEvents === 'none' ? returnNone : this.rootDoc._isGroup ? this.groupChildPointerEvents : undefined}
                 DataDoc={childData}
                 Document={childLayout}
                 renderDepth={this.props.renderDepth + 1}
                 replica={entry.replica}
+                hideDecorations={BoolCast(childLayout._layout_isSvg && childLayout.type === DocumentType.LINK)}
                 suppressSetHeight={this.layoutEngine ? true : false}
                 renderCutoffProvider={this.renderCutoffProvider}
                 CollectionFreeFormView={this}
@@ -1327,7 +1330,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 childFilters={this.childDocFilters}
                 childFiltersByRanges={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
-                isDocumentActive={this.props.childDocumentsActive?.() || this.rootDoc._isGroup ? this.props.isDocumentActive : this.isContentActive}
+                isDocumentActive={childLayout.pointerEvents === 'none' ? returnFalse : this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
                 isContentActive={this.childContentsActive}
                 focus={this.Document._isGroup ? this.groupFocus : this.isAnnotationOverlay ? this.props.focus : this.focus}
                 addDocTab={this.addDocTab}
@@ -1344,7 +1347,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 bringToFront={this.bringToFront}
                 layout_showTitle={this.props.childlayout_showTitle}
                 dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
-                pointerEvents={this.pointerEvents}
+                pointerEvents={this.childPointerEventsFunc}
                 //fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.treeView_FreezeChildDimensions)} // bcz: check this
             />
         );
@@ -1412,6 +1415,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             height: _height,
             transition: StrCast(childDocLayout.dataTransition),
             pair: params.pair,
+            pointerEvents: Cast(childDoc.pointerEvents, 'string', null),
             replica: '',
         };
     }
@@ -1531,7 +1535,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     newPos.z !== lastPos.z ||
                     newPos.rotation !== lastPos.rotation ||
                     newPos.zIndex !== lastPos.zIndex ||
-                    newPos.transition !== lastPos.transition
+                    newPos.transition !== lastPos.transition ||
+                    newPos.pointerEvents !== lastPos.pointerEvents
                 ) {
                     this._layoutPoolData.set(entry[0], newPos);
                     somethingChanged = true;
@@ -1625,8 +1630,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 );
 
                 this._disposers.active = reaction(
-                    () => this.isContentActive(),
-                    active => this.rootDoc[this.autoResetFieldKey] && !active && this.resetView()
+                    () => this.isContentActive(), // if autoreset is on, then whenever the view is selected, it will be restored to it default pan/zoom positions
+                    active => this.rootDoc[this.autoResetFieldKey] && active && this.resetView()
                 );
 
                 this._disposers.fitContent = reaction(
-- 
cgit v1.2.3-70-g09d2


From 3141c62397071efee60510ef90df69ff04701757 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 21 Oct 2023 14:26:33 -0400
Subject: change doc decorations rotation icon to bottom right.  cleaned up
 lock icon. fixed autoresetview of collections to not fire when dragging.

---
 src/client/views/DocumentDecorations.scss               | 17 +++++++++++------
 src/client/views/DocumentDecorations.tsx                |  8 +++-----
 .../collectionFreeForm/CollectionFreeFormView.tsx       |  2 +-
 3 files changed, 15 insertions(+), 12 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 2764339e6..f41cf1385 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -14,8 +14,9 @@ $resizeHandler: 8px;
         border-radius: 100%;
         height: 30;
         width: 30;
-        right: -30;
-        top: calc(50% - 15px);
+        right: -40;
+        bottom: -40;
+        //top: calc(50% - 15px);
         position: absolute;
         pointer-events: all;
         cursor: pointer;
@@ -364,15 +365,19 @@ $resizeHandler: 8px;
         position: relative;
         background: black;
         color: rgb(145, 144, 144);
-        height: 14;
-        width: 14;
+        height: 20;
+        width: 20;
         pointer-events: all;
         margin: auto;
         display: flex;
         align-items: center;
-        flex-direction: column;
-        border-radius: 15%;
+        border-radius: 100%;
         cursor: default;
+        svg {
+            width: 10;
+            height: 10;
+            margin: auto;
+        }
     }
 
     .documentDecorations-rotationPath {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 8b8b7fa4d..f40d2ae8b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -845,9 +845,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                     &nbsp; */}
                     </div>
                 </div>
-            ) : (
-                <div />
-            );
+            ) : null;
 
         const titleArea = this._editingTitle ? (
             <input
@@ -946,7 +944,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                                     key="rad"
                                     className="documentDecorations-borderRadius"
                                     style={{
-                                        background: `${this._isRounding ? Colors.MEDIUM_BLUE : undefined}`,
+                                        background: `${this._isRounding ? Colors.MEDIUM_BLUE : SettingsManager.userColor}`,
                                         transform: `translate(${radiusHandleLocation ?? 0}px, ${(radiusHandleLocation ?? 0) + (this._showNothing ? 0 : this._titleHeight)}px)`,
                                     }}
                                     onPointerDown={this.onRadiusDown}
@@ -980,7 +978,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                                     }}>
                                     {this._isRotating ? null : (
                                         <Tooltip enterDelay={750} title={<div className="dash-tooltip">tap to set rotate center, drag to rotate</div>}>
-                                            <div className="documentDecorations-rotation" style={{ pointerEvents: 'all', background: 'transparent' }} onPointerDown={this.onRotateDown} onContextMenu={e => e.preventDefault()}>
+                                            <div className="documentDecorations-rotation" onPointerDown={this.onRotateDown} onContextMenu={e => e.preventDefault()}>
                                                 <IconButton icon={<FaUndo />} color={SettingsManager.userColor} />
                                             </div>
                                         </Tooltip>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5eff6b8e0..6c1308c0a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1631,7 +1631,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
                 this._disposers.active = reaction(
                     () => this.isContentActive(), // if autoreset is on, then whenever the view is selected, it will be restored to it default pan/zoom positions
-                    active => this.rootDoc[this.autoResetFieldKey] && active && this.resetView()
+                    active => !SnappingManager.GetIsDragging() && this.rootDoc[this.autoResetFieldKey] && active && this.resetView()
                 );
 
                 this._disposers.fitContent = reaction(
-- 
cgit v1.2.3-70-g09d2


From a91d5f3bdf1daf9b10a3f02acc79db7a0174a1d8 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 24 Oct 2023 13:46:36 -0400
Subject: fixed hide before/after in trails.  move ink mask to developer. 
 fixed tangent dragging on some curves that have no initial tangent.  fixed
 tree view highlights when dragging.

---
 src/client/documents/Documents.ts                  |  4 ---
 src/client/util/CurrentUserUtils.ts                |  2 +-
 src/client/views/InkStrokeProperties.ts            |  5 ++--
 src/client/views/PropertiesButtons.tsx             | 31 +++++++++-------------
 src/client/views/collections/TreeView.tsx          |  4 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/trails/PresBox.tsx          |  2 +-
 7 files changed, 21 insertions(+), 29 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index d61e4b3e9..161aba6e1 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1055,10 +1055,6 @@ export namespace Docs {
             I.stroke_isInkMask = isInkMask;
             I.text_align = 'center';
             I.title = 'ink';
-            I.x = options.x as number;
-            I.y = options.y as number;
-            I._width = options._width as number;
-            I._height = options._height as number;
             I.author = Doc.CurrentUserEmail;
             I.rotation = 0;
             I.defaultDoubleClick = 'click';
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 268ee2790..dc988b04d 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -678,7 +678,7 @@ export class CurrentUserUtils {
             { 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_);}`} },
             { title: "Line",   toolTip: "Line (double tap to lock mode)",   btnType: ButtonType.ToggleButton, icon: "minus",      toolType:GestureUtils.Gestures.Line,        scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} },
-            { title: "Mask",   toolTip: "Mask",                             btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask",                        scripts: {onClick:'{ return setInkProperty(self.toolType, value, _readOnly_);}'} },
+            { title: "Mask",   toolTip: "Mask",                             btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask",                        scripts: {onClick:'{ return setInkProperty(self.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } },
             { title: "Width",  toolTip: "Stroke width",                     btnType: ButtonType.NumberSliderButton,               toolType: "strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(self.toolType, value, _readOnly_);}'}, numBtnMin: 1},
             { title: "Ink",    toolTip: "Stroke color",                     btnType: ButtonType.ColorButton,  icon: "pen",        toolType: "strokeColor", ignoreClick: true, scripts: {script: '{ return setInkProperty(self.toolType, value, _readOnly_);}'} },
         ];
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index abc4381a6..13bd12361 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -12,6 +12,7 @@ import { DocumentManager } from '../util/DocumentManager';
 import { undoBatch } from '../util/UndoManager';
 import { InkingStroke } from './InkingStroke';
 import { DocumentView } from './nodes/DocumentView';
+import _ = require('lodash');
 
 export class InkStrokeProperties {
     static _Instance: InkStrokeProperties | undefined;
@@ -262,7 +263,7 @@ export class InkStrokeProperties {
                 var endDir = { x: 0, y: 0 };
                 for (var i = 0; i < nearestSeg / 4 + 1; i++) {
                     const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
-                    if (i === 0) startDir = bez.derivative(0);
+                    if (i === 0) startDir = bez.derivative(_.isEqual(bez.derivative(0), { x: 0, y: 0, t: 0 }) ? 1e-8 : 0);
                     if (i === nearestSeg / 4) endDir = bez.derivative(nearestT);
                     for (var t = 0; t < (i === nearestSeg / 4 ? nearestT + 0.05 : 1); t += 0.05) {
                         const pt = bez.compute(i !== nearestSeg / 4 ? t : Math.min(nearestT, t));
@@ -273,7 +274,7 @@ export class InkStrokeProperties {
                 for (var i = nearestSeg / 4; i < splicedPoints.length / 4; i++) {
                     const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
                     if (i === nearestSeg / 4) startDir = bez.derivative(nearestT);
-                    if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(1);
+                    if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(_.isEqual(bez.derivative(1), { x: 0, y: 0, t: 1 }) ? 1 - 1e-8 : 1);
                     for (var t = i === nearestSeg / 4 ? nearestT : 0; t < (i === nearestSeg / 4 ? 1 + 0.05 + 1e-7 : 1 + 1e-7); t += 0.05) {
                         const pt = bez.compute(Math.min(1, t));
                         samplesRight.push(new Point(pt.x, pt.y));
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 61aa616ec..ff79a8390 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -1,38 +1,33 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Icon, Tooltip } from '@material-ui/core';
+import { Dropdown, DropdownType, IListItemProps, Toggle, ToggleType, Type } from 'browndash-components';
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import { AiOutlineColumnWidth } from 'react-icons/ai';
+import { BiHide, BiShow } from 'react-icons/bi';
+import { BsGrid3X3GapFill } from 'react-icons/bs';
+import { CiGrid31 } from 'react-icons/ci';
+import { FaBraille, FaLock, FaLockOpen } from 'react-icons/fa';
+import { MdClosedCaption, MdClosedCaptionDisabled, MdGridOff, MdGridOn, MdSubtitles, MdSubtitlesOff, MdTouchApp } from 'react-icons/md';
+import { RxWidth } from 'react-icons/rx';
+import { TbEditCircle, TbEditCircleOff, TbHandOff, TbHandStop, TbHighlight, TbHighlightOff } from 'react-icons/tb';
+import { TfiBarChart } from 'react-icons/tfi';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
-import { Id } from '../../fields/FieldSymbols';
 import { InkField } from '../../fields/InkField';
 import { RichTextField } from '../../fields/RichTextField';
-import { BoolCast, ScriptCast, StrCast } from '../../fields/Types';
+import { BoolCast, ScriptCast } from '../../fields/Types';
 import { ImageField } from '../../fields/URLField';
-import { Utils } from '../../Utils';
 import { DocUtils } from '../documents/Documents';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { IsFollowLinkScript } from '../util/LinkFollower';
 import { LinkManager } from '../util/LinkManager';
 import { SelectionManager } from '../util/SelectionManager';
+import { SettingsManager } from '../util/SettingsManager';
 import { undoable, undoBatch } from '../util/UndoManager';
 import { Colors } from './global/globalEnums';
 import { InkingStroke } from './InkingStroke';
 import { DocumentView, OpenWhere } from './nodes/DocumentView';
-import { pasteImageBitmap } from './nodes/WebBoxRenderer';
 import './PropertiesButtons.scss';
 import React = require('react');
-import { JsxElement } from 'typescript';
-import { FaBraille, FaHighlighter, FaLock, FaLockOpen, FaThumbtack } from 'react-icons/fa';
-import { AiOutlineApple, AiOutlineColumnWidth, AiOutlinePicture } from 'react-icons/ai';
-import { MdClosedCaption, MdClosedCaptionDisabled, MdGridOff, MdGridOn, MdSubtitles, MdSubtitlesOff, MdTouchApp } from 'react-icons/md';
-import { TbEditCircle, TbEditCircleOff, TbHandOff, TbHandStop, TbHighlight, TbHighlightOff } from 'react-icons/tb';
-import { BiHide, BiShow } from 'react-icons/bi';
-import { BsGrid3X3GapFill } from 'react-icons/bs';
-import { TfiBarChart } from 'react-icons/tfi';
-import { CiGrid31 } from 'react-icons/ci';
-import { RxWidth } from 'react-icons/rx';
-import { Dropdown, DropdownType, IListItemProps, Toggle, ToggleType, Type } from 'browndash-components';
-import { SettingsManager } from '../util/SettingsManager';
 
 enum UtilityButtonState {
     Default,
@@ -526,7 +521,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                 {toggle(this.fitContentButton, { display: !isFreeForm && !isMap ? 'none' : '' })}
                 {/* {toggle(this.isLightboxButton, { display: !isFreeForm && !isMap ? 'none' : '' })} */}
                 {toggle(this.layout_autoHeightButton, { display: !isText && !isStacking && !isTree ? 'none' : '' })}
-                {toggle(this.maskButton, { display: !isInk ? 'none' : '' })}
+                {toggle(this.maskButton, { display: isNovice || !isInk ? 'none' : '' })}
                 {toggle(this.hideImageButton, { display: !isImage ? 'none' : '' })}
                 {toggle(this.chromeButton, { display: isNovice ? 'none' : '' })}
                 {toggle(this.gridButton, { display: !isCollection ? 'none' : '' })}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index b57402531..193c70add 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -751,7 +751,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                         : {
                               pointerEvents: this.props.isContentActive() ? 'all' : undefined,
                               opacity: checked === 'unchecked' || typeof iconType !== 'string' ? undefined : 0.4,
-                              color: StrCast(this.doc.color, checked === 'unchecked' ? 'white' : 'inherit'),
+                              color: checked === 'unchecked' ? SettingsManager.userColor : 'inherit',
                           }
                 }>
                 {this.props.treeView.outlineMode ? (
@@ -881,7 +881,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                             // just render a title for a tree view label (identified by treeViewDoc being set in 'props')
                             maxWidth: props?.PanelWidth() || undefined,
                             background: props?.styleProvider?.(doc, props, StyleProp.BackgroundColor),
-                            outline: `solid ${highlightColor} ${highlightIndex}px`,
+                            outline: SnappingManager.GetIsDragging() ? undefined: `solid ${highlightColor} ${highlightIndex}px`,
                             paddingLeft: NumCast(treeView.rootDoc.childXPadding, NumCast(treeView.props.childXPadding, Doc.IsComicStyle(doc)?20:0)),
                             paddingRight: NumCast(treeView.rootDoc.childXPadding, NumCast(treeView.props.childXPadding, Doc.IsComicStyle(doc)?20:0)),
                             paddingTop: treeView.props.childYPadding,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 6c1308c0a..7fa22804d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -714,7 +714,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     points,
                     ActiveIsInkMask(),
                     {
-                        title: 'ink stroke',
+                        title: ge.gesture.toString(),
                         x: B.x - (ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale) / 2,
                         y: B.y - (ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale) / 2,
                         _width: B.width + ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale,
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 383b400c8..944302934 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -830,7 +830,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this.childDocs.forEach((doc, index) => {
             const curDoc = Cast(doc, Doc, null);
             const tagDoc = PresBox.targetRenderedDoc(curDoc);
-            const itemIndexes: number[] = this.getAllIndexes(this.tagDocs, tagDoc);
+            const itemIndexes: number[] = this.getAllIndexes(this.tagDocs, curDoc);
             let opacity: Opt<number> = index === this.itemIndex ? 1 : undefined;
             if (curDoc.presentation_hide) {
                 if (index !== this.itemIndex) {
-- 
cgit v1.2.3-70-g09d2


From 6d60f2835502cb58d680aeeda8ce022f73ddcfa6 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 24 Oct 2023 13:57:59 -0400
Subject: adjustment to group pointer events for ink.

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7fa22804d..5f7dda562 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1300,7 +1300,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
     childPointerEventsFunc = () => this.childPointerEvents;
     childContentsActive = () => (this.props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)();
-    groupChildPointerEvents = () => (this.props.isDocumentActive?.() ? 'all' : 'none');
+    groupChildPointerEvents = () => (this.props.isDocumentActive?.() && !this.isContentActive() ? 'all' : 'none');
     getChildDocView(entry: PoolData) {
         const childLayout = entry.pair.layout;
         const childData = entry.pair.data;
-- 
cgit v1.2.3-70-g09d2


From bbdba27c743a871c51ff99f52a3d348fdd5d2faf Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 25 Oct 2023 18:45:30 -0400
Subject: fix to group pointer events for nested groups.

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5f7dda562..9639ec73c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1307,7 +1307,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return (
             <CollectionFreeFormDocumentView
                 key={childLayout[Id] + (entry.replica || '')}
-                GroupPointerEvents={(childLayout._isGroup && !this.childContentsActive()) || childLayout.pointerEvents === 'none' ? returnNone : this.rootDoc._isGroup ? this.groupChildPointerEvents : undefined}
+                GroupPointerEvents={this.rootDoc._isGroup ? this.groupChildPointerEvents : (childLayout._isGroup && !this.childContentsActive()) || childLayout.pointerEvents === 'none' ? returnNone : undefined}
                 DataDoc={childData}
                 Document={childLayout}
                 renderDepth={this.props.renderDepth + 1}
-- 
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/collections/collectionFreeForm/CollectionFreeFormView.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 bc14d740a23d922df96f92f99eacb51f70139643 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 31 Oct 2023 17:35:45 -0400
Subject: fixed deselecting video/audio with escape. fixed focus or open on
 iconified docs to just show them (not toggle), and fixed to showDocument on
 target unless its container isn't displayed.

---
 src/client/util/DocumentManager.ts                 |  7 ++-
 .../collections/CollectionStackedTimeline.tsx      |  1 -
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 59 +++++++++-------------
 src/client/views/nodes/trails/PresBox.tsx          |  6 +++
 4 files changed, 36 insertions(+), 37 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index f7eca2379..7cc8afaa6 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -298,7 +298,10 @@ export class DocumentManager {
         let contextView: DocumentView | undefined; // view containing context that contains target
         let focused = false;
         while (true) {
-            docView.rootDoc.layout_fieldKey === 'layout_icon' ? await new Promise<void>(res => docView.iconify(res)) : undefined;
+            if (docView.rootDoc.layout_fieldKey === 'layout_icon') {
+                await new Promise<void>(res => docView.iconify(res));
+                options.didMove = true;
+            }
             const nextFocus = docView.props.focus(docView.rootDoc, options); // focus the view within its container
             focused = focused || (nextFocus === undefined ? false : true); // keep track of whether focusing on a view needed to actually change anything
             const { childDocView, viewSpec } = await iterator(docView);
@@ -343,7 +346,7 @@ export function DocFocusOrOpen(doc: Doc, options: DocFocusOptions = { willZoomCe
             DocumentManager.Instance.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.rootDoc));
         } else {
             const container = DocCast(containingDoc ?? doc.embedContainer ?? Doc.BestEmbedding(doc));
-            const showDoc = !Doc.IsSystem(container) ? container : doc;
+            const showDoc = !Doc.IsSystem(container) && !cv ? container : doc;
             options.toggleTarget = undefined;
             DocumentManager.Instance.showDocument(showDoc, options, () => DocumentManager.Instance.showDocument(doc, { ...options, openLocation: undefined })).then(() => {
                 const cv = DocumentManager.Instance.getDocumentView(containingDoc);
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index c4650647c..7c61bc4da 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -203,7 +203,6 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                     this._trimStart = this.clipStart;
                     this._trimStart = this.clipEnd;
                     this._trimming = TrimScope.None;
-                    e.stopPropagation();
                     break;
                 case 'ArrowLeft':
                     this.props.setTime(Math.min(Math.max(this.clipStart, this.currentTime - jump), this.clipEnd));
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 0d43d6ab8..10d0117ef 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -82,6 +82,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     private _lastY: number = 0;
     private _downX: number = 0;
     private _downY: number = 0;
+    private _downTime = 0;
     private _inkToTextStartX: number | undefined;
     private _inkToTextStartY: number | undefined;
     private _wordPalette: Map<string, string> = new Map<string, string>();
@@ -92,7 +93,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     private _layoutPoolData = observable.map<string, PoolData>();
     private _layoutSizeData = observable.map<string, { width?: number; height?: number }>();
     private _cachedPool: Map<string, PoolData> = new Map();
-    private _lastTap = 0;
     private _batch: UndoManager.Batch | undefined = undefined;
     private _brushtimer: any;
     private _brushtimer1: any;
@@ -130,10 +130,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @observable GroupChildDrag: boolean = false; // child document view being dragged.  needed to update drop areas of groups when a group item is dragged.
     @observable _brushedView = { width: 0, height: 0, panX: 0, panY: 0, opacity: 0 }; // highlighted region of freeform canvas used by presentations to indicate a region
 
-    constructor(props: any) {
-        super(props);
-    }
-
     @computed get views() {
         const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask !== -1 && ele.inkMask !== undefined).map(ele => ele.ele);
         const renderableEles = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && (ele.inkMask === -1 || ele.inkMask === undefined)).map(ele => ele.ele);
@@ -640,6 +636,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     onPointerDown = (e: React.PointerEvent): void => {
         this._downX = this._lastX = e.pageX;
         this._downY = this._lastY = e.pageY;
+        this._downTime = Date.now();
         if (e.button === 0 && !e.altKey && !e.ctrlKey && this.props.isContentActive(true)) {
             if (
                 !this.props.Document._isGroup && // group freeforms don't pan when dragged -- instead let the event go through to allow the group itself to drag
@@ -809,31 +806,24 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @action
     onClick = (e: React.MouseEvent) => {
         if (this._lightboxDoc) this._lightboxDoc = undefined;
-        if (this.onBrowseClickHandler()) {
-            if (this.props.DocumentView?.()) {
-                this.onBrowseClickHandler().script.run({ documentView: this.props.DocumentView(), clientX: e.clientX, clientY: e.clientY });
-            }
-            e.stopPropagation();
-            e.preventDefault();
-        } else if (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3) {
-            if (e.shiftKey && (this.props.renderDepth === 0 || this.isContentActive())) {
-                if (Date.now() - this._lastTap < 300) {
-                    // reset zoom of freeform view to 1-to-1 on a shift + double click
-                    this.zoomSmoothlyAboutPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1);
-                }
+        if (Utils.isClick(e.pageX, e.pageY, this._downX, this._downY, this._downTime)) {
+            if (this.onBrowseClickHandler()) {
+                this.onBrowseClickHandler().script.run({ documentView: this.props.DocumentView?.(), clientX: e.clientX, clientY: e.clientY });
+                e.stopPropagation();
+                e.preventDefault();
+            } else if (this.isContentActive() && e.shiftKey) {
+                // reset zoom of freeform view to 1-to-1 on a shift + double click
+                this.zoomSmoothlyAboutPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1);
                 e.stopPropagation();
                 e.preventDefault();
             }
-            this._lastTap = Date.now();
         }
     };
 
     @action
     scrollPan = (e: WheelEvent | { deltaX: number; deltaY: number }): void => {
         PresBox.Instance?.pauseAutoPres();
-        const dx = e.deltaX;
-        const dy = e.deltaY;
-        this.setPan(NumCast(this.Document[this.panXFieldKey]) - dx, NumCast(this.Document[this.panYFieldKey]) - dy, 0, true);
+        this.setPan(NumCast(this.Document[this.panXFieldKey]) - e.deltaX, NumCast(this.Document[this.panYFieldKey]) - e.deltaY, 0, true);
     };
 
     @action
@@ -2301,20 +2291,21 @@ class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFor
 export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY: number) {
     const browseTransitionTime = 500;
     SelectionManager.DeselectAll();
-    DocumentManager.Instance.showDocument(dv.rootDoc, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => {
-        if (!focused) {
-            const selfFfview = !dv.rootDoc._isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
-            let containers = dv.props.docViewPath();
-            let parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
-            for (var cont of containers) {
-                parFfview = parFfview ?? cont.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+    dv &&
+        DocumentManager.Instance.showDocument(dv.rootDoc, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => {
+            if (!focused) {
+                const selfFfview = !dv.rootDoc._isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
+                let containers = dv.props.docViewPath();
+                let parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+                for (var cont of containers) {
+                    parFfview = parFfview ?? cont.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+                }
+                while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+                const ffview = selfFfview && selfFfview.rootDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it.  Otherwise, zoom out on the parent ffview
+                ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime);
+                Doc.linkFollowHighlight(dv?.props.Document, false);
             }
-            while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
-            const ffview = selfFfview && selfFfview.rootDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it.  Otherwise, zoom out on the parent ffview
-            ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime);
-            Doc.linkFollowHighlight(dv?.props.Document, false);
-        }
-    });
+        });
 }
 ScriptingGlobals.add(CollectionBrowseClick);
 ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) {
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 70d9443a2..6bbf3208c 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -518,6 +518,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 changed = true;
             }
         }
+        if (pinDataTypes?.temporal || (!pinDataTypes && activeItem.timecodeToShow !== undefined)) {
+            if (bestTarget._layout_currentTimecode !== activeItem.timecodeToShow) {
+                bestTarget._layout_currentTimecode = activeItem.timecodeToShow;
+                changed = true;
+            }
+        }
         if (pinDataTypes?.inkable || (!pinDataTypes && (activeItem.config_fillColor !== undefined || activeItem.color !== undefined))) {
             if (bestTarget.fillColor !== activeItem.config_fillColor) {
                 Doc.GetProto(bestTarget).fillColor = activeItem.config_fillColor;
-- 
cgit v1.2.3-70-g09d2


From 66c5b6625ecd9690dbbff8af248626f21a21c1bd Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 1 Nov 2023 00:45:54 -0400
Subject: cleaned up presentaton path lines

---
 .../collectionFreeForm/CollectionFreeFormView.scss |   4 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 115 +++++------------
 src/client/views/nodes/trails/PresBox.tsx          | 136 +++++++++------------
 3 files changed, 95 insertions(+), 160 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index c90fdf013..250760bd5 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -54,10 +54,14 @@
         }
     }
 
+    .presPathLabels {
+        pointer-events: none;
+    }
     svg.presPaths {
         position: absolute;
         z-index: 100000;
         overflow: visible;
+        pointer-events: none;
     }
 
     svg.presPaths-hidden {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 10d0117ef..079f1b9d6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -128,7 +128,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @observable _marqueeRef: HTMLDivElement | null = null;
     @observable _marqueeViewRef = React.createRef<MarqueeView>();
     @observable GroupChildDrag: boolean = false; // child document view being dragged.  needed to update drop areas of groups when a group item is dragged.
-    @observable _brushedView = { width: 0, height: 0, panX: 0, panY: 0, opacity: 0 }; // highlighted region of freeform canvas used by presentations to indicate a region
+    @observable _brushedView: { width: number; height: number; panX: number; panY: number } | undefined; // highlighted region of freeform canvas used by presentations to indicate a region
 
     @computed get views() {
         const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask !== -1 && ele.inkMask !== undefined).map(ele => ele.ele);
@@ -729,9 +729,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 if (this._inkToTextStartX && this._inkToTextStartY) {
                     const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y)));
                     const setDocs = this.getActiveDocuments().filter(s => DocCast(s.proto)?.type === DocumentType.RTF && s.color);
-                    const sets = setDocs.map(sd => {
-                        return Cast(sd.text, RichTextField)?.Text as string;
-                    });
+                    const sets = setDocs.map(sd => Cast(sd.text, RichTextField)?.Text as string);
                     if (sets.length && sets[0]) {
                         this._wordPalette.clear();
                         const colors = setDocs.map(sd => FieldValue(sd.color) as string);
@@ -1919,12 +1917,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     showPresPaths = () => (CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.getPaths(this.rootDoc) : null);
-
     brushedView = () => this._brushedView;
-    gridColor = () => {
-        const backColor = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box');
-        return lightOrDark(backColor);
-    };
+    gridColor = () => lightOrDark(this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box'));
     @computed get marqueeView() {
         TraceMobx();
         return (
@@ -1952,7 +1946,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     style={{ opacity: this.props.dontRenderDocuments ? 0.7 : undefined }}>
                     {this.layoutDoc._freeform_backgroundGrid ? (
                         <div>
-                            <CollectionFreeFormBackgroundGrid // bcz : UGHH  don't know why, but if we don't wrap in a div, then PDF's don't render whenn taking snapshot of a dashboard and the background grid is on!!?
+                            <CollectionFreeFormBackgroundGrid // bcz : UGHH  don't know why, but if we don't wrap in a div, then PDF's don't render when taking snapshot of a dashboard and the background grid is on!!?
                                 PanelWidth={this.props.PanelWidth}
                                 PanelHeight={this.props.PanelHeight}
                                 panX={this.panX}
@@ -1968,13 +1962,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         </div>
                     ) : null}
                     <CollectionFreeFormViewPannableContents
+                        rootDoc={this.rootDoc}
                         brushedView={this.brushedView}
                         isAnnotationOverlay={this.isAnnotationOverlay}
-                        isAnnotationOverlayScrollable={this.props.isAnnotationOverlayScrollable}
                         transform={this.contentTransform}
-                        zoomScaling={this.zoomScaling}
                         presPaths={this.showPresPaths}
-                        presPinView={BoolCast(this.Document.config_pinView)}
                         transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.props.DocumentView?.()?.rootDoc._viewTransition, 'string', null))}
                         viewDefDivClick={this.props.viewDefDivClick}>
                         {this.children}
@@ -1999,12 +1991,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     brushView = (viewport: { width: number; height: number; panX: number; panY: number }, transTime: number) => {
         this._brushtimer1 && clearTimeout(this._brushtimer1);
         this._brushtimer && clearTimeout(this._brushtimer);
-        this._brushedView = { width: 0, height: 0, panX: 0, panY: 0, opacity: 0 };
+        this._brushedView = undefined;
         this._brushtimer1 = setTimeout(
             action(() => {
-                this._brushedView = { ...viewport, panX: viewport.panX - viewport.width / 2, panY: viewport.panY - viewport.height / 2, opacity: 1 };
+                this._brushedView = { ...viewport, panX: viewport.panX - viewport.width / 2, panY: viewport.panY - viewport.height / 2 };
                 this._brushtimer = setTimeout(
-                    action(() => (this._brushedView.opacity = 0)),
+                    action(() => (this._brushedView = undefined)),
                     2500
                 );
             }),
@@ -2076,7 +2068,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         {this._firstRender ? this.placeholder : this.marqueeView}
                         {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
 
-                        {/* // uncomment to show snap lines */}
                         <div className="snapLines" style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
                             <svg style={{ width: '100%', height: '100%' }}>
                                 {(this._hLines ?? [])
@@ -2108,69 +2099,22 @@ class CollectionFreeFormOverlayView extends React.Component<CollectionFreeFormOv
 }
 
 interface CollectionFreeFormViewPannableContentsProps {
-    transform: () => string;
-    zoomScaling: () => number;
+    rootDoc: Doc;
     viewDefDivClick?: ScriptField;
     children?: React.ReactNode | undefined;
-    //children: () => JSX.Element[];
     transition?: string;
-    presPaths: () => JSX.Element | null;
-    presPinView?: boolean;
     isAnnotationOverlay: boolean | undefined;
-    isAnnotationOverlayScrollable: boolean | undefined;
-    brushedView: () => { panX: number; panY: number; width: number; height: number; opacity: number };
+    presPaths: () => JSX.Element | null;
+    transform: () => string;
+    brushedView: () => { panX: number; panY: number; width: number; height: number } | undefined;
 }
 
 @observer
 class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps> {
-    @observable _drag: string = '';
-
-    //Adds event listener so knows pointer is down and moving
-    onPointerDown = (e: React.PointerEvent): void => {
-        e.stopPropagation();
-        e.preventDefault();
-        this._drag = (e.target as any)?.id ?? '';
-        document.getElementById(this._drag) && setupMoveUpEvents(e.target, e, this.onPointerMove, emptyFunction, emptyFunction);
-    };
-
-    //Adjusts the value in NodeStore
-    @action
-    onPointerMove = (e: PointerEvent) => {
-        const doc = document.getElementById('resizable');
-        const toNumber = (original: number, delta: number) => original + delta * this.props.zoomScaling();
-        if (doc) {
-            switch (this._drag) {
-                case 'resizer-br':
-                    doc.style.width = toNumber(doc.offsetWidth, e.movementX) + 'px';
-                    doc.style.height = toNumber(doc.offsetHeight, e.movementY) + 'px';
-                    break;
-                case 'resizer-bl':
-                    doc.style.width = toNumber(doc.offsetWidth, -e.movementX) + 'px';
-                    doc.style.height = toNumber(doc.offsetHeight, e.movementY) + 'px';
-                    doc.style.left = toNumber(doc.offsetLeft, e.movementX) + 'px';
-                    break;
-                case 'resizer-tr':
-                    doc.style.width = toNumber(doc.offsetWidth, -e.movementX) + 'px';
-                    doc.style.height = toNumber(doc.offsetHeight, -e.movementY) + 'px';
-                    doc.style.top = toNumber(doc.offsetTop, e.movementY) + 'px';
-                case 'resizer-tl':
-                    doc.style.width = toNumber(doc.offsetWidth, -e.movementX) + 'px';
-                    doc.style.height = toNumber(doc.offsetHeight, -e.movementY) + 'px';
-                    doc.style.top = toNumber(doc.offsetTop, e.movementY) + 'px';
-                    doc.style.left = toNumber(doc.offsetLeft, e.movementX) + 'px';
-                case 'resizable':
-                    doc.style.top = toNumber(doc.offsetTop, e.movementY) + 'px';
-                    doc.style.left = toNumber(doc.offsetLeft, e.movementX) + 'px';
-            }
-            return false;
-        }
-        return true;
-    };
-
     @computed get presPaths() {
         return !this.props.presPaths() ? null : (
             <>
-                <div key="presorder">{PresBox.Instance?.order}</div>
+                <div className="presPathLabels">{PresBox.Instance?.orderedPathLabels(this.props.rootDoc)}</div>
                 <svg key="svg" className="presPaths">
                     <defs>
                         <marker id="markerSquare" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" orient="auto" overflow="visible">
@@ -2188,39 +2132,40 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
             </>
         );
     }
+    // rectangle highlight used when following trail/link to a region of a collection that isn't a document
+    @computed get brushedView() {
+        const brushedView = this.props.brushedView();
+        return !brushedView ? null : (
+            <div
+                className="collectionFreeFormView-brushView"
+                style={{
+                    transform: `translate(${brushedView.panX}px, ${brushedView.panY}px)`,
+                    width: brushedView.width,
+                    height: brushedView.height,
+                    border: `orange solid ${brushedView.width * 0.005}px`,
+                }}
+            />
+        );
+    }
 
     render() {
-        const brushedView = this.props.brushedView();
         return (
             <div
                 className={'collectionfreeformview' + (this.props.viewDefDivClick ? '-viewDef' : '-none')}
                 onScroll={e => {
                     const target = e.target as any;
                     if (getComputedStyle(target)?.overflow === 'visible') {
-                        // if collection is visible, then scrolling will mess things up since there are no scroll bars
-                        target.scrollTop = target.scrollLeft = 0;
+                        target.scrollTop = target.scrollLeft = 0; // if collection is visible, scrolling messes things up since there are no scroll bars
                     }
                 }}
                 style={{
                     transform: this.props.transform(),
                     transition: this.props.transition,
                     width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable.  This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection
-                    //willChange: "transform"
                 }}>
                 {this.props.children}
-                {
-                    <div
-                        className="collectionFreeFormView-brushView"
-                        style={{
-                            opacity: brushedView.opacity,
-                            transform: `translate(${brushedView.panX}px, ${brushedView.panY}px)`,
-                            width: brushedView.width,
-                            height: brushedView.height,
-                            border: `orange solid ${brushedView.width * 0.005}px`,
-                        }}
-                    />
-                }
                 {this.presPaths}
+                {this.brushedView}
             </div>
         );
     }
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 6bbf3208c..6493117b0 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
 import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { Doc, DocListCast, FieldResult, Opt, StrListCast } from '../../../../fields/Doc';
+import { Doc, DocListCast, FieldResult, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
 import { Animation } from '../../../../fields/DocSymbols';
 import { Copy, Id } from '../../../../fields/FieldSymbols';
 import { InkField } from '../../../../fields/InkField';
@@ -1307,71 +1307,55 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     getAllIndexes = (arr: Doc[], val: Doc) => arr.map((doc, i) => (doc === val ? i : -1)).filter(i => i !== -1);
 
     // Adds the index in the pres path graphically
-    @computed get order() {
+    orderedPathLabels = (collection: Doc) => {
         const order: JSX.Element[] = [];
-        const docs: Doc[] = [];
-        const presCollection = DocumentManager.GetContextPath(this.activeItem).reverse().lastElement();
+        const docs = new Set<Doc>();
+        const presCollection = collection;
         const dv = DocumentManager.Instance.getDocumentView(presCollection);
-        this.childDocs
-            .filter(doc => Cast(doc.presentation_targetDoc, Doc, null))
-            .forEach((doc, index) => {
-                const tagDoc = Cast(doc.presentation_targetDoc, Doc, null);
-                const srcContext = Cast(tagDoc.embedContainer, Doc, null);
+        this.childDocs.forEach((doc, index) => {
+            const tagDoc = PresBox.targetRenderedDoc(doc);
+            const srcContext = Cast(tagDoc.embedContainer, Doc, null);
+            const labelCreator = (top: number, left: number, edge: number, fontSize: number) => (
+                <div className="pathOrder" key={tagDoc.id + 'pres' + index} style={{ top, left, width: edge, height: edge, fontSize }} onClick={() => this.selectElement(doc)}>
+                    <div className="pathOrder-frame">{index + 1}</div>
+                </div>
+            );
+            if (presCollection === srcContext) {
+                const gap = 2;
                 const width = NumCast(tagDoc._width) / 10;
                 const height = Math.max(NumCast(tagDoc._height) / 10, 15);
                 const edge = Math.max(width, height);
                 const fontSize = edge * 0.8;
-                const gap = 2;
-                if (presCollection === srcContext) {
-                    // Case A: Document is contained within the collection
-                    if (docs.includes(tagDoc)) {
-                        const prevOccurances: number = this.getAllIndexes(docs, tagDoc).length;
-                        docs.push(tagDoc);
-                        order.push(
-                            <div
-                                className="pathOrder"
-                                key={tagDoc.id + 'pres' + index}
-                                style={{ top: NumCast(tagDoc.y) + (prevOccurances * (edge + gap) - edge / 2), left: NumCast(tagDoc.x) - edge / 2, width: edge, height: edge, fontSize: fontSize }}
-                                onClick={() => this.selectElement(doc)}>
-                                <div className="pathOrder-frame">{index + 1}</div>
-                            </div>
-                        );
-                    } else {
-                        docs.push(tagDoc);
-                        order.push(
-                            <div
-                                className="pathOrder"
-                                key={tagDoc.id + 'pres' + index}
-                                style={{ top: NumCast(tagDoc.y) - edge / 2, left: NumCast(tagDoc.x) - edge / 2, width: edge, height: edge, fontSize: fontSize }}
-                                onClick={() => this.selectElement(doc)}>
-                                <div className="pathOrder-frame">{index + 1}</div>
-                            </div>
-                        );
-                    }
-                } else if (doc.config_pinView && presCollection === tagDoc && dv) {
-                    // Case B: Document is presPinView and is presCollection
-                    const scale: number = 1 / NumCast(doc.config_viewScale);
-                    const height: number = dv.props.PanelHeight() * scale;
-                    const width: number = dv.props.PanelWidth() * scale;
-                    const indWidth = width / 10;
-                    const indHeight = Math.max(height / 10, 15);
-                    const indEdge = Math.max(indWidth, indHeight);
-                    const indFontSize = indEdge * 0.8;
-                    const xLoc: number = NumCast(doc.config_panX) - width / 2;
-                    const yLoc: number = NumCast(doc.config_panY) - height / 2;
-                    docs.push(tagDoc);
-                    order.push(
-                        <>
-                            <div className="pathOrder" key={tagDoc.id + 'pres' + index} style={{ top: yLoc - indEdge / 2, left: xLoc - indEdge / 2, width: indEdge, height: indEdge, fontSize: indFontSize }} onClick={() => this.selectElement(doc)}>
-                                <div className="pathOrder-frame">{index + 1}</div>
-                            </div>
-                            <div className="pathOrder-presPinView" style={{ top: yLoc, left: xLoc, width: width, height: height, borderWidth: indEdge / 10 }}></div>
-                        </>
-                    );
+                // Case A: Document is contained within the collection
+                if (docs.has(tagDoc)) {
+                    const prevOccurences = this.getAllIndexes(Array.from(docs), tagDoc).length;
+                    order.push(labelCreator(NumCast(tagDoc.y) + (prevOccurences * (edge + gap) - edge / 2), NumCast(tagDoc.x) - edge / 2, edge, fontSize));
+                } else {
+                    order.push(labelCreator(NumCast(tagDoc.y) - edge / 2, NumCast(tagDoc.x) - edge / 2, edge, fontSize));
                 }
-            });
+            } else if (doc.config_pinView && presCollection === tagDoc && dv) {
+                // Case B: Document is presPinView and is presCollection
+                const scale = 1 / NumCast(doc.config_viewScale);
+                const viewBounds = NumListCast(doc.config_viewBounds, [0, 0, dv.props.PanelWidth(), dv.props.PanelHeight()]);
+                const height = (viewBounds[3] - viewBounds[1]) * scale;
+                const width = (viewBounds[2] - viewBounds[0]) * scale;
+                const indWidth = width / 10;
+                const indHeight = Math.max(height / 10, 15);
+                const indEdge = Math.max(indWidth, indHeight);
+                const indFontSize = indEdge * 0.8;
+                const left = NumCast(doc.config_panX) - width / 2;
+                const top = NumCast(doc.config_panY) - height / 2;
+                order.push(
+                    <>
+                        {labelCreator(top - indEdge / 2, left - indEdge / 2, indEdge, indFontSize)}
+                        <div className="pathOrder-presPinView" style={{ top, left, width, height, borderWidth: indEdge / 10 }}></div>
+                    </>
+                );
+            }
+            docs.add(tagDoc);
+        });
         return order;
-    }
+    };
 
     /**
      * Method called for viewing paths which adds a single line with
@@ -1381,22 +1365,24 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
      * (Design needed for when documents in presentation trail are in another
      * collection)
      */
-    @computed get paths() {
+    pathLines = (collection: Doc) => {
         let pathPoints = '';
-        this.childDocs.forEach((doc, index) => {
-            const tagDoc = PresBox.targetRenderedDoc(doc);
-            if (tagDoc) {
-                const n1x = NumCast(tagDoc.x) + NumCast(tagDoc._width) / 2;
-                const n1y = NumCast(tagDoc.y) + NumCast(tagDoc._height) / 2;
-                if ((index = 0)) pathPoints = n1x + ',' + n1y;
-                else pathPoints = pathPoints + ' ' + n1x + ',' + n1y;
-            } else if (doc.config_pinView) {
-                const n1x = NumCast(doc.config_panX);
-                const n1y = NumCast(doc.config_panY);
-                if ((index = 0)) pathPoints = n1x + ',' + n1y;
-                else pathPoints = pathPoints + ' ' + n1x + ',' + n1y;
-            }
-        });
+        this.childDocs
+            .filter(doc => PresBox.targetRenderedDoc(doc)?.embedContainer === collection)
+            .forEach((doc, index) => {
+                const tagDoc = PresBox.targetRenderedDoc(doc);
+                if (tagDoc) {
+                    const n1x = NumCast(tagDoc.x) + NumCast(tagDoc._width) / 2;
+                    const n1y = NumCast(tagDoc.y) + NumCast(tagDoc._height) / 2;
+                    if ((index = 0)) pathPoints = n1x + ',' + n1y;
+                    else pathPoints = pathPoints + ' ' + n1x + ',' + n1y;
+                } else if (doc.config_pinView) {
+                    const n1x = NumCast(doc.config_panX);
+                    const n1y = NumCast(doc.config_panY);
+                    if ((index = 0)) pathPoints = n1x + ',' + n1y;
+                    else pathPoints = pathPoints + ' ' + n1x + ',' + n1y;
+                }
+            });
         return (
             <polyline
                 points={pathPoints}
@@ -1413,8 +1399,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 markerEnd="url(#markerSquareFilled)"
             />
         );
-    }
-    getPaths = (collection: Doc) => this.paths; // needs to be smarter and figure out the paths to draw for this specific collection.  or better yet, draw everything in an overlay layer instad of within a collection
+    };
+    getPaths = (collection: Doc) => this.pathLines(collection); // needs to be smarter and figure out the paths to draw for this specific collection.  or better yet, draw everything in an overlay layer instad of within a collection
 
     // Converts seconds to ms and updates presentation_transition
     public static SetTransitionTime = (number: String, setter: (timeInMS: number) => void, change?: number) => {
-- 
cgit v1.2.3-70-g09d2


From e00122142a0e5960bd5c0ff2dfda5ea807eb5e2f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 1 Nov 2023 13:07:39 -0400
Subject: changed collection background grid to dots like figma until zoomed
 in, then lines.

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 35 ++++++++++++++--------
 1 file changed, 23 insertions(+), 12 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 079f1b9d6..1e341f60d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -16,7 +16,7 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } fro
 import { ImageField } from '../../../../fields/URLField';
 import { TraceMobx } from '../../../../fields/util';
 import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
-import { aggregateBounds, emptyFunction, intersectRect, lightOrDark, returnFalse, returnNone, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, returnFalse, returnNone, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
@@ -1918,7 +1918,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     showPresPaths = () => (CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.getPaths(this.rootDoc) : null);
     brushedView = () => this._brushedView;
-    gridColor = () => lightOrDark(this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box'));
+    gridColor = () =>
+        DashColor(lightOrDark(this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor)))
+            .fade(0.6)
+            .toString();
     @computed get marqueeView() {
         TraceMobx();
         return (
@@ -2187,9 +2190,9 @@ interface CollectionFreeFormViewBackgroundGridProps {
 @observer
 class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFormViewBackgroundGridProps> {
     chooseGridSpace = (gridSpace: number): number => {
-        if (!this.props.zoomScaling()) return 50;
-        const divisions = this.props.PanelWidth() / this.props.zoomScaling() / gridSpace + 3;
-        return divisions < 60 ? gridSpace : this.chooseGridSpace(gridSpace * 10);
+        if (!this.props.zoomScaling()) return gridSpace;
+        const divisions = this.props.PanelWidth() / this.props.zoomScaling() / gridSpace;
+        return divisions < 90 ? gridSpace : this.chooseGridSpace(gridSpace * 2);
     };
     render() {
         const gridSpace = this.chooseGridSpace(NumCast(this.props.layoutDoc['_backgroundGrid-spacing'], 50));
@@ -2215,14 +2218,22 @@ class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFor
                         ctx.clearRect(0, 0, w, h);
                         if (ctx) {
                             ctx.strokeStyle = strokeStyle;
+                            ctx.fillStyle = strokeStyle;
                             ctx.beginPath();
-                            for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) {
-                                ctx.moveTo(x, Cy - h);
-                                ctx.lineTo(x, Cy + h);
-                            }
-                            for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) {
-                                ctx.moveTo(Cx - w, y);
-                                ctx.lineTo(Cx + w, y);
+                            if (this.props.zoomScaling() > 1) {
+                                for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) {
+                                    ctx.moveTo(x, Cy - h);
+                                    ctx.lineTo(x, Cy + h);
+                                }
+                                for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) {
+                                    ctx.moveTo(Cx - w, y);
+                                    ctx.lineTo(Cx + w, y);
+                                }
+                            } else {
+                                for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace)
+                                    for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) {
+                                        ctx.fillRect(Math.round(x), Math.round(y), 1, 1);
+                                    }
                             }
                             ctx.stroke();
                         }
-- 
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/collections/collectionFreeForm/CollectionFreeFormView.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


From 84c15417f2247fc650a9f7b2c959479519bd3ebb Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 1 Nov 2023 23:54:49 -0400
Subject: fixes to snapping lines when dragging/resizing (lines are created for
 doc not being dragged, snapping lines are created for documents in groups). 
 cleanup of pres path code.

---
 src/Utils.ts                                       |  2 +-
 src/client/util/DragManager.ts                     | 22 +----
 src/client/util/SnappingManager.ts                 | 23 +++--
 src/client/views/DocumentDecorations.tsx           |  9 +-
 src/client/views/MainView.tsx                      |  3 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 98 +++++++++-------------
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  3 +
 src/client/views/nodes/trails/PresBox.tsx          | 46 ++++++----
 8 files changed, 92 insertions(+), 114 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 34419f665..330ca59f9 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -758,7 +758,7 @@ export function DashColor(color: string) {
 }
 
 export function lightOrDark(color: any) {
-    if (color === 'transparent') return Colors.DARK_GRAY;
+    if (color === 'transparent' || !color) return Colors.DARK_GRAY;
     if (color.startsWith?.('linear')) return Colors.BLACK;
     const nonAlphaColor = color.startsWith('#') ? (color as string).substring(0, 7) : color.startsWith('rgba') ? color.replace(/,.[^,]*\)/, ')').replace('rgba', 'rgb') : color;
     const col = DashColor(nonAlphaColor).rgb();
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 4f30e92ce..ea13eaa5b 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -191,13 +191,6 @@ export namespace DragManager {
 
     // drag a document and drop it (or make an embed/copy on drop)
     export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions, onDropCompleted?: (e?: DragCompleteEvent) => any) {
-        dragData.draggedViews.forEach(
-            action(view => {
-                const ffview = view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
-                ffview && (ffview.GroupChildDrag = BoolCast(ffview.Document._isGroup));
-                ffview?.setupDragLines(false);
-            })
-        );
         const addAudioTag = (dropDoc: any) => {
             dropDoc && !dropDoc.author_date && (dropDoc.author_date = new DateField());
             dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(() => dropDoc);
@@ -205,14 +198,7 @@ export namespace DragManager {
         };
         const finishDrag = async (e: DragCompleteEvent) => {
             const docDragData = e.docDragData;
-            setTimeout(() =>
-                dragData.draggedViews.forEach(
-                    action(view => {
-                        const ffview = view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
-                        ffview && (ffview.GroupChildDrag = false);
-                    })
-                )
-            );
+            setTimeout(() => dragData.draggedViews.forEach(view => view.props.CollectionFreeFormDocumentView?.().dragEnding()));
             onDropCompleted?.(e); // glr: optional additional function to be called - in this case with presentation trails
             if (docDragData && !docDragData.droppedDocuments.length) {
                 docDragData.dropAction = dragData.userDropAction || dragData.dropAction;
@@ -248,6 +234,7 @@ export namespace DragManager {
         };
         dragData.draggedDocuments.map(d => d.dragFactory); // does this help?  trying to make sure the dragFactory Doc is loaded
         StartDrag(eles, dragData, downX, downY, options, finishDrag);
+        dragData.draggedViews.forEach(view => view.props.CollectionFreeFormDocumentView?.().dragStarting());
         return true;
     }
 
@@ -281,9 +268,6 @@ export namespace DragManager {
         StartDrag(ele, dragData, downX, downY, options, undefined, 'Drag Column');
     }
 
-    export function SetSnapLines(horizLines: number[], vertLines: number[]) {
-        SnappingManager.setSnapLines(horizLines, vertLines);
-    }
     export function snapDragAspect(dragPt: number[], snapAspect: number) {
         let closest = Utils.SNAP_THRESHOLD;
         let near = dragPt;
@@ -491,11 +475,11 @@ export namespace DragManager {
         };
 
         const cleanupDrag = action((undo: boolean) => {
+            (dragData as DocumentDragData).draggedViews?.forEach(view => view.props.CollectionFreeFormDocumentView?.().dragEnding());
             hideDragShowOriginalElements(false);
             document.removeEventListener('pointermove', moveHandler, true);
             document.removeEventListener('pointerup', upHandler, true);
             SnappingManager.SetIsDragging(false);
-            SnappingManager.clearSnapLines();
             if (batch.end() && undo) UndoManager.Undo();
             docsBeingDragged.length = 0;
         });
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index ed9819fc0..3cb41ab4d 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -1,19 +1,19 @@
 import { observable, action, runInAction } from 'mobx';
-import { computedFn } from 'mobx-utils';
 import { Doc } from '../../fields/Doc';
 
 export namespace SnappingManager {
     class Manager {
         @observable IsDragging: boolean = false;
+        @observable IsResizing: Doc | undefined;
         @observable public horizSnapLines: number[] = [];
         @observable public vertSnapLines: number[] = [];
         @action public clearSnapLines() {
             this.vertSnapLines = [];
             this.horizSnapLines = [];
         }
-        @action public setSnapLines(horizLines: number[], vertLines: number[]) {
-            this.horizSnapLines = horizLines;
-            this.vertSnapLines = vertLines;
+        @action public addSnapLines(horizLines: number[], vertLines: number[]) {
+            this.horizSnapLines.push(...horizLines);
+            this.vertSnapLines.push(...vertLines);
         }
     }
 
@@ -22,8 +22,8 @@ export namespace SnappingManager {
     export function clearSnapLines() {
         manager.clearSnapLines();
     }
-    export function setSnapLines(horizLines: number[], vertLines: number[]) {
-        manager.setSnapLines(horizLines, vertLines);
+    export function addSnapLines(horizLines: number[], vertLines: number[]) {
+        manager.addSnapLines(horizLines, vertLines);
     }
     export function horizSnapLines() {
         return manager.horizSnapLines;
@@ -35,14 +35,13 @@ export namespace SnappingManager {
     export function SetIsDragging(dragging: boolean) {
         runInAction(() => (manager.IsDragging = dragging));
     }
+    export function SetIsResizing(doc: Doc | undefined) {
+        runInAction(() => (manager.IsResizing = doc));
+    }
     export function GetIsDragging() {
         return manager.IsDragging;
     }
-
-    export function SetShowSnapLines(show: boolean) {
-        runInAction(() => (Doc.UserDoc().freeform_snapLines = show));
-    }
-    export function GetShowSnapLines() {
-        return Doc.UserDoc().freeform_snapLines;
+    export function GetIsResizing() {
+        return manager.IsResizing;
     }
 }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 65f7c7a70..5a145e94a 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -63,7 +63,6 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     @observable public pullColor: string = 'white';
     @observable private _isRotating: boolean = false;
     @observable private _isRounding: boolean = false;
-    @observable private _isResizing: boolean = false;
     @observable private showLayoutAcl: boolean = false;
 
     constructor(props: any) {
@@ -475,7 +474,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        this._isResizing = true;
+        SnappingManager.SetIsResizing(SelectionManager.Docs().lastElement());
         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;
@@ -491,7 +490,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             ffview && ffviewSet.add(ffview);
             this._dragHeights.set(docView.layoutDoc, { start: NumCast(docView.rootDoc._height), lowest: NumCast(docView.rootDoc._height) });
         });
-        Array.from(ffviewSet).map(ffview => ffview.setupDragLines(false));
+        Array.from(ffviewSet).map(ffview => ffview.dragStarting(false, false));
     };
 
     onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => {
@@ -686,7 +685,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
 
     @action
     onPointerUp = (e: PointerEvent): void => {
-        this._isResizing = false;
+        SnappingManager.SetIsResizing(undefined);
         this._resizeHdlId = '';
         DocumentView.Interacting = false;
         this._resizeUndo?.end();
@@ -774,7 +773,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 = this._isResizing || seldocview.props.hideDecorations || seldocview.rootDoc.layout_hideDecorations;
+        const hideDecorations = SnappingManager.GetIsResizing() || 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;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 0a3389fc2..da5e4f966 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -918,7 +918,8 @@ export class MainView extends React.Component {
     }
     @computed get snapLines() {
         SnappingManager.GetIsDragging();
-        const dragged = DragManager.docsBeingDragged.lastElement();
+        SnappingManager.GetIsResizing();
+        const dragged = DragManager.docsBeingDragged.lastElement() ?? SelectionManager.Docs().lastElement();
         const dragPar = dragged ? DocumentManager.Instance.getDocumentView(dragged)?.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView : undefined;
         return !dragPar?.rootDoc.freeform_snapLines ? null : (
             <div className="mainView-snapLines">
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a8b743896..0c3033579 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -16,7 +16,7 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } fro
 import { ImageField } from '../../../../fields/URLField';
 import { TraceMobx } from '../../../../fields/util';
 import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
-import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, returnFalse, returnNone, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
@@ -118,8 +118,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
     @observable _panZoomTransition: number = 0; // sets the pan/zoom transform ease time- used by nudge(), focus() etc to smoothly zoom/pan.  set to 0 to use document's transition time or default of 0
-    @observable _hLines: number[] | undefined;
-    @observable _vLines: number[] | undefined;
     @observable _firstRender = false; // this turns off rendering of the collection's content so that there's instant feedback when a tab is switched of what content will be shown.  could be used for performance improvement
     @observable _showAnimTimeline = false;
     @observable _clusterSets: Doc[][] = [];
@@ -463,9 +461,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return false;
     };
 
-    onExternalDrop = (e: React.DragEvent) => {
-        return (pt => super.onExternalDrop(e, { x: pt[0], y: pt[1] }))(this.getTransform().transformPoint(e.pageX, e.pageY));
-    };
+    onExternalDrop = (e: React.DragEvent) => (([x, y]) => super.onExternalDrop(e, { x, y }))(this.getTransform().transformPoint(e.pageX, e.pageY));
 
     pickCluster(probe: number[]) {
         return this.childLayoutPairs
@@ -1820,9 +1816,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
         const viewctrls = ContextMenu.Instance.findByDescription('UI Controls...');
         const viewCtrlItems = viewctrls && 'subitems' in viewctrls ? viewctrls.subitems : [];
-        !Doc.noviceMode
-            ? viewCtrlItems.push({ description: (SnappingManager.GetShowSnapLines() ? 'Hide' : 'Show') + ' Snap Lines', event: () => SnappingManager.SetShowSnapLines(!SnappingManager.GetShowSnapLines()), icon: 'compress-arrows-alt' })
-            : null;
         !Doc.noviceMode ? viewCtrlItems.push({ description: (this.Document._freeform_useClusters ? 'Hide' : 'Show') + ' Clusters', event: () => this.updateClusters(!this.Document._freeform_useClusters), icon: 'braille' }) : null;
         !viewctrls && ContextMenu.Instance.addItem({ description: 'UI Controls...', subitems: viewCtrlItems, icon: 'eye' });
 
@@ -1858,23 +1851,38 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
 
     @action
-    setupDragLines = (snapToDraggedDoc: boolean = false) => {
+    dragEnding = () => {
+        this.GroupChildDrag = false;
+        SnappingManager.clearSnapLines();
+    };
+    @action
+    dragStarting = (snapToDraggedDoc: boolean = false, showGroupDragTarget: boolean, visited = new Set<Doc>()) => {
+        if (visited.has(this.rootDoc)) return;
+        visited.add(this.rootDoc);
+        showGroupDragTarget && (this.GroupChildDrag = BoolCast(this.Document._isGroup));
+        if (this.rootDoc._isGroup && this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView) {
+            this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.dragStarting(snapToDraggedDoc, false, visited);
+        }
         const activeDocs = this.getActiveDocuments();
         const size = this.getTransform().transformDirection(this.props.PanelWidth(), this.props.PanelHeight());
         const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] };
         const docDims = (doc: Doc) => ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) });
         const isDocInView = (doc: Doc, rect: { left: number; top: number; width: number; height: number }) => intersectRect(docDims(doc), rect);
 
-        const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) };
-        let snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to
-        !snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect))); // if not, see if there are background docs to snap to
-        !snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z !== undefined && isDocInView(doc, otherBounds))); // if not, then why not snap to floating docs
+        const snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to
+        activeDocs.forEach(
+            doc =>
+                doc._isGroup &&
+                SnappingManager.GetIsResizing() !== doc &&
+                !DragManager.docsBeingDragged.includes(doc) &&
+                (DocumentManager.Instance.getDocumentView(doc)?.ComponentView as CollectionFreeFormView)?.dragStarting(snapToDraggedDoc, false, visited)
+        );
 
         const horizLines: number[] = [];
         const vertLines: number[] = [];
         const invXf = this.getTransform().inverse();
         snappableDocs
-            .filter(doc => snapToDraggedDoc || !DragManager.docsBeingDragged.includes(Cast(doc.rootDocument, Doc, null) || doc))
+            .filter(doc => !doc._isGroup && (snapToDraggedDoc || (SnappingManager.GetIsResizing() !== doc && !DragManager.docsBeingDragged.includes(doc))))
             .forEach(doc => {
                 const { left, top, width, height } = docDims(doc);
                 const topLeftInScreen = invXf.transformPoint(left, top);
@@ -1883,7 +1891,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 horizLines.push(topLeftInScreen[1], topLeftInScreen[1] + docSize[1] / 2, topLeftInScreen[1] + docSize[1]); // horiz center line
                 vertLines.push(topLeftInScreen[0], topLeftInScreen[0] + docSize[0] / 2, topLeftInScreen[0] + docSize[0]); // right line
             });
-        DragManager.SetSnapLines(horizLines, vertLines);
+        SnappingManager.addSnapLines(horizLines, vertLines);
     };
 
     incrementalRendering = () => this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])).length !== 0;
@@ -1913,7 +1921,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         );
     }
 
-    showPresPaths = () => (CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.getPaths(this.rootDoc) : null);
     brushedView = () => this._brushedView;
     gridColor = () =>
         DashColor(lightOrDark(this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor)))
@@ -1921,7 +1928,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             .toString();
     @computed get marqueeView() {
         TraceMobx();
-        return (
+        return this._firstRender ? (
+            this.placeholder
+        ) : (
             <MarqueeView
                 {...this.props}
                 ref={this._marqueeViewRef}
@@ -1966,7 +1975,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         brushedView={this.brushedView}
                         isAnnotationOverlay={this.isAnnotationOverlay}
                         transform={this.contentTransform}
-                        presPaths={this.showPresPaths}
                         transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.props.DocumentView?.()?.rootDoc._viewTransition, 'string', null))}
                         viewDefDivClick={this.props.viewDefDivClick}>
                         {this.children}
@@ -2028,7 +2036,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 onClick={this.onClick}
                 onPointerDown={this.onPointerDown}
                 onPointerMove={this.onCursorMove}
-                onDrop={this.onExternalDrop.bind(this)}
+                onDrop={this.onExternalDrop}
                 onDragOver={e => e.preventDefault()}
                 onContextMenu={this.onContextMenu}
                 style={{
@@ -2065,18 +2073,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     </div>
                 ) : (
                     <>
-                        {this._firstRender ? this.placeholder : this.marqueeView}
+                        {this.marqueeView}
                         {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
-
-                        <div className="snapLines" style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
-                            <svg style={{ width: '100%', height: '100%' }}>
-                                {(this._hLines ?? [])
-                                    .map(l => <line x1="0" y1={l} x2="1000" y2={l} stroke="black" />) //
-                                    .concat((this._vLines ?? []).map(l => <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />)) ?? []}
-                            </svg>
-                        </div>
-
-                        {this.GroupChildDrag ? <div className="collectionFreeForm-groupDropper" /> : null}
+                        {!this.GroupChildDrag ? null : <div className="collectionFreeForm-groupDropper" />}
                     </>
                 )}
             </div>
@@ -2104,7 +2103,6 @@ interface CollectionFreeFormViewPannableContentsProps {
     children?: React.ReactNode | undefined;
     transition?: string;
     isAnnotationOverlay: boolean | undefined;
-    presPaths: () => JSX.Element | null;
     transform: () => string;
     brushedView: () => { panX: number; panY: number; width: number; height: number } | undefined;
 }
@@ -2112,41 +2110,21 @@ interface CollectionFreeFormViewPannableContentsProps {
 @observer
 class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps> {
     @computed get presPaths() {
-        return !this.props.presPaths() ? null : (
-            <>
-                <div className="presPathLabels">{PresBox.Instance?.orderedPathLabels(this.props.rootDoc)}</div>
-                <svg key="svg" className="presPaths">
-                    <defs>
-                        <marker id="markerSquare" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" orient="auto" overflow="visible">
-                            <rect x="0" y="0" width="3" height="3" stroke="#69a6db" strokeWidth="1" fill="white" fillOpacity="0.8" />
-                        </marker>
-                        <marker id="markerSquareFilled" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" orient="auto" overflow="visible">
-                            <rect x="0" y="0" width="3" height="3" stroke="#69a6db" strokeWidth="1" fill="#69a6db" />
-                        </marker>
-                        <marker id="markerArrow" markerWidth="3" markerHeight="3" refX="2" refY="4" orient="auto" overflow="visible">
-                            <path d="M2,2 L2,6 L6,4 L2,2 Z" stroke="#69a6db" strokeLinejoin="round" strokeWidth="1" fill="white" fillOpacity="0.8" />
-                        </marker>
-                    </defs>
-                    {this.props.presPaths()}
-                </svg>
-            </>
-        );
+        return CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.pathLines(this.props.rootDoc) : null;
     }
     // rectangle highlight used when following trail/link to a region of a collection that isn't a document
-    @computed get brushedView() {
-        const brushedView = this.props.brushedView();
-        return !brushedView ? null : (
+    showViewport = (viewport: { panX: number; panY: number; width: number; height: number } | undefined) =>
+        !viewport ? null : (
             <div
                 className="collectionFreeFormView-brushView"
                 style={{
-                    transform: `translate(${brushedView.panX}px, ${brushedView.panY}px)`,
-                    width: brushedView.width,
-                    height: brushedView.height,
-                    border: `orange solid ${brushedView.width * 0.005}px`,
+                    transform: `translate(${viewport.panX}px, ${viewport.panY}px)`,
+                    width: viewport.width,
+                    height: viewport.height,
+                    border: `orange solid ${viewport.width * 0.005}px`,
                 }}
             />
         );
-    }
 
     render() {
         return (
@@ -2165,7 +2143,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
                 }}>
                 {this.props.children}
                 {this.presPaths}
-                {this.brushedView}
+                {this.showViewport(this.props.brushedView())}
             </div>
         );
     }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 9f7ebc5d9..f9afe4d53 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -182,6 +182,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         }
     };
 
+    dragEnding = () => this.props.CollectionFreeFormView?.dragEnding();
+    dragStarting = () => this.props.CollectionFreeFormView?.dragStarting(false, true);
+
     nudge = (x: number, y: number) => {
         this.props.Document.x = NumCast(this.props.Document.x) + x;
         this.props.Document.y = NumCast(this.props.Document.y) + y;
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 54249a975..05810b63a 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1384,24 +1384,38 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 }
             });
         return (
-            <polyline
-                points={pathPoints}
-                style={{
-                    opacity: 1,
-                    stroke: '#69a6db',
-                    strokeWidth: 5,
-                    strokeDasharray: '10 5',
-                    boxShadow: '0px 4px 4px rgba(0, 0, 0, 0.25)',
-                }}
-                fill="none"
-                markerStart="url(#markerArrow)"
-                markerMid="url(#markerSquare)"
-                markerEnd="url(#markerSquareFilled)"
-            />
+            <>
+                <div className="presPathLabels">{PresBox.Instance?.orderedPathLabels(collection)}</div>
+                <svg key="svg" className="presPaths">
+                    <defs>
+                        <marker id="markerSquare" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" orient="auto" overflow="visible">
+                            <rect x="0" y="0" width="3" height="3" stroke="#69a6db" strokeWidth="1" fill="white" fillOpacity="0.8" />
+                        </marker>
+                        <marker id="markerSquareFilled" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" orient="auto" overflow="visible">
+                            <rect x="0" y="0" width="3" height="3" stroke="#69a6db" strokeWidth="1" fill="#69a6db" />
+                        </marker>
+                        <marker id="markerArrow" markerWidth="3" markerHeight="3" refX="2" refY="4" orient="auto" overflow="visible">
+                            <path d="M2,2 L2,6 L6,4 L2,2 Z" stroke="#69a6db" strokeLinejoin="round" strokeWidth="1" fill="white" fillOpacity="0.8" />
+                        </marker>
+                    </defs>
+                    <polyline
+                        points={pathPoints}
+                        style={{
+                            opacity: 1,
+                            stroke: '#69a6db',
+                            strokeWidth: 5,
+                            strokeDasharray: '10 5',
+                            boxShadow: '0px 4px 4px rgba(0, 0, 0, 0.25)',
+                        }}
+                        fill="none"
+                        markerStart="url(#markerArrow)"
+                        markerMid="url(#markerSquare)"
+                        markerEnd="url(#markerSquareFilled)"
+                    />
+                </svg>
+            </>
         );
     };
-    getPaths = (collection: Doc) => this.pathLines(collection); // needs to be smarter and figure out the paths to draw for this specific collection.  or better yet, draw everything in an overlay layer instad of within a collection
-
     // Converts seconds to ms and updates presentation_transition
     public static SetTransitionTime = (number: String, setter: (timeInMS: number) => void, change?: number) => {
         let timeInMS = Number(number) * 1000;
-- 
cgit v1.2.3-70-g09d2

