From 124c69af0941bd1ed5b03f3275aef37007f06ea7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 23 Sep 2024 03:45:04 -0400
Subject: made dropping links marks them as svgs.  fixed doc drag interactions
 when items are selected to not have decorations bounce. fixed card deck to 
 not shrink cards when card deck is short small and items are selected. 
 fixied dragging cards when colletcion is nested to not have decorations
 bounce,  and to allow rapid selection and dragging of cards.  fixed dragging
 docs into/out of card view.

---
 .../views/collections/CollectionCardDeckView.tsx   | 83 +++++++++++++---------
 1 file changed, 51 insertions(+), 32 deletions(-)

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

diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 70b751f4c..e5a6ebc7f 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -1,4 +1,4 @@
-import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction } from 'mobx';
+import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { ClientUtils, DashColor, returnFalse, returnZero } from '../../../ClientUtils';
@@ -46,8 +46,9 @@ export class CollectionCardView extends CollectionSubView() {
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _disposers: { [key: string]: IReactionDisposer } = {};
     private _textToDoc = new Map<string, Doc>();
+    private _dropped = false; // indicate when a card doc has just moved;
 
-    @observable _forceChildXf = false;
+    @observable _forceChildXf = 0;
     @observable _hoveredNodeIndex = -1;
     @observable _docRefs = new ObservableMap<Doc, DocumentView>();
     @observable _maxRowCount = 10;
@@ -99,6 +100,7 @@ export class CollectionCardView extends CollectionSubView() {
     };
 
     componentDidMount() {
+        this._props.setContentViewBox?.(this);
         // Reaction to cardSort changes
         this._disposers.sort = reaction(
             () => GPTPopup.Instance.visible,
@@ -110,6 +112,17 @@ export class CollectionCardView extends CollectionSubView() {
                 }
             }
         );
+        // if card deck moves, then the child doc views are hidden so their screen to local transforms will return empty rectangles
+        // when inquired from the dom (below in childScreenToLocal).  When the doc is actually renders, we need to act like the
+        // dash data just changed and trigger a React involidation with the correct data (read from the dom).
+        this._disposers.child = reaction(
+            () => [this.Document.x, this.Document.y],
+            () => {
+                if (!Array.from(this._docRefs.values()).every(dv => dv.ContentDiv?.getBoundingClientRect().width)) {
+                    setTimeout(action(() => this._forceChildXf++));
+                }
+            }
+        );
     }
 
     componentWillUnmount() {
@@ -174,6 +187,7 @@ export class CollectionCardView extends CollectionSubView() {
     inactiveDocs = () => this.childDocsWithoutLinks.filter(d => !DocumentView.SelectedDocs().includes(d));
 
     childPanelWidth = () => NumCast(this.layoutDoc.childPanelWidth, this._props.PanelWidth() / 2);
+    childPanelHeight = () => this._props.PanelHeight() * this.fitContentScale;
     onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
     isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
     isChildContentActive = () => !!this.isContentActive();
@@ -265,14 +279,7 @@ export class CollectionCardView extends CollectionSubView() {
 
     @action
     onPointerMove = (x: number, y: number) => {
-        this._docDraggedIndex = -1;
-        if (DragManager.docsBeingDragged.length) {
-            const newIndex = this.findCardDropIndex(x, y);
-
-            if (newIndex !== this._docDraggedIndex && newIndex != -1) {
-                this._docDraggedIndex = newIndex;
-            }
-        }
+        this._docDraggedIndex = DragManager.docsBeingDragged.length ? this.findCardDropIndex(x, y) : -1;
     };
 
     /**
@@ -304,6 +311,7 @@ export class CollectionCardView extends CollectionSubView() {
                     if (de.complete.docDragData.removeDocument?.(draggedDoc)) {
                         this.dataDoc[this.fieldKey] = new List<Doc>(sorted);
                     }
+                    this._dropped = true;
                 }
                 e.stopPropagation();
                 return true;
@@ -338,7 +346,8 @@ export class CollectionCardView extends CollectionSubView() {
      * @param isDesc
      * @returns
      */
-    sort = (docs: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => {
+    sort = (docsIn: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => {
+        const docs = docsIn.slice(); // need make new object list since sort() modifies the incoming list which confuses mobx caching
         sortType &&
             docs.sort((docA, docB) => {
                 const [typeA, typeB] = (() => {
@@ -362,21 +371,21 @@ export class CollectionCardView extends CollectionSubView() {
                 const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0;
                 return isDesc ? out : -out;
             });
-        if (dragIndex != -1) {
+        if (dragIndex !== -1) {
             const draggedDoc = DragManager.docsBeingDragged[0];
             const originalIndex = docs.findIndex(doc => doc === draggedDoc);
 
             originalIndex !== -1 && docs.splice(originalIndex, 1);
-            docs.splice(dragIndex, 0, draggedDoc);
+            draggedDoc && docs.splice(dragIndex, 0, draggedDoc);
         }
 
-        return [...docs]; // need to spread docs into a new object list since sort() modifies the incoming list which confuses mobx caching
+        return docs;
     };
 
     displayDoc = (doc: Doc, screenToLocalTransform: () => Transform) => (
         <DocumentView
             {...this._props}
-            ref={action((r: DocumentView) => r?.ContentDiv && this._docRefs.set(doc, r))}
+            ref={action((r: DocumentView) => (!r?.ContentDiv ? this._docRefs.delete(doc) : this._docRefs.set(doc, r)))}
             Document={doc}
             NativeWidth={returnZero}
             NativeHeight={returnZero}
@@ -385,11 +394,12 @@ export class CollectionCardView extends CollectionSubView() {
             renderDepth={this._props.renderDepth + 1}
             LayoutTemplate={this._props.childLayoutTemplate}
             LayoutTemplateString={this._props.childLayoutString}
+            containerViewPath={this.childContainerViewPath}
             ScreenToLocalTransform={screenToLocalTransform} // makes sure the box wrapper thing is in the right spot
             isContentActive={emptyFunction}
             isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
             PanelWidth={this.childPanelWidth}
-            PanelHeight={() => this._props.PanelHeight() * this.fitContentScale}
+            PanelHeight={this.childPanelHeight}
             dontCenter="y" // Don't center it vertically, because the grid it's in is already doing that and we don't want to do it twice.
             dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType}
             showTags={true}
@@ -591,7 +601,10 @@ export class CollectionCardView extends CollectionSubView() {
             const isSelected = view?.ComponentView?.isAnyChildContentActive?.() || view?.IsSelected ? true : false;
 
             const childScreenToLocal = () => {
+                // need to explicitly trigger an invalidation since we're reading everything from the Dom
                 this._forceChildXf;
+                this._props.ScreenToLocalTransform();
+
                 const dref = this._docRefs.get(doc);
                 const { translateX, translateY, scale } = ClientUtils.GetScreenTransform(dref?.ContentDiv);
                 return new Transform(-translateX + (dref?.centeringX || 0) * scale, 
@@ -606,25 +619,30 @@ export class CollectionCardView extends CollectionSubView() {
                 return (rowCenterIndex - indexInRow) * 100 - 50;
             };
             const aspect = NumCast(doc.height) / NumCast(doc.width, 1);
-            const vscale = Math.min((this._props.PanelHeight() * 0.95 * this.fitContentScale) / (aspect * this.childPanelWidth()), 
-                                    (this._props.PanelHeight() - 80) / (aspect * (this._props.PanelWidth() / 10))); // prettier-ignore
+            const vscale = Math.max(1,Math.min((this._props.PanelHeight() * 0.95 * this.fitContentScale) / (aspect * this.childPanelWidth()), 
+                                    (this._props.PanelHeight() - 80) / (aspect * (this._props.PanelWidth() / 10)))); // prettier-ignore
             const hscale = Math.min(this.sortedDocs.length, this._maxRowCount) / 2; // bcz: hack - the grid is divided evenly into maxRowCount cells, so the max scaling would be maxRowCount -- but making things that wide is ugly, so cap it off at half the window size
             return (
                 <div
                     key={doc[Id]}
                     className={`card-item${isSelected ? '-active' : anySelected ? '-inactive' : ''}`}
-                    onPointerUp={() => {
-                        if (DocumentView.SelectedDocs().includes(doc)) return;
-                        // this turns off documentDecorations during a transition, then turns them back on afterward.
-                        SnappingManager.SetIsResizing(doc[Id]);
-                        setTimeout(
-                            action(() => {
-                                SnappingManager.SetIsResizing(undefined);
-                                this._forceChildXf = !this._forceChildXf;
-                            }),
-                            900
-                        );
-                    }}
+                    onPointerUp={action(() => {
+                        // if a card doc has just moved, or a card is selected and in front, then ignore this event
+                        if (DocumentView.SelectedDocs().includes(doc) || this._dropped) {
+                            this._dropped = false;
+                        } else {
+                            // otherwise, turn off documentDecorations becase we're in a selection transition and want to avoid artifacts.
+                            // Turn them back on when the animation has completed and the render and backend structures are in synch
+                            SnappingManager.SetIsResizing(doc[Id]);
+                            setTimeout(
+                                action(() => {
+                                    SnappingManager.SetIsResizing(undefined);
+                                    this._forceChildXf++;
+                                }),
+                                600
+                            );
+                        }
+                    })}
                     style={{
                         width: this.childPanelWidth(),
                         height: 'max-content',
@@ -633,7 +651,7 @@ export class CollectionCardView extends CollectionSubView() {
                                     rotate(${!isSelected ? this.rotate(amCards, calcRowIndex) : 0}deg)
                                     scale(${isSelected ? `${Math.min(hscale, vscale) * 100}%` : this._hoveredNodeIndex === index ? 1.05 : 1})`,
                     }} // prettier-ignore
-                    onMouseEnter={() => this.setHoveredNodeIndex(index)}>
+                    onPointerEnter={() => !SnappingManager.IsDragging && this.setHoveredNodeIndex(index)}>
                     {this.displayDoc(doc, childScreenToLocal)}
                 </div>
             );
@@ -647,7 +665,8 @@ export class CollectionCardView extends CollectionSubView() {
             <div
                 className="collectionCardView-outer"
                 ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)}
-                onPointerMove={e => this.onPointerMove(e.clientX, e.clientY)}
+                onPointerLeave={action(() => (this._docDraggedIndex = -1))}
+                onPointerMove={e => this.onPointerMove(...this._props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY))}
                 onDrop={this.onExternalDrop.bind(this)}
                 style={{
                     background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string,
-- 
cgit v1.2.3-70-g09d2

