From f63aa4c65f6336c7708c50ced83867702a310240 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 4 Oct 2024 18:39:15 -0400
Subject: cleaning up CardDeckView so that animations work smoothly

---
 .../views/collections/CollectionCardDeckView.tsx   | 199 +++++++++------------
 1 file changed, 84 insertions(+), 115 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 101cc8082..a9ab9de26 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -2,7 +2,6 @@ import { IReactionDisposer, ObservableMap, action, computed, makeObservable, obs
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { ClientUtils, DashColor, returnFalse, returnZero } from '../../../ClientUtils';
-import { emptyFunction } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
@@ -13,7 +12,6 @@ import { gptImageLabel } from '../../apis/gpt/GPT';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DragManager } from '../../util/DragManager';
 import { dropActionType } from '../../util/DropActionTypes';
-import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoable } from '../../util/UndoManager';
@@ -23,12 +21,12 @@ import { DocumentView } from '../nodes/DocumentView';
 import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
 import './CollectionCardDeckView.scss';
 import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
+import { computedFn } from 'mobx-utils';
 
 enum cardSortings {
     Time = 'time',
     Type = 'type',
     Color = 'color',
-    Custom = 'custom',
     Chat = 'chat',
     Tag = 'tag',
     None = '',
@@ -46,14 +44,13 @@ 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;
+    private _dropped = false; // set when a card doc has just moved and the drop method has been called - prevents the pointerUp method from hiding doc decorations (which needs to be done when clicking on a card to animate it to front/center)
 
     @observable _forceChildXf = 0;
     @observable _hoveredNodeIndex = -1;
     @observable _docRefs = new ObservableMap<Doc, DocumentView>();
     @observable _maxRowCount = 10;
     @observable _docDraggedIndex: number = -1;
-    @observable overIndex: number = -1;
 
     static imageUrlToBase64 = async (imageUrl: string): Promise<string> => {
         try {
@@ -101,7 +98,6 @@ export class CollectionCardView extends CollectionSubView() {
 
     componentDidMount() {
         this._props.setContentViewBox?.(this);
-        // Reaction to cardSort changes
         this._disposers.sort = reaction(
             () => GPTPopup.Instance.visible,
             isVis => {
@@ -135,8 +131,7 @@ export class CollectionCardView extends CollectionSubView() {
     }
 
     /**
-     * The child documents to be rendered-- either all of them except the Links or the docs in the currently active
-     * custom group
+     * The child documents to be rendered-- everything other than ink/link docs (which are marks as being svg's)
      */
     @computed get childDocsWithoutLinks() {
         return this.childDocs.filter(l => !l.layout_isSvg);
@@ -155,8 +150,7 @@ export class CollectionCardView extends CollectionSubView() {
      */
     quizMode = () => {
         const randomIndex = Math.floor(Math.random() * this.childDocs.length);
-        SelectionManager.DeselectAll();
-        DocumentView.SelectView(DocumentView.getDocumentView(this.childDocs[randomIndex]), false);
+        DocumentView.getDocumentView(this.childDocs[randomIndex])?.select(false);
     };
 
     /**
@@ -168,29 +162,15 @@ export class CollectionCardView extends CollectionSubView() {
 
     @action
     setHoveredNodeIndex = (index: number) => {
-        if (!DocumentView.SelectedDocs().includes(this.childDocs[index])) {
-            this._hoveredNodeIndex = index;
-        }
+        if (!SnappingManager.IsDragging) this._hoveredNodeIndex = index;
     };
-    /**
-     * Translates the hovered node to the center of the screen
-     * @param index
-     * @returns
-     */
-    translateHover = (index: number) => (this._hoveredNodeIndex === index && !DocumentView.SelectedDocs().includes(this.childDocs[index]) ? -50 : 0);
-
-    isSelected = (index: number) => DocumentView.SelectedDocs().includes(this.childDocs[index]);
-
-    /**
-     * Returns all the documents except the one that's currently selected
-     */
-    inactiveDocs = () => this.childDocsWithoutLinks.filter(d => !DocumentView.SelectedDocs().includes(d));
 
+    isSelected = (doc: Doc) => this._docRefs.get(doc)?.IsSelected;
     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();
+    isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this.isAnyChildContentActive();
+    isAnyChildContentActive = this._props.isAnyChildContentActive;
 
     /**
      * Returns the degree to rotate a card dependind on the amount of cards in their row and their index in said row
@@ -202,13 +182,14 @@ export class CollectionCardView extends CollectionSubView() {
         if (amCards == 1) return 0;
 
         const possRotate = -30 + index * (30 / ((amCards - (amCards % 2)) / 2));
-        const stepMag = Math.abs(-30 + (amCards / 2 - 1) * (30 / ((amCards - (amCards % 2)) / 2)));
-
-        if (amCards % 2 === 0 && possRotate === 0) {
-            return possRotate + Math.abs(-30 + (index - 1) * (30 / (amCards / 2)));
-        }
-        if (amCards % 2 === 0 && index > (amCards + 1) / 2) {
-            return possRotate + stepMag;
+        if (amCards % 2 === 0) {
+            if (possRotate === 0) {
+                return possRotate + Math.abs(-30 + (index - 1) * (30 / (amCards / 2)));
+            }
+            if (index > (amCards + 1) / 2) {
+                const stepMag = Math.abs(-30 + (amCards / 2 - 1) * (30 / ((amCards - (amCards % 2)) / 2)));
+                return possRotate + stepMag;
+            }
         }
 
         return possRotate;
@@ -274,21 +255,12 @@ export class CollectionCardView extends CollectionSubView() {
 
     /**
      * Checks to see if a card is being dragged and calls the appropriate methods if so
-     * @param e the current pointer event
      */
-
     @action
     onPointerMove = (x: number, y: number) => {
         this._docDraggedIndex = DragManager.docsBeingDragged.length ? this.findCardDropIndex(x, y) : -1;
     };
 
-    /**
-     * Handles external drop of images/PDFs etc from outside Dash.
-     */
-    onExternalDrop = async (e: React.DragEvent): Promise<void> => {
-        super.onExternalDrop(e, {});
-    };
-
     /**
      * Resets all the doc dragging vairables once a card is dropped
      * @param e
@@ -326,7 +298,7 @@ export class CollectionCardView extends CollectionSubView() {
     }
 
     /**
-     * Used to determine how to sort cards based on tags. The lestmost tags are given lower values while cards to the right are
+     * Used to determine how to sort cards based on tags. The leftmost tags are given lower values while cards to the right are
      * given higher values. Decimals are used to determine placement for cards with multiple tags
      * @param doc the doc whose value is being determined
      * @returns its value based on its tags
@@ -352,24 +324,14 @@ export class CollectionCardView extends CollectionSubView() {
             docs.sort((docA, docB) => {
                 const [typeA, typeB] = (() => {
                     switch (sortType) {
-                        case cardSortings.Time:
-                            return [DateCast(docA.author_date)?.date ?? Date.now(), DateCast(docB.author_date)?.date ?? Date.now()];
-                        case cardSortings.Color: {
-                            const d1 = DashColor(StrCast(docA.backgroundColor));
-                            const d2 = DashColor(StrCast(docB.backgroundColor));
-                            return [d1.hsv().hue(), d2.hsv().hue()];
-                        }
-                        case cardSortings.Tag:
-                            return [this.tagValue(docA) ?? 9999, this.tagValue(docB) ?? 9999];
-                        case cardSortings.Chat:
-                            return [NumCast(docA.chatIndex) ?? 9999, NumCast(docB.chatIndex) ?? 9999];
-                        default:
-                            return [StrCast(docA.type), StrCast(docB.type)];
+                        default:                
+                        case cardSortings.Type: return [StrCast(docA.type),                                   StrCast(docB.type)];
+                        case cardSortings.Chat: return [NumCast(docA.chatIndex, 9999),                        NumCast(docB.chatIndex,9999)];
+                        case cardSortings.Time: return [DateCast(docA.author_date)?.date ?? Date.now(),       DateCast(docB.author_date)?.date ?? Date.now()];
+                        case cardSortings.Color:return [DashColor(StrCast(docA.backgroundColor)).hsv().hue(), DashColor(StrCast(docB.backgroundColor)).hsv().hue()];
                     }
-                })();
-
-                const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0;
-                return isDesc ? out : -out;
+                })(); //prettier-ignore
+                return (typeA < typeB ? -1 : typeA > typeB ? 1 : 0) * (isDesc ? 1 : -1);
             });
         if (dragIndex !== -1) {
             const draggedDoc = DragManager.docsBeingDragged[0];
@@ -382,6 +344,15 @@ export class CollectionCardView extends CollectionSubView() {
         return docs;
     };
 
+    isChildContentActive = () =>
+        this._props.isContentActive?.() === false
+            ? false
+            : this._props.isDocumentActive?.() && (this._props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive))
+              ? true
+              : this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false
+                ? false
+                : undefined;
+
     displayDoc = (doc: Doc, screenToLocalTransform: () => Transform) => (
         <DocumentView
             {...this._props}
@@ -396,13 +367,14 @@ export class CollectionCardView extends CollectionSubView() {
             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.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={BoolCast(this.layoutDoc.showChildTags)}
+            whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
+            isContentActive={this.isChildContentActive}
             dontHideOnDrag
         />
     );
@@ -416,13 +388,11 @@ export class CollectionCardView extends CollectionSubView() {
         if (this.sortedDocs.length < this._maxRowCount) {
             return this.sortedDocs.length;
         }
-        // 13 - 3 = 10
         const totalCards = this.sortedDocs.length;
         // if 9 or less
         if (index < totalCards - (totalCards % this._maxRowCount)) {
             return this._maxRowCount;
         }
-        // (3)
         return totalCards % this._maxRowCount;
     };
     /**
@@ -443,17 +413,17 @@ export class CollectionCardView extends CollectionSubView() {
     /**
      * Determines how far to translate a card in the y direction depending on its index, whether or not its being hovered, or if it's selected
      * @param isHovered
-     * @param isSelected
+     * @param isActive
      * @param realIndex
      * @param amCards
      * @param calcRowIndex
      * @returns
      */
-    calculateTranslateY = (isHovered: boolean, isSelected: boolean, realIndex: number, amCards: number, calcRowIndex: number) => {
+    calculateTranslateY = (isHovered: boolean, isActive: boolean, realIndex: number, amCards: number, calcRowIndex: number) => {
         const rowHeight = (this._props.PanelHeight() * this.fitContentScale) / this.numRows;
         const rowIndex = Math.trunc(realIndex / this._maxRowCount);
         const rowToCenterShift = this.numRows / 2 - rowIndex;
-        if (isSelected) return rowToCenterShift * rowHeight - rowHeight / 2;
+        if (isActive) return rowToCenterShift * rowHeight - rowHeight / 2;
         if (amCards == 1) return 50 * this.fitContentScale;
         return this.translateY(amCards, calcRowIndex, realIndex);
     };
@@ -576,15 +546,43 @@ export class CollectionCardView extends CollectionSubView() {
         await this.childPairStringListAndUpdateSortDesc();
     };
 
+    childScreenToLocal = computedFn((doc: Doc, index: number, calcRowIndex: number, isSelected: boolean, amCards: number) => () => {
+        // 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);
+        if (!scale) return new Transform(0, 0, 0);
+
+        return new Transform(-translateX + (dref?.centeringX || 0) * scale, 
+                             -translateY + (dref?.centeringY || 0) * scale, 1)
+                  .scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, calcRowIndex) : 0); // prettier-ignore
+    });
+
+    cardPointerUp = action((doc: Doc) => {
+        // if a card doc has just moved, or a card is selected and in front, then ignore this event
+        if (this.isSelected(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++;
+                }),
+                1000
+            );
+        }
+    });
+
     /**
      * Actually renders all the cards
      */
     @computed get renderCards() {
-        const sortedDocs = this.sortedDocs;
-        const anySelected = this.childDocs.some(doc => DocumentView.SelectedDocs().includes(doc));
-        const isEmpty = this.childDocsWithoutLinks.length === 0;
-
-        if (isEmpty) {
+        if (!this.childDocsWithoutLinks.length) {
             return (
                 <span className="no-card-span" style={{ width: ` ${this._props.PanelWidth()}px`, height: ` ${this._props.PanelHeight()}px` }}>
                     Sorry ! There are no cards in this group
@@ -593,31 +591,18 @@ export class CollectionCardView extends CollectionSubView() {
         }
 
         // Map sorted documents to their rendered components
-        return sortedDocs.map((doc, index) => {
-            const realIndex = sortedDocs.indexOf(doc);
+        return this.sortedDocs.map((doc, index) => {
+            const realIndex = this.sortedDocs.indexOf(doc);
             const calcRowIndex = this.overflowIndexCalc(realIndex);
             const amCards = this.overflowAmCardsCalc(realIndex);
             const view = DocumentView.getDocumentView(doc, this.DocumentView?.());
-            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);
-                if (!scale) return new Transform(0, 0, 0);
-
-                return new Transform(-translateX + (dref?.centeringX || 0) * scale, 
-                                     -translateY + (dref?.centeringY || 0) * scale, 1)
-                          .scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, calcRowIndex) : 0); // prettier-ignore
-            };
+            const childScreenToLocal = this.childScreenToLocal(doc, index, calcRowIndex, !!view?.IsContentActive, amCards);
 
             const translateIfSelected = () => {
                 const indexInRow = index % this._maxRowCount;
                 const rowIndex = Math.trunc(index / this._maxRowCount);
-                const rowCenterIndex = Math.min(this._maxRowCount, sortedDocs.length - rowIndex * this._maxRowCount) / 2;
+                const rowCenterIndex = Math.min(this._maxRowCount, this.sortedDocs.length - rowIndex * this._maxRowCount) / 2;
                 return (rowCenterIndex - indexInRow) * 100 - 50;
             };
             const aspect = NumCast(doc.height) / NumCast(doc.width, 1);
@@ -627,33 +612,18 @@ export class CollectionCardView extends CollectionSubView() {
             return (
                 <div
                     key={doc[Id]}
-                    className={`card-item${isSelected ? '-active' : anySelected ? '-inactive' : ''}`}
-                    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++;
-                                }),
-                                1000
-                            );
-                        }
-                    })}
+                    className={`card-item${view?.IsContentActive ? '-active' : this.isAnyChildContentActive() ? '-inactive' : ''}`}
+                    onPointerUp={() => this.cardPointerUp(doc)}
                     style={{
                         width: this.childPanelWidth(),
                         height: 'max-content',
-                        transform: `translateY(${this.calculateTranslateY(this._hoveredNodeIndex === index, isSelected, realIndex, amCards, calcRowIndex)}px)
-                                    translateX(calc(${isSelected ? translateIfSelected() : 0}% + ${this.translateOverflowX(realIndex, amCards)}px))
-                                    rotate(${!isSelected ? this.rotate(amCards, calcRowIndex) : 0}deg)
-                                    scale(${isSelected ? `${Math.min(hscale, vscale) * 100}%` : this._hoveredNodeIndex === index ? 1.05 : 1})`,
+                        transform: `translateY(${this.calculateTranslateY(this._hoveredNodeIndex === index, !!view?.IsContentActive, realIndex, amCards, calcRowIndex)}px)
+                                    translateX(calc(${view?.IsContentActive ? translateIfSelected() : 0}% + ${this.translateOverflowX(realIndex, amCards)}px))
+                                    rotate(${!view?.IsContentActive ? this.rotate(amCards, calcRowIndex) : 0}deg)
+                                    scale(${view?.IsContentActive ? `${Math.min(hscale, vscale) * 100}%` : this._hoveredNodeIndex === index ? 1.1 : 1})`,
                     }} // prettier-ignore
-                    onPointerEnter={() => !SnappingManager.IsDragging && this.setHoveredNodeIndex(index)}>
+                    onPointerEnter={() => this.setHoveredNodeIndex(index)}
+                    onPointerLeave={() => this.setHoveredNodeIndex(-1)}>
                     {this.displayDoc(doc, childScreenToLocal)}
                 </div>
             );
@@ -680,8 +650,7 @@ export class CollectionCardView extends CollectionSubView() {
                         ...(!isEmpty && { transform: `scale(${1 / this.fitContentScale})` }),
                         ...(!isEmpty && { height: `${100 * this.fitContentScale}%` }),
                         gridAutoRows: `${100 / this.numRows}%`,
-                    }}
-                    onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
+                    }}>
                     {this.renderCards}
                 </div>
             </div>
-- 
cgit v1.2.3-70-g09d2


From cd7e213e7e0b9b6606ae068c29412bb64cdf6f2d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 7 Oct 2024 10:43:48 -0400
Subject: comment cleanup

---
 .../views/collections/CollectionCardDeckView.tsx   | 24 ++++++++++------------
 1 file changed, 11 insertions(+), 13 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 a9ab9de26..0c2bcc580 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -411,15 +411,14 @@ export class CollectionCardView extends CollectionSubView() {
     translateOverflowX = (realIndex: number, calcRowCards: number) => (realIndex < this._maxRowCount ? 0 : (this._maxRowCount - calcRowCards) * (this.childPanelWidth() / 2));
 
     /**
-     * Determines how far to translate a card in the y direction depending on its index, whether or not its being hovered, or if it's selected
-     * @param isHovered
-     * @param isActive
-     * @param realIndex
-     * @param amCards
-     * @param calcRowIndex
-     * @returns
+     * Determines how far to translate a card in the y direction depending on its index and if it's selected
+     * @param isActive whether the card is focused for interaction
+     * @param realIndex index of card from start of deck
+     * @param amCards ??
+     * @param calcRowIndex index of card from start of row
+     * @returns Y translation of card
      */
-    calculateTranslateY = (isHovered: boolean, isActive: boolean, realIndex: number, amCards: number, calcRowIndex: number) => {
+    calculateTranslateY = (isActive: boolean, realIndex: number, amCards: number, calcRowIndex: number) => {
         const rowHeight = (this._props.PanelHeight() * this.fitContentScale) / this.numRows;
         const rowIndex = Math.trunc(realIndex / this._maxRowCount);
         const rowToCenterShift = this.numRows / 2 - rowIndex;
@@ -592,9 +591,8 @@ export class CollectionCardView extends CollectionSubView() {
 
         // Map sorted documents to their rendered components
         return this.sortedDocs.map((doc, index) => {
-            const realIndex = this.sortedDocs.indexOf(doc);
-            const calcRowIndex = this.overflowIndexCalc(realIndex);
-            const amCards = this.overflowAmCardsCalc(realIndex);
+            const calcRowIndex = this.overflowIndexCalc(index);
+            const amCards = this.overflowAmCardsCalc(index);
             const view = DocumentView.getDocumentView(doc, this.DocumentView?.());
 
             const childScreenToLocal = this.childScreenToLocal(doc, index, calcRowIndex, !!view?.IsContentActive, amCards);
@@ -617,8 +615,8 @@ export class CollectionCardView extends CollectionSubView() {
                     style={{
                         width: this.childPanelWidth(),
                         height: 'max-content',
-                        transform: `translateY(${this.calculateTranslateY(this._hoveredNodeIndex === index, !!view?.IsContentActive, realIndex, amCards, calcRowIndex)}px)
-                                    translateX(calc(${view?.IsContentActive ? translateIfSelected() : 0}% + ${this.translateOverflowX(realIndex, amCards)}px))
+                        transform: `translateY(${this.calculateTranslateY(!!view?.IsContentActive, index, amCards, calcRowIndex)}px)
+                                    translateX(calc(${view?.IsContentActive ? translateIfSelected() : 0}% + ${this.translateOverflowX(index, amCards)}px))
                                     rotate(${!view?.IsContentActive ? this.rotate(amCards, calcRowIndex) : 0}deg)
                                     scale(${view?.IsContentActive ? `${Math.min(hscale, vscale) * 100}%` : this._hoveredNodeIndex === index ? 1.1 : 1})`,
                     }} // prettier-ignore
-- 
cgit v1.2.3-70-g09d2


From efaa2991ac2670c73a81ab07f61c1626a4300507 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 7 Oct 2024 22:03:54 -0400
Subject: refactored imageUrlToBase64 into clientUtils.  lint fixes.

---
 src/ClientUtils.ts                                 | 21 ++++++++++++
 src/client/views/InkTranscription.tsx              | 36 +++++----------------
 .../views/collections/CollectionCardDeckView.tsx   | 23 ++------------
 .../collectionFreeForm/ImageLabelBox.tsx           | 22 +++++++------
 src/client/views/nodes/ComparisonBox.tsx           | 37 ++++++----------------
 src/client/views/nodes/LabelBox.tsx                |  3 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 21 ++----------
 src/client/views/smartdraw/SmartDrawHandler.tsx    |  4 +--
 8 files changed, 59 insertions(+), 108 deletions(-)

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

diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts
index 972910071..e8165d5ab 100644
--- a/src/ClientUtils.ts
+++ b/src/ClientUtils.ts
@@ -655,6 +655,27 @@ export function dateRangeStrToDates(dateStr: string) {
     return { start: new Date(dateRangeParts[0]), end: new Date(dateRangeParts[1]) };
 }
 
+/**
+ * converts the image to base url formate
+ * @param imageUrl imageurl taken from the collection icon
+ */
+export async function imageUrlToBase64(imageUrl: string): Promise<string> {
+    try {
+        const response = await fetch(imageUrl);
+        const blob = await response.blob();
+
+        return new Promise((resolve, reject) => {
+            const reader = new FileReader();
+            reader.readAsDataURL(blob);
+            reader.onloadend = () => resolve(reader.result as string);
+            reader.onerror = error => reject(error);
+        });
+    } catch (error) {
+        console.error('Error:', error);
+        throw error;
+    }
+}
+
 function replaceCanvases(oldDiv: HTMLElement, newDiv: HTMLElement) {
     if (oldDiv.childNodes && newDiv) {
         for (let i = 0; i < oldDiv.childNodes.length; i++) {
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
index 24d53a8c8..c5a9d3ba4 100644
--- a/src/client/views/InkTranscription.tsx
+++ b/src/client/views/InkTranscription.tsx
@@ -1,19 +1,19 @@
 import * as iink from 'iink-ts';
 import { action, observable } from 'mobx';
 import * as React from 'react';
+import { imageUrlToBase64 } from '../../ClientUtils';
+import { aggregateBounds } from '../../Utils';
 import { Doc, DocListCast } from '../../fields/Doc';
 import { InkData, InkField, InkTool } from '../../fields/InkField';
 import { Cast, DateCast, ImageCast, NumCast } from '../../fields/Types';
-import { aggregateBounds } from '../../Utils';
+import { ImageField, URLField } from '../../fields/URLField';
+import { gptHandwriting } from '../apis/gpt/GPT';
 import { DocumentType } from '../documents/DocumentTypes';
-import { CollectionFreeFormView, MarqueeView } from './collections/collectionFreeForm';
-import { InkingStroke } from './InkingStroke';
-import './InkTranscription.scss';
 import { Docs } from '../documents/Documents';
+import './InkTranscription.scss';
+import { InkingStroke } from './InkingStroke';
+import { CollectionFreeFormView, MarqueeView } from './collections/collectionFreeForm';
 import { DocumentView } from './nodes/DocumentView';
-import { ImageField } from '../../fields/URLField';
-import { gptHandwriting } from '../apis/gpt/GPT';
-import { URLField } from '../../fields/URLField';
 /**
  * Class component that handles inking in writing mode
  */
@@ -260,7 +260,7 @@ export class InkTranscription extends React.Component {
                     const hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`;
                     let response;
                     try {
-                        const hrefBase64 = await this.imageUrlToBase64(hrefComplete);
+                        const hrefBase64 = await imageUrlToBase64(hrefComplete);
                         response = await gptHandwriting(hrefBase64);
                     } catch {
                         console.error('Error getting image');
@@ -291,26 +291,6 @@ export class InkTranscription extends React.Component {
         }
         return undefined;
     }
-    /**
-     * converts the image to base url formate
-     * @param imageUrl imageurl taken from the collection icon
-     */
-    imageUrlToBase64 = async (imageUrl: string): Promise<string> => {
-        try {
-            const response = await fetch(imageUrl);
-            const blob = await response.blob();
-
-            return new Promise((resolve, reject) => {
-                const reader = new FileReader();
-                reader.readAsDataURL(blob);
-                reader.onloadend = () => resolve(reader.result as string);
-                reader.onerror = error => reject(error);
-            });
-        } catch (error) {
-            console.error('Error:', error);
-            throw error;
-        }
-    };
 
     /**
      * Creates the ink grouping once the user leaves the writing mode.
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 842724e39..c61b0b4dd 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -1,7 +1,8 @@
 import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
+import { computedFn } from 'mobx-utils';
 import * as React from 'react';
-import { ClientUtils, DashColor, returnFalse, returnZero } from '../../../ClientUtils';
+import { ClientUtils, DashColor, imageUrlToBase64, returnFalse, returnZero } from '../../../ClientUtils';
 import { Doc } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
@@ -21,7 +22,6 @@ import { DocumentView } from '../nodes/DocumentView';
 import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
 import './CollectionCardDeckView.scss';
 import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
-import { computedFn } from 'mobx-utils';
 
 enum cardSortings {
     Time = 'time',
@@ -52,23 +52,6 @@ export class CollectionCardView extends CollectionSubView() {
     @observable _maxRowCount = 10;
     @observable _docDraggedIndex: number = -1;
 
-    static imageUrlToBase64 = async (imageUrl: string): Promise<string> => {
-        try {
-            const response = await fetch(imageUrl);
-            const blob = await response.blob();
-
-            return new Promise((resolve, reject) => {
-                const reader = new FileReader();
-                reader.readAsDataURL(blob);
-                reader.onloadend = () => resolve(reader.result as string);
-                reader.onerror = error => reject(error);
-            });
-        } catch (error) {
-            console.error('Error:', error);
-            throw error;
-        }
-    };
-
     constructor(props: SubCollectionViewProps) {
         super(props);
         makeObservable(this);
@@ -462,7 +445,7 @@ export class CollectionCardView extends CollectionSubView() {
         const hrefParts = href.split('.');
         const hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`;
         try {
-            const hrefBase64 = await CollectionCardView.imageUrlToBase64(hrefComplete);
+            const hrefBase64 = await imageUrlToBase64(hrefComplete);
             const response = await gptImageLabel(hrefBase64, 'Give three to five labels to describe this image.');
             image[DocData].description = response.trim();
             return response; // Return the response from gptImageLabel
diff --git a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
index 753685b97..583f2e656 100644
--- a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
+++ b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
@@ -6,6 +6,7 @@ import 'ldrs/ring';
 import { action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import React from 'react';
+import { imageUrlToBase64 } from '../../../../ClientUtils';
 import { Utils, numberRange } from '../../../../Utils';
 import { Doc, NumListCast, Opt } from '../../../../fields/Doc';
 import { DocData } from '../../../../fields/DocSymbols';
@@ -22,13 +23,13 @@ import { MainView } from '../../MainView';
 import { DocumentView } from '../../nodes/DocumentView';
 import { FieldView, FieldViewProps } from '../../nodes/FieldView';
 import { OpenWhere } from '../../nodes/OpenWhere';
-import { CollectionCardView } from '../CollectionCardDeckView';
 import './ImageLabelBox.scss';
 import { MarqueeOptionsMenu } from './MarqueeOptionsMenu';
 
 export class ImageInformationItem {}
 
 export class ImageLabelBoxData {
+    // eslint-disable-next-line no-use-before-define
     static _instance: ImageLabelBoxData;
     @observable _docs: Doc[] = [];
     @observable _labelGroups: string[] = [];
@@ -47,8 +48,8 @@ export class ImageLabelBoxData {
     };
 
     @action
-    addLabel = (label: string) => {
-        label = label.toUpperCase().trim();
+    addLabel = (labelIn: string) => {
+        const label = labelIn.toUpperCase().trim();
         if (label.length > 0) {
             if (!this._labelGroups.includes(label)) {
                 this._labelGroups = [...this._labelGroups, label.startsWith('#') ? label : '#' + label];
@@ -68,9 +69,10 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(ImageLabelBox, fieldKey);
     }
+    // eslint-disable-next-line no-use-before-define
+    public static Instance: ImageLabelBox;
 
     private _dropDisposer?: DragManager.DragDropDisposer;
-    public static Instance: ImageLabelBox;
     private _inputRef = React.createRef<HTMLInputElement>();
     @observable _loading: boolean = false;
     private _currentLabel: string = '';
@@ -99,7 +101,7 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
     @observable _displayImageInformation: boolean = false;
 
-    constructor(props: any) {
+    constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
         ring.register();
@@ -165,7 +167,7 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const imageInfos = this._selectedImages.map(async doc => {
             if (!doc[DocData].tags_chat) {
                 const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)]).url.href.split('.');
-                return CollectionCardView.imageUrlToBase64(`${name}_o.${type}`).then(hrefBase64 =>
+                return imageUrlToBase64(`${name}_o.${type}`).then(hrefBase64 =>
                 !hrefBase64 ? undefined : 
                     gptImageLabel(hrefBase64,'Give three labels to describe this image.').then(labels => 
                         ({ doc, labels }))) ; // prettier-ignore
@@ -178,13 +180,13 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
                 const labels = imageInfo.labels.split('\n');
                 labels.forEach(label => {
-                    label =
+                    const hashLabel =
                         '#' +
                         label
                             .replace(/^\d+\.\s*|-|f\*/, '')
                             .replace(/^#/, '')
                             .trim();
-                    (imageInfo.doc[DocData].tags_chat as List<string>).push(label);
+                    (imageInfo.doc[DocData].tags_chat as List<string>).push(hashLabel);
                 });
             }
         });
@@ -214,7 +216,7 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
         // most similar one.
         this._selectedImages.forEach(doc => {
             const embedLists = numberRange((doc[DocData].tags_chat as List<string>).length).map(n => Array.from(NumListCast(doc[DocData][`tags_embedding_${n + 1}`])));
-            const bestEmbedScore = (embedding: Opt<number[]>) => Math.max(...embedLists.map((l, index) => (embedding && similarity(Array.from(embedding), l)!) || 0));
+            const bestEmbedScore = (embedding: Opt<number[]>) => Math.max(...embedLists.map(l => (embedding && similarity(Array.from(embedding), l)!) || 0));
             const {label: mostSimilarLabelCollect} = 
                 this._labelGroups.map(label => ({ label, similarityScore: bestEmbedScore(labelToEmbedding.get(label)) }))
                            .reduce((prev, cur) => cur.similarityScore < 0.3 || cur.similarityScore <= prev.similarityScore ? prev: cur,
@@ -243,7 +245,7 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (this._selectedImages.length === 0) {
             return (
                 <div className="searchBox-container" style={{ pointerEvents: 'all', color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }} ref={ele => this.createDropTarget(ele!)}>
-                    <p style={{ fontSize: 'large' }}>In order to classify and sort images, marquee select the desired images and press the 'Classify and Sort Images' button. Then, add the desired groups for the images to be put in.</p>
+                    <p style={{ fontSize: 'large' }}>In order to classify and sort images, marquee select the desired images and press the &apos;Classify and Sort Images&apos; button. Then, add the desired groups for the images to be put in.</p>
                 </div>
             );
         }
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index c32bbc803..f7c478729 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -5,7 +5,7 @@ import { IReactionDisposer, action, computed, makeObservable, observable, reacti
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import ReactLoading from 'react-loading';
-import { returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents } from '../../../ClientUtils';
+import { imageUrlToBase64, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents } from '../../../ClientUtils';
 import { emptyFunction } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
@@ -365,7 +365,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 'Content-Type': 'application/json',
             },
         });
-        this.Document.phoneticTranscription = response.data['transcription'];
+        this.Document.phoneticTranscription = response.data.transcription;
     };
 
     /**
@@ -374,7 +374,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
      * @returns
      */
     getYouTubeVideoId = (url: string) => {
-        const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=|\?v=)([^#\&\?]*).*/;
+        const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=|\?v=)([^#&?]*).*/;
         const match = url.match(regExp);
         return match && match[2].length === 11 ? match[2] : null;
     };
@@ -392,7 +392,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 'Content-Type': 'application/json',
             },
         });
-        return response.data['transcription'];
+        return response.data.transcription;
     };
 
     createFlashcardPile(collectionArr: Doc[], gpt: boolean) {
@@ -402,9 +402,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
             _layout_fitWidth: false,
             _layout_autoHeight: true,
         });
-        newCol['x'] = this.layoutDoc['x'];
-        newCol['y'] = NumCast(this.layoutDoc['y']) + 50;
-        newCol.type_collection = 'carousel';
+        newCol.x = this.layoutDoc.x;
+        newCol.y = NumCast(this.layoutDoc.y) + 50;
+        newCol.type_collection = CollectionViewType.Carousel as string;
         for (let i = 0; i < collectionArr.length; i++) {
             DocCast(collectionArr[i])[DocData].embedContainer = newCol;
         }
@@ -606,34 +606,17 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 _height: 150,
                 title: '--snapshot' + NumCast(this.layoutDoc._layout_currentTimecode) + ' image-',
             });
