From 2b4288efa2f21db46addd19c8884f80dba835f2d Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Tue, 17 Sep 2024 09:20:54 -0400
Subject: pulling from amster wish me luck

---
 .../views/collections/CollectionCardDeckView.tsx   | 343 +++++++++------------
 1 file changed, 146 insertions(+), 197 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 5dafb1088..4bd9a77b5 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -19,15 +19,7 @@ import { DocumentView } from '../nodes/DocumentView';
 import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
 import './CollectionCardDeckView.scss';
 import { CollectionSubView } from './CollectionSubView';
-import { FieldsDropdown } from '../FieldsDropdown';
-import { Button, IconButton } from 'browndash-components';
-import { faStar } from '@fortawesome/free-solid-svg-icons';
-import { FaStar, FaHeart, FaRobot, FaCloud } from 'react-icons/fa';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { SettingsManager } from '../../util/SettingsManager';
-import { Tooltip } from '@mui/material';
 import { dropActionType } from '../../util/DropActionTypes';
-import { List } from '../../../fields/List';
 import { DocCast } from '../../../fields/Types';
 import { SelectionManager } from '../../util/SelectionManager';
 
@@ -56,12 +48,8 @@ export class CollectionCardView extends CollectionSubView() {
     @observable _docDraggedIndex: number = -1;
     @observable _isACardBeingDragged: boolean = false;
     @observable overIndex: number = -1;
-    
-
-    static getButtonGroup(groupFieldKey: 'chat' | 'star' | 'idea' | 'like', doc: Doc): number | undefined {
-        return Cast(doc[groupFieldKey], 'number', null);
-    }
 
+   
     static imageUrlToBase64 = async (imageUrl: string): Promise<string> => {
         try {
             const response = await fetch(imageUrl);
@@ -79,49 +67,47 @@ export class CollectionCardView extends CollectionSubView() {
         }
     };
 
-
     constructor(props: any) {
         super(props);
         makeObservable(this);
         this.setRegenerateCallback();
     }
 
-    setRegenerateCallback() {
+    /**
+     * Callback to ensure gpt's text versions of the child docs are updated
+     */
+    setRegenerateCallback = () => {
         GPTPopup.Instance.setRegenerateCallback(this.childPairStringListAndUpdateSortDesc);
     }
 
+    /**
+     * update's gpt's doc-text list and initializes callbacks
+     */
     @action
     childPairStringListAndUpdateSortDesc = async () => {
         const sortDesc = await this.childPairStringList(); // Await the promise to get the string result
         GPTPopup.Instance.setSortDesc(sortDesc.join());
         GPTPopup.Instance.onSortComplete = (sortResult: string, questionType: string, tag?: string) => this.processGptOutput(sortResult, questionType, tag);
-        GPTPopup.Instance.onQuizRandom = () => this.quizMode()
-
+        GPTPopup.Instance.onQuizRandom = () => this.quizMode();
     };
 
     componentDidMount(): void {
-
-        this.Document.childFilters_boolean = 'OR'
+        this.Document.childFilters_boolean = 'OR';
         this.childDocsWithoutLinks.forEach(c => {
-            c[DocData].showIconTags = true
-            
+            c[DocData].showIconTags = true;
         });
 
-        
         // Reaction to cardSort changes
         this._disposers.sort = reaction(
             () => this.cardSort,
-            (cardSort) => {
+            cardSort => {
                 if (cardSort === cardSortings.Chat) {
                     this.openChatPopup();
                 } else {
                     GPTPopup.Instance.setVisible(false);
                 }
-                // Force re-render to see if this resolves the sorting issue
             }
         );
-
-       
     }
 
     componentWillUnmount() {
@@ -134,10 +120,9 @@ export class CollectionCardView extends CollectionSubView() {
     }
 
     @computed get cardSort() {
-        // console.log(StrCast(this.Document.cardSort) as any as cardSortings)
         return StrCast(this.Document.cardSort) as any as cardSortings;
     }
-    
+
     /**
      * how much to scale down the contents of the view so that everything will fit
      */
@@ -156,32 +141,32 @@ export class CollectionCardView extends CollectionSubView() {
 
         if (activeGroups.length > 0) {
             return regularDocs.filter(doc => {
-                const activeTags = StrListCast(doc.cardSort_activeIcons)
+                const activeTags = StrListCast(doc.cardSort_activeIcons);
                 return activeTags !== undefined && activeTags.some(tag => activeGroups.includes(tag));
-            })
+            });
         }
 
         // Default return for non-custom cardSort or other cases, filtering out links
         return regularDocs;
     }
 
-    quizMode () {
-
-        const randomIndex = Math.floor(Math.random() * this.childDocs.length)
-        console.log('hiiiiiii')
-
-        SelectionManager.DeselectAll()
-        DocumentView.SelectView(DocumentView.getDocumentView(this.childDocs[randomIndex]), false)
-
+    /**
+     * When in quiz mode, randomly selects a document 
+     */
+    quizMode = () => {
+        const randomIndex = Math.floor(Math.random() * this.childDocs.length);
+        SelectionManager.DeselectAll();
+        DocumentView.SelectView(DocumentView.getDocumentView(this.childDocs[randomIndex]), false);
     }
 
-   
-
     /**
      * Number of rows of cards to be rendered
      */
-    @computed get numRows() { return Math.ceil(this.sortedDocs.length / 10); }
+    @computed get numRows() {
+        return Math.ceil(this.sortedDocs.length / 10);
+    }
 
+    
     @action
     setHoveredNodeIndex = (index: number) => {
         if (!DocumentView.SelectedDocs().includes(this.childDocs[index])) {
@@ -251,87 +236,98 @@ export class CollectionCardView extends CollectionSubView() {
         return Math.abs(stepMag * (apex - index - 1)) - rowOffset;
     };
 
-     findCardDropIndex = (mouseX: number, mouseY: number, direction: 'left' | 'right') => {
-
-        const amCardsTotal = this.sortedDocs.length
+    /**
+     * When dragging a card, determines the index the card should be set to if dropped
+     * @param mouseX mouse's x location
+     * @param mouseY mouses' y location
+     * @returns the card's new index
+     */
+    findCardDropIndex = (mouseX: number, mouseY: number) => {
+        const amCardsTotal = this.sortedDocs.length;
         let index = 0;
-        const cardWidth = amCardsTotal < this._maxRowCount ? this._props.PanelWidth() / amCardsTotal : this._props.PanelWidth() /  this._maxRowCount;
-    
+        const cardWidth = amCardsTotal < this._maxRowCount ? this._props.PanelWidth() / amCardsTotal : this._props.PanelWidth() / this._maxRowCount;
+
         // Calculate the adjusted X position accounting for the initial offset
-        let adjustedX = mouseX
+        let adjustedX = mouseX;
 
         const amRows = Math.ceil(amCardsTotal / this._maxRowCount);
-        const rowHeight = this._props.PanelHeight( ) / amRows
-        const currRow = Math.floor((mouseY - 100) / rowHeight) //rows start at 0
+        const rowHeight = this._props.PanelHeight() / amRows;
+        const currRow = Math.floor((mouseY - 100) / rowHeight); //rows start at 0
 
         if (adjustedX < 0) {
             return 0; // Before the first column
         }
 
-        if (amCardsTotal < this._maxRowCount)   { 
+        if (amCardsTotal < this._maxRowCount) {
             index = Math.floor(adjustedX / cardWidth);
-        }
-
-        else if (currRow != amRows -1 ){
-            index = Math.floor(adjustedX / cardWidth) + (currRow * this._maxRowCount)
-        }
-
-        else {
-            // console.log(amRows + "am rows")
-            const rowAmCards = amCardsTotal - (currRow * this._maxRowCount)
-            const offset = ((this._maxRowCount - rowAmCards ) / 2) * cardWidth
-            adjustedX = mouseX - offset
-
-            index = Math.floor(adjustedX / cardWidth) + (currRow * this._maxRowCount);
+        } else if (currRow != amRows - 1) {
+            index = Math.floor(adjustedX / cardWidth) + currRow * this._maxRowCount;
+        } else {
+            const rowAmCards = amCardsTotal - currRow * this._maxRowCount;
+            const offset = ((this._maxRowCount - rowAmCards) / 2) * cardWidth;
+            adjustedX = mouseX - offset;
+
+            index = Math.floor(adjustedX / cardWidth) + currRow * this._maxRowCount;
         }
         return index;
     };
 
+    /**
+     * Checks to see if a card is being dragged and calls the appropriate methods if so
+     * @param e the current pointer event
+     */
+
     @action
     onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
-        if (DragManager.docsBeingDragged.length != 0 ) {
-            this._isACardBeingDragged = true
-            
-            const direction = e.movementX > 0 ? 'right' : 'left';
-            const newIndex = this.findCardDropIndex(e.clientX, e.clientY, direction);
-            
+        if (DragManager.docsBeingDragged.length != 0) {
+            this._isACardBeingDragged = true;
+
+            const newIndex = this.findCardDropIndex(e.clientX, e.clientY);
+
             if (newIndex !== this._docDraggedIndex && newIndex != -1) {
                 this._docDraggedIndex = newIndex;
-                }
+            }
         }
     };
 
-
+    /**
+     * Resets all the doc dragging vairables once a card is dropped
+     * @param e 
+     * @param de drop event
+     * @returns true if a card has been dropped, falls if not
+     */
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
         if (de.complete.docDragData) {
             this._isACardBeingDragged = false;
             this._docDraggedIndex = -1;
-            e.stopPropagation()
-            const draggedDocs = de.complete.docDragData?.draggedDocuments;
+            e.stopPropagation();
             return true;
         }
         return false;
     };
 
-
     get sortedDocs() {
-        // console.log("hi hi hi")
-        // console.log(this.layoutDoc.cardSort_isDesc + "layoutdoc desc")
-        // console.log(this.cardSort + "card sort")
-  
         return this.sort(this.childDocsWithoutLinks, this.cardSort, BoolCast(this.Document.cardSort_isDesc), this._docDraggedIndex);
     }
 
-    tagValue(doc: Doc) {
+
+    /**
+     * Used to determine how to sort cards based on tags. The lestmost 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
+     */
+
+    tagValue = (doc: Doc) => {
         const keys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
-    
+
         const isTagActive = (buttonID: number) => {
             return BoolCast(doc[StrCast(Doc.UserDoc()[keys[buttonID]])]);
         };
-    
+
         let base = '';
         let fraction = '';
-    
+
         for (let i = 0; i < keys.length; i++) {
             if (isTagActive(i)) {
                 if (base === '') {
@@ -341,20 +337,18 @@ export class CollectionCardView extends CollectionSubView() {
                 }
             }
         }
-    
+
         // If no tag was active, return 0 by default
         if (base === '') {
             return 0;
         }
-    
+
         // Construct the final number by appending the fraction if it exists
         const numberString = fraction ? `${base}.${fraction}` : base;
-    
+
         // Convert the result to a number and return
         return Number(numberString);
     }
-    
-
 
     /**
      * Called in the sortedDocsType method. Compares the cards' value in regards to the desired sort type-- earlier cards are move to the
@@ -365,66 +359,48 @@ export class CollectionCardView extends CollectionSubView() {
      * @returns
      */
     @action sort = (docs: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => {
-        // console.log('hiiii')
-        // if (sortType === cardSortings.None) return docs;
-        
-        // if(sortType !== cardSortings.None){
-            docs.sort((docA, docB) => {
-            
-                const [typeA, typeB] = (() => {
+        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: 
-                        return [ClientUtils.hexToHsv(StrCast(docA.backgroundColor)),
-                                ClientUtils.hexToHsv(StrCast(docB.backgroundColor))];
+                    case cardSortings.Time:
+                        return [DateCast(docA.author_date)?.date ?? Date.now(), DateCast(docB.author_date)?.date ?? Date.now()];
+                    case cardSortings.Color:
+                        return [ClientUtils.hexToHsv(StrCast(docA.backgroundColor)), ClientUtils.hexToHsv(StrCast(docB.backgroundColor))];
                     case cardSortings.Tag:
-                        return [this.tagValue(docA) ?? 9999,
-                                this.tagValue(docB) ?? 9999];
+                        return [this.tagValue(docA) ?? 9999, this.tagValue(docB) ?? 9999];
                     case cardSortings.Chat:
-                        return [NumCast(docA.chatIndex) ?? 9999,
-                                NumCast(docB.chatIndex) ?? 9999]
-                    
+                        return [NumCast(docA.chatIndex) ?? 9999, NumCast(docB.chatIndex) ?? 9999];
 
                     default:
-                        return [StrCast(docA.type), StrCast(docB.type)]
+                        return [StrCast(docA.type), StrCast(docB.type)];
                 }
             })();
-    
-            // console.log(`Sorting ${sortType}: ${typeA} vs ${typeB}`);
+
             const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0;
-            // console.log(`Comparison result: ${out} (isDesc: ${isDesc})`);
 
-            if (isDesc){
-                return out
+            if (isDesc) {
+                return out;
             }
 
-            return -out
-    
+            return -out;
         });
-    // }
-    
         if (dragIndex != -1) {
             const draggedDoc = DragManager.docsBeingDragged[0];
             const originalIndex = docs.findIndex(doc => doc === draggedDoc);
-    
+
             docs.splice(originalIndex, 1);
             docs.splice(dragIndex, 0, draggedDoc);
         }
-    
+
         return docs;
     };
-    
-
-
 
     displayDoc = (doc: Doc, screenToLocalTransform: () => Transform) => (
         <DocumentView
             // eslint-disable-next-line react/jsx-props-no-spreading
             {...this._props}
             ref={action((r: DocumentView) => r?.ContentDiv && this._docRefs.set(doc, r))}
-            Document={doc} 
+            Document={doc}
             NativeWidth={returnZero}
             NativeHeight={returnZero}
             fitWidth={returnFalse}
@@ -439,7 +415,6 @@ export class CollectionCardView extends CollectionSubView() {
             PanelHeight={this.panelHeight(doc)}
             dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType}
             dontHideOnDrag
-            // pointerEvents={this.blockPointerEventsWhenDragging(doc)}
         />
     );
 
@@ -486,17 +461,16 @@ export class CollectionCardView extends CollectionSubView() {
      * @returns
      */
     calculateTranslateY = (isHovered: boolean, isSelected: 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;
+        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 (amCards == 1) return 50 * this.fitContentScale;
         // const trans = isHovered ? this.translateHover(realIndex) : 0;
         const trans = 0;
         return trans + this.translateY(amCards, calcRowIndex, realIndex);
     };
 
-  
     /**
      * A list of the text content of all the child docs. RTF documents will have just their text and pdf documents will have the first 50 words.
      * Image documents are converted to bse64 and gpt generates a description for them. all other documents use their title. This string is
@@ -514,7 +488,7 @@ export class CollectionCardView extends CollectionSubView() {
         };
         const docTextPromises = this.childDocsWithoutLinks.map(async doc => {
             const docText = (await docToText(doc)) ?? '';
-            doc['gptInputText'] = docText
+            doc['gptInputText'] = docText;
             this._textToDoc.set(docText.replace(/\n/g, ' ').trim(), doc);
             return `======${docText.replace(/\n/g, ' ').trim()}======`;
         });
@@ -545,108 +519,85 @@ export class CollectionCardView extends CollectionSubView() {
     };
 
     /**
-     * Converts the gpt output into a hashmap that can be used for sorting. lists are seperated by ==== while elements within the list are seperated by ~~~~~~
+     * Processes gpt's output depending on the type of question the user asked. Converts gpt's string output to 
+     * usable code
      * @param gptOutput
      */
     @action processGptOutput = (gptOutput: string, questionType: string, tag?: string) => {
-
-        console.log("HIIII")
-        console.log(StrCast(this.Document.cardSort) + "cardSort")
+        console.log('HIIII');
+        console.log(StrCast(this.Document.cardSort) + 'cardSort');
         // Split the string into individual list items
         const listItems = gptOutput.split('======').filter(item => item.trim() !== '');
 
-
-        if (questionType == '2' || questionType == '4'){
+        if (questionType == '2' || questionType == '4') {
             this.childDocs.forEach(d => {
-                d['chatFilter'] = false
-            })
+                d['chatFilter'] = false;
+            });
         }
-        // console.log(listItems + " LISTT");
-    
-        // Debug: print the map contents
-        // console.log("Map contents:", Array.from(this._textToDoc.entries()));
-    
+
+
         listItems.forEach((item, index) => {
-            // Normalize the item (trim whitespace)
             const normalizedItem = item.trim();
-            // console.log("Normalized item:", normalizedItem);
-    
-            // Find the corresponding Doc in the textToDoc map
+            // find the corresponding Doc in the textToDoc map
             const doc = this._textToDoc.get(normalizedItem);
-            // console.log("DOC:", doc);
-            // console.log("ITEM:", normalizedItem);
-    
+
             if (doc) {
-                switch (questionType){
-                    case '6': 
-                        doc.chatIndex = index
-                        console.log(index)
-                        break
+                switch (questionType) {
+                    case '6':
+                        doc.chatIndex = index;
+                        console.log(index);
+                        break;
                     case '1':
-                        const allHotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles)
-
-                        let myTag = ''
-
-                        if (tag){
-                            for (let i=0; i< allHotKeys.length; i++){
-                                if (tag.includes(allHotKeys[i])){
-                                    myTag = StrCast(Doc.UserDoc()[allHotKeys[i]])
-                                    break
-                                }
-
-                                else if (tag.includes(StrCast(Doc.UserDoc()[allHotKeys[i]]))){
-                                    myTag =  StrCast(Doc.UserDoc()[allHotKeys[i]])
-                                    break
+                        const allHotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
+
+                        let myTag = '';
+
+                        if (tag) {
+                            for (let i = 0; i < allHotKeys.length; i++) {
+                                if (tag.includes(allHotKeys[i])) {
+                                    myTag = StrCast(Doc.UserDoc()[allHotKeys[i]]);
+                                    break;
+                                } else if (tag.includes(StrCast(Doc.UserDoc()[allHotKeys[i]]))) {
+                                    myTag = StrCast(Doc.UserDoc()[allHotKeys[i]]);
+                                    break;
                                 }
                             }
 
-                            if (myTag != ''){
-                                doc[myTag] = true
-                            }         
+                            if (myTag != '') {
+                                doc[myTag] = true;
+                            }
                         }
-                        break
+                        break;
                     case '2':
                     case '4':
-                        doc['chatFilter'] = true
+                        doc['chatFilter'] = true;
                         Doc.setDocFilter(DocCast(this.Document.embedContainer), 'chatFilter', true, 'match');
-                        break
-
-
-                
+                        break;
                 }
             } else {
                 console.warn(`No matching document found for item: ${normalizedItem}`);
             }
-
         });
+    };
+
 
-    }
-    
-        // );
-    // };
     /**
      * Opens up the chat popup and starts the process for smart sorting.
      */
     openChatPopup = async () => {
         GPTPopup.Instance.setVisible(true);
         GPTPopup.Instance.setMode(GPTPopupMode.CARD);
-        const sortDesc = await this.childPairStringList(); // Await the promise to get the string result
         GPTPopup.Instance.setCardsDoneLoading(true); // Set dataDoneLoading to true after data is loaded
-        GPTPopup.Instance.setSortDesc(sortDesc.join());
-        GPTPopup.Instance.onSortComplete = (sortResult: string, questionType: string, tag?: string) => this.processGptOutput(sortResult, questionType, tag);
-        GPTPopup.Instance.onQuizRandom = () => this.quizMode()
+        await this.childPairStringListAndUpdateSortDesc()
     };
 
     /**
      * Actually renders all the cards
      */
     renderCards = () => {
-        // console.log('christ')
         const anySelected = this.childDocs.some(doc => DocumentView.SelectedDocs().includes(doc));
         const isEmpty = this.childDocsWithoutLinks.length === 0;
-        const isDesc = BoolCast(this.Document.cardSort_isDesc)
-
-        // console.log(this.childDocsWithoutLinks.length + "length")
+        const isDesc = BoolCast(this.Document.cardSort_isDesc);
 
         if (isEmpty) {
             return (
@@ -676,9 +627,9 @@ export class CollectionCardView extends CollectionSubView() {
             const translateIfSelected = () => {
                 const indexInRow = index % this._maxRowCount;
                 const rowIndex = Math.trunc(index / this._maxRowCount);
-                const rowCenterIndex =  Math.min(this._maxRowCount, this.sortedDocs.length - rowIndex * this._maxRowCount)/2;
+                const rowCenterIndex = Math.min(this._maxRowCount, this.sortedDocs.length - rowIndex * this._maxRowCount) / 2;
                 return (rowCenterIndex - indexInRow) * 100 - 50;
-            }
+            };
             return (
                 <div
                     key={doc[Id]}
@@ -698,13 +649,12 @@ export class CollectionCardView extends CollectionSubView() {
                         width: this.panelWidth(),
                         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"}))
+                                    translateX(calc(${(isSelected ? translateIfSelected() : 0) + '% + ' + this.translateOverflowX(realIndex, amCards) + 'px'}))
                                     rotate(${!isSelected ? this.rotate(amCards, calcRowIndex) : 0}deg)
                                     scale(${isSelected ? 2 : this._hoveredNodeIndex === index ? 1.05 : 1})`,
-                                }}
+                    }}
                     onMouseEnter={() => this.setHoveredNodeIndex(index)}>
                     {this.displayDoc(doc, childScreenToLocal)}
-                    {/* {this.renderButtons(doc, this.cardSort)} */}
                 </div>
             );
         });
@@ -714,7 +664,7 @@ export class CollectionCardView extends CollectionSubView() {
         const isEmpty = this.childDocsWithoutLinks.length === 0;
         const transformValue = `scale(${1 / this.fitContentScale})`;
         const heightValue = `${100 * this.fitContentScale}%`;
-    
+
         return (
             <div
                 onPointerMove={e => this.onPointerMove(e)}
@@ -729,7 +679,7 @@ export class CollectionCardView extends CollectionSubView() {
                     style={{
                         ...(!isEmpty && { transform: transformValue }),
                         ...(!isEmpty && { height: heightValue }),
-                        gridAutoRows: `${100 / this.numRows}%`
+                        gridAutoRows: `${100 / this.numRows}%`,
                     }}
                     onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
                     {this.renderCards()}
@@ -737,5 +687,4 @@ export class CollectionCardView extends CollectionSubView() {
             </div>
         );
     }
-    
 }
-- 
cgit v1.2.3-70-g09d2

