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.

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 98 +++++++++-------------
 1 file changed, 38 insertions(+), 60 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 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>
         );
     }
-- 
cgit v1.2.3-70-g09d2