-            imageSnapshot['x'] = this.layoutDoc['x'];
-            imageSnapshot['y'] = this.layoutDoc['y'];
+            imageSnapshot.x = this.layoutDoc.x;
+            imageSnapshot.y = this.layoutDoc.y;
             return imageSnapshot;
         } catch (error) {
             console.log(error);
         }
     };
 
-    static imageUrlToBase64 = async (imageUrl: string): Promise<string> => {
-        try {
-            const response = await fetch(imageUrl);
-            const blob = await response.blob();
-
-            return new Promise((resolve, reject) => {
-                const reader = new FileReader();
-                reader.readAsDataURL(blob);
-                reader.onloadend = () => resolve(reader.result as string);
-                reader.onerror = error => reject(error);
-            });
-        } catch (error) {
-            console.error('Error:', error);
-            throw error;
-        }
-    };
-
     getImageDesc = async (u: string) => {
         try {
-            const hrefBase64 = await ComparisonBox.imageUrlToBase64(u);
+            const hrefBase64 = await imageUrlToBase64(u);
             const response = await gptImageLabel(hrefBase64, 'Answer the following question as a short flashcard response. Do not include a label.' + (this.dataDoc.text as RichTextField)?.Text);
 
             DocCast(this.dataDoc[this.props.fieldKey + '_0'])[DocData].text = response;
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 07c0a114a..696ba5697 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -1,5 +1,5 @@
 import { Property } from 'csstype';
-import { action, computed, makeObservable, observable } from 'mobx';
+import { action, computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as textfit from 'textfit';
@@ -22,7 +22,6 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
     private dropDisposer?: DragManager.DragDropDisposer;
     private _timeout: NodeJS.Timeout | undefined;
-    @observable private _editLabel = false;
     _divRef: HTMLDivElement | null = null;
 
     constructor(props: FieldViewProps) {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 037b6315c..c89737e1e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, Selection, TextSelection, Transacti
 import { EditorView, NodeViewConstructor } from 'prosemirror-view';
 import * as React from 'react';
 import { BsMarkdownFill } from 'react-icons/bs';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, returnFalse, returnZero, setupMoveUpEvents, simMouseEvent, smoothScroll, StopEvent } from '../../../../ClientUtils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, imageUrlToBase64, returnFalse, returnZero, setupMoveUpEvents, simMouseEvent, smoothScroll, StopEvent } from '../../../../ClientUtils';
 import { DateField } from '../../../../fields/DateField';
 import { CreateLinkToActiveAudio, Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc';
 import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, DocData, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
@@ -1008,30 +1008,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         // console.log('HI' + this.ProseRef?.getElementsByTagName('img'));
     };
 
-    static imageUrlToBase64 = async (imageUrl: string): Promise<string> => {
-        try {
-            const response = await fetch(imageUrl);
-            const blob = await response.blob();
-
-            return new Promise((resolve, reject) => {
-                const reader = new FileReader();
-                reader.readAsDataURL(blob);
-                reader.onloadend = () => resolve(reader.result as string);
-                reader.onerror = error => reject(error);
-            });
-        } catch (error) {
-            console.error('Error:', error);
-            throw error;
-        }
-    };
-
     getImageDesc = async (u: string) => {
         // if (StrCast(this.dataDoc.description)) return StrCast(this.dataDoc.description); // Return existing description
         // const { href } = (u as URLField).url;
         const hrefParts = u.split('.');
         const hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`;
         try {
-            const hrefBase64 = await FormattedTextBox.imageUrlToBase64(u);
+            const hrefBase64 = await imageUrlToBase64(u);
             const response = await gptImageLabel(
                 hrefBase64,
                 'Make flashcards out of this text and image with each question and answer labeled as question and answer. Do not label each flashcard and do not include asterisks: ' + (this.dataDoc.text as RichTextField)?.Text
diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx
index 75ef55060..b4635673c 100644
--- a/src/client/views/smartdraw/SmartDrawHandler.tsx
+++ b/src/client/views/smartdraw/SmartDrawHandler.tsx
@@ -7,6 +7,7 @@ import React from 'react';
 import { AiOutlineSend } from 'react-icons/ai';
 import ReactLoading from 'react-loading';
 import { INode, parse } from 'svgson';
+import { imageUrlToBase64 } from '../../../ClientUtils';
 import { unimplementedFunction } from '../../../Utils';
 import { Doc, DocListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
@@ -19,7 +20,6 @@ import { undoable } from '../../util/UndoManager';
 import { SVGToBezier, SVGType } from '../../util/bezierFit';
 import { InkingStroke } from '../InkingStroke';
 import { ObservableReactComponent } from '../ObservableReactComponent';
-import { CollectionCardView } from '../collections/CollectionCardDeckView';
 import { MarqueeView } from '../collections/collectionFreeForm';
 import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, DocumentView } from '../nodes/DocumentView';
 import './SmartDrawHandler.scss';
@@ -310,7 +310,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
         const hrefParts = href.split('.');
         const hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`;
         try {
-            const hrefBase64 = await CollectionCardView.imageUrlToBase64(hrefComplete);
+            const hrefBase64 = await imageUrlToBase64(hrefComplete);
             const strokes = DocListCast(drawing[DocData].data);
             const coords: string[] = [];
             strokes.forEach((stroke, i) => {
-- 
cgit v1.2.3-70-g09d2

