From 6bfa2fd696a306a984b29c6ea11fa66e93e0474c Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Tue, 4 Jun 2024 12:48:41 -0400
Subject: changes

---
 .../views/collections/CollectionCardDeckView.tsx    | 21 +++++++++++++++------
 1 file changed, 15 insertions(+), 6 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 de46180e6..0ca3c5a44 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -170,6 +170,8 @@ export class CollectionCardView extends CollectionSubView() {
      * @returns
      */
     rotate = (amCards: number, index: number) => {
+        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)));
 
@@ -242,16 +244,20 @@ export class CollectionCardView extends CollectionSubView() {
                                 return   [DateCast(docA.author_date)?.date ?? Date.now(),
                                           DateCast(docB.author_date)?.date ?? Date.now()];
                     case cardSortings.Color: 
-                                  return [DashColor(StrCast(docA.backgroundColor)).hsv().toString(), // If docA.type is undefined, use an empty string
-                                          DashColor(StrCast(docB.backgroundColor)).hsv().toString()]; // If docB.type is undefined, use an empty string
+                                return [ClientUtils.hexToHsv(StrCast(docA.backgroundColor)) ?? 9999, // If docA.type is undefined, use an empty string
+                                        ClientUtils.hexToHsv(StrCast(docB.backgroundColor))] ?? 9999; // If docB.type is undefined, use an empty string
                     case cardSortings.Custom:
-                                  return [CollectionCardView.getButtonGroup(this.cardSort_customField, docA)??0,
-                                          CollectionCardView.getButtonGroup(this.cardSort_customField, docB)??0];
+                                  return [CollectionCardView.getButtonGroup(this.cardSort_customField, docA)??9999,
+                                          CollectionCardView.getButtonGroup(this.cardSort_customField, docB)??9999];
                     default:      return [StrCast(docA.type), // If docA.type is undefined, use an empty string
                                           StrCast(docB.type)]; // If docB.type is undefined, use an empty string
                     } // prettier-ignore
             })();
 
+            console.log(ClientUtils.hexToHsv(StrCast(docA.backgroundColor)) ?? 9999 + "docA col")
+            console.log(ClientUtils.hexToHsv(StrCast(docB.backgroundColor)) ?? 9999 + "docB col")
+
+
             const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0;
             return isDesc ? -out : out; // Reverse the sort order if descending is true
         });
@@ -323,7 +329,7 @@ export class CollectionCardView extends CollectionSubView() {
      * @returns
      */
     calculateTranslateY = (isHovered: boolean, isSelected: boolean, realIndex: number, amCards: number, calcRowIndex: number) => {
-        if (isSelected) return 50 * this.fitContentScale;
+        if (isSelected || amCards == 1) return 50 * this.fitContentScale;
         const trans = isHovered ? this.translateHover(realIndex) : 0;
         return trans + this.translateY(amCards, calcRowIndex, realIndex);
     };
@@ -472,7 +478,7 @@ export class CollectionCardView extends CollectionSubView() {
                                 SnappingManager.SetIsResizing(undefined);
                                 this._forceChildXf = !this._forceChildXf;
                             }),
-                            700
+                            900
                         );
                     }}
                     style={{
@@ -490,6 +496,9 @@ export class CollectionCardView extends CollectionSubView() {
             );
         });
     };
+
+
+
     render() {
         return (
             <div
-- 
cgit v1.2.3-70-g09d2


From d3d2abbb011fe9c2a7d14b2a4b3016d55033aae8 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Thu, 6 Jun 2024 11:33:14 -0400
Subject: hm

---
 src/client/util/CurrentUserUtils.ts                | 51 ++++++++++++++++------
 .../views/collections/CollectionCardDeckView.scss  |  2 +-
 .../views/collections/CollectionCardDeckView.tsx   |  4 +-
 src/client/views/global/globalScripts.ts           | 42 +++++++++---------
 4 files changed, 63 insertions(+), 36 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 0d241650a..f2ef9b943 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -660,21 +660,32 @@ pie title Minerals in my tap water
     }
     static cardTools(): Button[] {
         return [
-            { title: "Time",      icon:"hourglass-half", toolTip:"Sort by most recent document creation",      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"time",   funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Type",      icon:"eye",            toolTip:"Sort by document type",                      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"docType",funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Color",     icon:"palette",        toolTip:"Sort by document color",                     btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { btnList: new List<string>(["Time", "Type", "Color", "ChatGPT", "Custom 1", "Custom 2", "Custom 3" ]),
+                title: "Sort Type",  toolTip: "Card Sort Type",   btnType: ButtonType.DropdownList, ignoreClick: true, width: 150, scripts: { script:  '{ return showFreeform(value, _readOnly_); }'}},
+                // { title: "Font",     toolTip: "Font", width: 100,     btnType: ButtonType.DropdownList,                      toolType:"font",     ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'},
+            //   btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
+            // { title: "Time",      icon:"hourglass-half", toolTip:"Sort by most recent document creation",      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"time",   funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            // { title: "Type",      icon:"eye",            toolTip:"Sort by document type",                      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"docType",funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            // { title: "Color",     icon:"palette",        toolTip:"Sort by document color",                     btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "AIs",   icon:"AI Sort",   toolTip:"Filter AI labels",        subMenu: this.cardGroupTools("robot"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("chat", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
+            // { title: "Custom",  icon:"heart",     toolTip:"Add Like labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"like",                  funcs: {hidden:`showFreeform ("like", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "1st", icon:"1st",     toolTip:"Filter likes", width: 10, subMenu: this.cardGroupTools("heart"),     expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("like", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
+            // { title: "Custom",  icon:"star",      toolTip:"Add Star labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"star",                  funcs: {hidden:`showFreeform ("star", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "2nd", icon:"2nd",     toolTip:"Filter stars", width: 80, subMenu: this.cardGroupTools("star"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("star", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, 
+            // { title: "Custom",  icon:"cloud", toolTip:"Add Idea labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"idea",                  funcs: {hidden:`showFreeform ("idea", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "3rd", icon:"3rd",     toolTip:"Filter ideas", width: 80, subMenu: this.cardGroupTools("cloud"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("idea", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
          ]
     }
     static labelTools(): Button[] {
         return [
-            { title: "Smart",    icon:"robot",     toolTip:"Have ChatGPT Label and sort your cards for you!",           btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"chat",                  funcs: {hidden:`showFreeform ("chat", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            // { title: "Smart",    icon:"robot",     toolTip:"Have ChatGPT Label and sort your cards for you!",           btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"chat",                  funcs: {hidden:`showFreeform ("chat", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "AIs",   icon:"AI Sort",   toolTip:"Filter AI labels",        subMenu: this.cardGroupTools("robot"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("chat", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
-            { title: "Like",  icon:"heart",     toolTip:"Add Like labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"like",                  funcs: {hidden:`showFreeform ("like", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Likes", icon:"Likes",     toolTip:"Filter likes", width: 10, subMenu: this.cardGroupTools("heart"),     expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("like", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
-            { title: "Star",  icon:"star",      toolTip:"Add Star labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"star",                  funcs: {hidden:`showFreeform ("star", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Stars", icon:"Stars",     toolTip:"Filter stars", width: 80, subMenu: this.cardGroupTools("star"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("star", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, 
-            { title: "Idea",  icon:"cloud", toolTip:"Add Idea labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"idea",                  funcs: {hidden:`showFreeform ("idea", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Ideas", icon:"Ideas",     toolTip:"Filter ideas", width: 80, subMenu: this.cardGroupTools("cloud"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("idea", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
+            // { title: "Custom",  icon:"heart",     toolTip:"Add Like labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"like",                  funcs: {hidden:`showFreeform ("like", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "1st", icon:"1st",     toolTip:"Filter likes", width: 10, subMenu: this.cardGroupTools("heart"),     expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("like", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
+            // { title: "Custom",  icon:"star",      toolTip:"Add Star labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"star",                  funcs: {hidden:`showFreeform ("star", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "2nd", icon:"2nd",     toolTip:"Filter stars", width: 80, subMenu: this.cardGroupTools("star"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("star", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, 
+            // { title: "Custom",  icon:"cloud", toolTip:"Add Idea labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"idea",                  funcs: {hidden:`showFreeform ("idea", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "3rd", icon:"3rd",     toolTip:"Filter ideas", width: 80, subMenu: this.cardGroupTools("cloud"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("idea", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
         ]
     }
     static cardGroupTools(icon: string): Button[] {
@@ -794,8 +805,22 @@ pie title Minerals in my tap water
             { title: "Doc",     icon: "Doc",           toolTip: "Freeform Doc tools",   subMenu: CurrentUserUtils.freeTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode, true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "View",    icon: "View",          toolTip: "View tools",           subMenu: CurrentUserUtils.viewTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Stack",   icon: "View",          toolTip: "Stacking tools",       subMenu: CurrentUserUtils.stackTools(),  expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
-            { title: "Card",    icon: "Sort",          toolTip: "Card sort",            subMenu: CurrentUserUtils.cardTools(),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
-            { title: "Label",   icon: "Label",         toolTip: "Assign card labels",   subMenu: CurrentUserUtils.labelTools(),  expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+            
+            
+            // { title: "SortType", btnList: new List<string>(["Time", "Type", "Color", "ChatGPT", "Custom 1", "Custom 2", "Custom 3" ]),
+            //       toolTip: "Card Sort Type",   btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script:  '{ return showFreeform(value, _readOnly_); }'  },  
+            //     //   funcs: {hidden: `!SelectedDocType("card", this.expertMode)`}
+            //     },
+            // { title: "Visibility",   icon:"Visibility",   toolTip:"Filter AI labels",        subMenu: this.cardGroupTools("robot"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("chat", true)`, width: 100, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
+            // // { title: "Custom",  icon:"heart",     toolTip:"Add Like labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"like",                  funcs: {hidden:`showFreeform ("like", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            // { title: "Visibility", icon:"Visibility",     toolTip:"Filter likes", width: 10, subMenu: this.cardGroupTools("heart"),     expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("like", true)`, width: 100, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
+            // // { title: "Custom",  icon:"star",      toolTip:"Add Star labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"star",                  funcs: {hidden:`showFreeform ("star", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            // { title: "Visibility", icon:"Visibility",     toolTip:"Filter stars", width: 80, subMenu: this.cardGroupTools("star"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("star", true)`, width: 100, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, 
+            // // { title: "Custom",  icon:"cloud", toolTip:"Add Idea labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"idea",                  funcs: {hidden:`showFreeform ("idea", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            // { title: "Visibility", icon:"Visibility",     toolTip:"Filter ideas", width: 80, subMenu: this.cardGroupTools("cloud"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("idea", true)`,width: 100, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
+            
+            { title: "Card",    icon: "Card",          toolTip: "Card View Tools",            subMenu: CurrentUserUtils.cardTools(),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+            // { title: "Create",   icon: "Create",         toolTip: "Assign card labels",   subMenu: CurrentUserUtils.labelTools(),  expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
             { title: "Video",   icon: "Video",         toolTip: "Video functions",      subMenu: CurrentUserUtils.videoTools(),  expertMode: false, toolType:DocumentType.VID,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected
             { title: "Image",   icon: "Image",         toolTip: "Image functions",      subMenu: CurrentUserUtils.imageTools(),  expertMode: false, toolType:DocumentType.IMG,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected
@@ -1109,4 +1134,4 @@ ScriptingGlobals.add(function importDocument()  { return CurrentUserUtils.import
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; });
 // eslint-disable-next-line prefer-arrow-callback
-ScriptingGlobals.add(function getSharingDoc()   { return Doc.SharingDoc() });
+ScriptingGlobals.add(function getSharingDoc()   { return Doc.SharingDoc() });
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
index a089b248d..8a721a626 100644
--- a/src/client/views/collections/CollectionCardDeckView.scss
+++ b/src/client/views/collections/CollectionCardDeckView.scss
@@ -28,7 +28,7 @@
     // width: 300px;
     background-color: rgb(218, 218, 218); /* Background color of the container */
     border-radius: 50px; /* Rounds the corners of the container */
-    transform: translateY(75px);
+    transform: translateY(25px);
     // box-shadow: 0 4px 8px rgba(0,0,0,0.1); /* Optional: Adds shadow for depth */
     align-items: center; /* Centers buttons vertically */
     justify-content: start; /* Centers buttons horizontally */
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 0ca3c5a44..127c4dc48 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -254,8 +254,8 @@ export class CollectionCardView extends CollectionSubView() {
                     } // prettier-ignore
             })();
 
-            console.log(ClientUtils.hexToHsv(StrCast(docA.backgroundColor)) ?? 9999 + "docA col")
-            console.log(ClientUtils.hexToHsv(StrCast(docB.backgroundColor)) ?? 9999 + "docB col")
+            // console.log(ClientUtils.hexToHsv(StrCast(docA.backgroundColor)) ?? 9999 + "docA col")
+            // console.log(ClientUtils.hexToHsv(StrCast(docB.backgroundColor)) ?? 9999 + "docB col")
 
 
             const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0;
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 7730ed385..107e21e08 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -135,10 +135,10 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
 });
 
 // eslint-disable-next-line prefer-arrow-callback
-ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce', checkResult?: boolean, persist?: boolean) {
+ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce', checkResult?: boolean, persist?: boolean, ) {
     const selected = DocumentView.SelectedDocs().lastElement();
     // prettier-ignore
-    const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'like' | 'star' | 'idea' | 'chat' | '1' | '2' | '3' | '4', 
+    const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | "Time" | "Type"| "Color"| "ChatGPT"| "Custom 1"| "Custom 2"| "Custom 3" , 
     { 
         waitForRender?: boolean; 
         checkResult: (doc: Doc) => any; 
@@ -173,48 +173,49 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
             checkResult: (doc: Doc) => BoolCast(Doc.UserDoc().defaultToFlashcards, false),
             setDoc: (doc: Doc, dv: DocumentView) => Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards,
         }],
-        ['time', {
-            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "time",
+        ['Time', {
+            checkResult: (doc: Doc) => {StrCast(doc?.cardSort);
+            console.log(StrCast(doc?.cardSort + "card sort"))},
             setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "time",
         }],
-        ['docType', {
-            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "type",
+        ['Type', {
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort),
             setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "type",
         }],
-        ['color', {
-            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "color",
+        ['Color', {
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort),
             setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "color",
         }],
-        ['links', {
-            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "links",
-            setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "links",
-        }],
-        ['like', {
-            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && StrCast(doc?.cardSort_customField) === "like",
+        // ['links', {
+        //     checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "links",
+        //     setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "links",
+        // }],
+        ['Custom 1', {
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort) + " 1",
             setDoc: (doc: Doc, dv: DocumentView) => {
                 doc.cardSort = "custom";
                 doc.cardSort_customField = "like";
                 doc.cardSort_visibleSortGroups = new List<number>();
             }
         }],
-        ['star', {
-            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && StrCast(doc?.cardSort_customField) === "star",
+        ['Custom 2', {
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort) + " 2",
             setDoc: (doc: Doc, dv: DocumentView) => {
                 doc.cardSort = "custom";
                 doc.cardSort_customField = "star";
                 doc.cardSort_visibleSortGroups = new List<number>();
             }
         }],
-        ['idea', {
-            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && StrCast(doc?.cardSort_customField) === "idea",
+        ['Custom 3', {
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort) + " 3",
             setDoc: (doc: Doc, dv: DocumentView) => {
                 doc.cardSort = "custom";
                 doc.cardSort_customField = "idea";
                 doc.cardSort_visibleSortGroups = new List<number>();
             }
         }],
-        ['chat', {
-            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom"  && StrCast(doc?.cardSort_customField) === "chat",
+        ['ChatGPT', {
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort_customField),
             setDoc: (doc: Doc, dv: DocumentView) => {
                 doc.cardSort = "custom";
                 doc.cardSort_customField = "chat";
@@ -233,6 +234,7 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
     }
 
     if (checkResult) {
+        console.log("HIIIIII")
         return map.get(attr)?.checkResult(selected);
     }
     const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
-- 
cgit v1.2.3-70-g09d2


From cca4b5bbc32ccfaafbaba9306545eaddc6953954 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Mon, 10 Jun 2024 12:32:20 -0400
Subject: hm

---
 src/client/documents/Documents.ts                       |  1 +
 src/client/util/CurrentUserUtils.ts                     |  2 +-
 src/client/views/collections/CollectionCardDeckView.tsx | 13 +++++++++++++
 src/client/views/global/globalScripts.ts                |  6 ++++--
 src/client/views/pdf/PDFViewer.tsx                      |  2 +-
 5 files changed, 20 insertions(+), 4 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a67e6b4f6..34407c641 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -479,6 +479,7 @@ export class DocumentOptions {
     userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)');
 
     cardSort?: STRt = new StrInfo('way cards are sorted in deck view');
+    cardSortForDropDown?: STRt = new StrInfo('needed for dropdown and i dont know why')
     cardSort_customField?: STRt = new StrInfo('field key used for sorting cards');
     cardSort_visibleSortGroups?: List<number>; // which sorting values are being filtered (shown)
 }
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 438867eeb..2ca2ce9c9 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -661,7 +661,7 @@ pie title Minerals in my tap water
     static cardTools(): Button[] {
         return [
             { btnList: new List<string>(["Time", "Type", "Color", "Chat GPT", "Custom 1", "Custom 2", "Custom 3" ]),
-                title: "Sort Type",  toolTip: "Card Sort Type",   btnType: ButtonType.DropdownList, ignoreClick: true, width: 150, scripts: { script:  '{ return setCardSort(value, _readOnly_); }'}},
+                title: "Sort Type",  toolTip: "Card Sort Type",   btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script:  '{ return setCardSort(value, _readOnly_); }'}},
             // { title: "Font",     toolTip: "Font", width: 100,     btnType: ButtonType.DropdownList,                      toolType:"font",     ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'},
             //   btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
             // { title: "Time",      icon:"hourglass-half", toolTip:"Sort by most recent document creation",      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"time",   funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 127c4dc48..d68fb57f8 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -19,6 +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';
 
 enum cardSortings {
     Time = 'time',
@@ -384,6 +385,8 @@ export class CollectionCardView extends CollectionSubView() {
             return response; // Return the response from gptImageLabel
         } catch (error) {
             console.log('bad things have happened');
+
+            console.log(error)
         }
         return '';
     };
@@ -517,6 +520,16 @@ export class CollectionCardView extends CollectionSubView() {
                     onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
                     {this.renderCards()}
                 </div>
+                <div className="collectionNotetaking-pivotField" style={{ right: 0, top: 0, position: 'absolute' }}>
+                    <FieldsDropdown
+                        Document={this.Document}
+                        selectFunc={undoable(fieldKey => {
+                            this.layoutDoc._pivotField = fieldKey;
+                            // this.removeEmptyColumns();
+                        }, 'change pivot field')}
+                        placeholder={StrCast(this.layoutDoc._pivotField)}
+                    />
+                </div>
             </div>
         );
     }
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 0a6f78e2e..549cb5c8f 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -344,7 +344,8 @@ ScriptingGlobals.add(function setCardSort(value?: any, checkResult?: boolean) {
         // console.log(attr + "attricute")
         // console.log(map.get(attr)?.checkResult(selected) + "check result")
         console.log(StrCast(selected?.cardSort) + "check")
-        return StrCast(selected?.cardSort);
+        const hi = StrCast(selected?.cardSort)
+        return StrCast(selected?.cardSortForDropDown) ?? "Time";
     }
     
     function docFields(doc: Doc): void {
@@ -373,8 +374,9 @@ ScriptingGlobals.add(function setCardSort(value?: any, checkResult?: boolean) {
 
     // const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
     DocumentView.Selected().map(dv => {
+        dv.Document.cardSortForDropDown = value
+
         if (value != 'Chat GPT') {
-            
             dv.Document.cardSort = value.trim().split(/\s+/)[0].toLowerCase();
         }
         docFields(dv.Document);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index db47a84e1..72448e851 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -33,7 +33,7 @@ import './PDFViewer.scss';
 
 // pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
 // The workerSrc property shall be specified.
-Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.3.136/build/pdf.worker.mjs';
+Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.2.67/build/pdf.worker.mjs';
 
 interface IViewerProps extends FieldViewProps {
     pdfBox: PDFBox;
-- 
cgit v1.2.3-70-g09d2


From 585e6ece3c2bee7b5a747ec571c5c2af1861d324 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Fri, 14 Jun 2024 12:42:58 -0400
Subject: starting dragging ??????

---
 package-lock.json                                  | 1129 +++++++++++++++-----
 package.json                                       |    1 +
 src/client/apis/gpt/GPT.ts                         |    2 +-
 .../views/collections/CollectionCardDeckView.scss  |   39 +-
 .../views/collections/CollectionCardDeckView.tsx   |   79 +-
 src/client/views/global/globalScripts.ts           |    7 +-
 src/client/views/pdf/GPTPopup/GPTPopup.scss        |    1 +
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |  151 ++-
 8 files changed, 1082 insertions(+), 327 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index fbc0aa579..4899f14e7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1828,6 +1828,97 @@
                 "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
                 "wrap-ansi": "^8.1.0",
                 "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+            },
+            "dependencies": {
+                "ansi-regex": {
+                    "version": "5.0.1",
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                    "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                },
+                "ansi-styles": {
+                    "version": "4.3.0",
+                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+                    "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+                    "requires": {
+                        "color-convert": "^2.0.1"
+                    }
+                },
+                "color-convert": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+                    "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+                    "requires": {
+                        "color-name": "~1.1.4"
+                    }
+                },
+                "color-name": {
+                    "version": "1.1.4",
+                    "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+                    "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+                },
+                "emoji-regex": {
+                    "version": "8.0.0",
+                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+                    "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+                },
+                "string-width-cjs": {
+                    "version": "npm:string-width@4.2.3",
+                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+                    "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+                    "requires": {
+                        "emoji-regex": "^8.0.0",
+                        "is-fullwidth-code-point": "^3.0.0",
+                        "strip-ansi": "^6.0.1"
+                    },
+                    "dependencies": {
+                        "strip-ansi": {
+                            "version": "6.0.1",
+                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                            "requires": {
+                                "ansi-regex": "^5.0.1"
+                            }
+                        }
+                    }
+                },
+                "strip-ansi-cjs": {
+                    "version": "npm:strip-ansi@6.0.1",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                    "requires": {
+                        "ansi-regex": "^5.0.1"
+                    }
+                },
+                "wrap-ansi-cjs": {
+                    "version": "npm:wrap-ansi@7.0.0",
+                    "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+                    "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+                    "requires": {
+                        "ansi-styles": "^4.0.0",
+                        "string-width": "^4.1.0",
+                        "strip-ansi": "^6.0.0"
+                    },
+                    "dependencies": {
+                        "string-width": {
+                            "version": "4.2.3",
+                            "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+                            "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+                            "requires": {
+                                "emoji-regex": "^8.0.0",
+                                "is-fullwidth-code-point": "^3.0.0",
+                                "strip-ansi": "^6.0.1"
+                            }
+                        },
+                        "strip-ansi": {
+                            "version": "6.0.1",
+                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                            "requires": {
+                                "ansi-regex": "^5.0.1"
+                            }
+                        }
+                    }
+                }
             }
         },
         "@jimp/bmp": {
@@ -7327,11 +7418,6 @@
             "integrity": "sha512-tgsWVG80dM5PVEBSbXUttPJTBCOo0IKbBh4R4z/SHsC5C81A3aaUH4fsbj+JYk7fopApU/Mao1c0EWTE592TSg==",
             "dev": true
         },
-        "@types/web": {
-            "version": "0.0.143",
-            "resolved": "https://registry.npmjs.org/@types/web/-/web-0.0.143.tgz",
-            "integrity": "sha512-TazK16/OqeeqfQRB/Tv/NwzJagHbLi/w5g26FLbiFte/8LpPq6BuTyXHO/cpgwJpE6KGgFSNYb6Ap05Tz9XvCA=="
-        },
         "@types/webidl-conversions": {
             "version": "7.0.3",
             "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
@@ -8422,6 +8508,37 @@
                 "react-measure": "^2.5.2"
             },
             "dependencies": {
+                "ansi-regex": {
+                    "version": "5.0.1",
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                    "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                },
+                "ansi-styles": {
+                    "version": "4.3.0",
+                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+                    "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+                    "requires": {
+                        "color-convert": "^2.0.1"
+                    }
+                },
+                "color-convert": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+                    "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+                    "requires": {
+                        "color-name": "~1.1.4"
+                    }
+                },
+                "color-name": {
+                    "version": "1.1.4",
+                    "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+                    "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+                },
+                "emoji-regex": {
+                    "version": "8.0.0",
+                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+                    "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+                },
                 "npm": {
                     "version": "9.9.3",
                     "resolved": "https://registry.npmjs.org/npm/-/npm-9.9.3.tgz",
@@ -8501,13 +8618,19 @@
                     "dependencies": {
                         "@colors/colors": {
                             "version": "1.5.0",
+                            "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
+                            "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==",
                             "optional": true
                         },
                         "@gar/promisify": {
-                            "version": "1.1.3"
+                            "version": "1.1.3",
+                            "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
+                            "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="
                         },
                         "@isaacs/cliui": {
                             "version": "8.0.2",
+                            "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+                            "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
                             "requires": {
                                 "string-width": "^5.1.2",
                                 "string-width-cjs": "npm:string-width@^4.2.0",
@@ -8518,13 +8641,19 @@
                             },
                             "dependencies": {
                                 "ansi-regex": {
-                                    "version": "6.0.1"
+                                    "version": "6.0.1",
+                                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+                                    "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA=="
                                 },
                                 "emoji-regex": {
-                                    "version": "9.2.2"
+                                    "version": "9.2.2",
+                                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+                                    "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
                                 },
                                 "string-width": {
                                     "version": "5.1.2",
+                                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+                                    "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
                                     "requires": {
                                         "eastasianwidth": "^0.2.0",
                                         "emoji-regex": "^9.2.2",
@@ -8533,6 +8662,8 @@
                                 },
                                 "strip-ansi": {
                                     "version": "7.1.0",
+                                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+                                    "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
                                     "requires": {
                                         "ansi-regex": "^6.0.1"
                                     }
@@ -8540,10 +8671,14 @@
                             }
                         },
                         "@isaacs/string-locale-compare": {
-                            "version": "1.1.0"
+                            "version": "1.1.0",
+                            "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz",
+                            "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="
                         },
                         "@npmcli/arborist": {
                             "version": "6.5.1",
+                            "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-6.5.1.tgz",
+                            "integrity": "sha512-cdV8pGurLK0CifZRilMJbm2CZ3H4Snk8PAqOngj5qmgFLjEllMLvScSZ3XKfd+CK8fo/hrPHO9zazy9OYdvmUg==",
                             "requires": {
                                 "@isaacs/string-locale-compare": "^1.1.0",
                                 "@npmcli/fs": "^3.1.0",
@@ -8582,6 +8717,8 @@
                         },
                         "@npmcli/config": {
                             "version": "6.4.1",
+                            "resolved": "https://registry.npmjs.org/@npmcli/config/-/config-6.4.1.tgz",
+                            "integrity": "sha512-uSz+elSGzjCMANWa5IlbGczLYPkNI/LeR+cHrgaTqTrTSh9RHhOFA4daD2eRUz6lMtOW+Fnsb+qv7V2Zz8ML0g==",
                             "requires": {
                                 "@npmcli/map-workspaces": "^3.0.2",
                                 "ci-info": "^4.0.0",
@@ -8595,18 +8732,24 @@
                         },
                         "@npmcli/disparity-colors": {
                             "version": "3.0.0",
+                            "resolved": "https://registry.npmjs.org/@npmcli/disparity-colors/-/disparity-colors-3.0.0.tgz",
+                            "integrity": "sha512-5R/z157/f20Fi0Ou4ZttL51V0xz0EdPEOauFtPCEYOLInDBRCj1/TxOJ5aGTrtShxEshN2d+hXb9ZKSi5RLBcg==",
                             "requires": {
                                 "ansi-styles": "^4.3.0"
                             }
                         },
                         "@npmcli/fs": {
                             "version": "3.1.0",
+                            "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz",
+                            "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==",
                             "requires": {
                                 "semver": "^7.3.5"
                             }
                         },
                         "@npmcli/git": {
                             "version": "4.1.0",
+                            "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.1.0.tgz",
+                            "integrity": "sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==",
                             "requires": {
                                 "@npmcli/promise-spawn": "^6.0.0",
                                 "lru-cache": "^7.4.4",
@@ -8620,6 +8763,8 @@
                         },
                         "@npmcli/installed-package-contents": {
                             "version": "2.0.2",
+                            "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.0.2.tgz",
+                            "integrity": "sha512-xACzLPhnfD51GKvTOOuNX2/V4G4mz9/1I2MfDoye9kBM3RYe5g2YbscsaGoTlaWqkxeiapBWyseULVKpSVHtKQ==",
                             "requires": {
                                 "npm-bundled": "^3.0.0",
                                 "npm-normalize-package-bin": "^3.0.0"
@@ -8627,6 +8772,8 @@
                         },
                         "@npmcli/map-workspaces": {
                             "version": "3.0.4",
+                            "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-3.0.4.tgz",
+                            "integrity": "sha512-Z0TbvXkRbacjFFLpVpV0e2mheCh+WzQpcqL+4xp49uNJOxOnIAPZyXtUxZ5Qn3QBTGKA11Exjd9a5411rBrhDg==",
                             "requires": {
                                 "@npmcli/name-from-folder": "^2.0.0",
                                 "glob": "^10.2.2",
@@ -8636,6 +8783,8 @@
                         },
                         "@npmcli/metavuln-calculator": {
                             "version": "5.0.1",
+                            "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-5.0.1.tgz",
+                            "integrity": "sha512-qb8Q9wIIlEPj3WeA1Lba91R4ZboPL0uspzV0F9uwP+9AYMVB2zOoa7Pbk12g6D2NHAinSbHh6QYmGuRyHZ874Q==",
                             "requires": {
                                 "cacache": "^17.0.0",
                                 "json-parse-even-better-errors": "^3.0.0",
@@ -8645,19 +8794,27 @@
                         },
                         "@npmcli/move-file": {
                             "version": "2.0.1",
+                            "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz",
+                            "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==",
                             "requires": {
                                 "mkdirp": "^1.0.4",
                                 "rimraf": "^3.0.2"
                             }
                         },
                         "@npmcli/name-from-folder": {
-                            "version": "2.0.0"
+                            "version": "2.0.0",
+                            "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-2.0.0.tgz",
+                            "integrity": "sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg=="
                         },
                         "@npmcli/node-gyp": {
-                            "version": "3.0.0"
+                            "version": "3.0.0",
+                            "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz",
+                            "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA=="
                         },
                         "@npmcli/package-json": {
                             "version": "4.0.1",
+                            "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-4.0.1.tgz",
+                            "integrity": "sha512-lRCEGdHZomFsURroh522YvA/2cVb9oPIJrjHanCJZkiasz1BzcnLr3tBJhlV7S86MBJBuAQ33is2D60YitZL2Q==",
                             "requires": {
                                 "@npmcli/git": "^4.1.0",
                                 "glob": "^10.2.2",
@@ -8670,18 +8827,24 @@
                         },
                         "@npmcli/promise-spawn": {
                             "version": "6.0.2",
+                            "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz",
+                            "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==",
                             "requires": {
                                 "which": "^3.0.0"
                             }
                         },
                         "@npmcli/query": {
                             "version": "3.1.0",
+                            "resolved": "https://registry.npmjs.org/@npmcli/query/-/query-3.1.0.tgz",
+                            "integrity": "sha512-C/iR0tk7KSKGldibYIB9x8GtO/0Bd0I2mhOaDb8ucQL/bQVTmGoeREaFj64Z5+iCBRf3dQfed0CjJL7I8iTkiQ==",
                             "requires": {
                                 "postcss-selector-parser": "^6.0.10"
                             }
                         },
                         "@npmcli/run-script": {
                             "version": "6.0.2",
+                            "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.2.tgz",
+                            "integrity": "sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA==",
                             "requires": {
                                 "@npmcli/node-gyp": "^3.0.0",
                                 "@npmcli/promise-spawn": "^6.0.0",
@@ -8692,19 +8855,27 @@
                         },
                         "@pkgjs/parseargs": {
                             "version": "0.11.0",
+                            "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+                            "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
                             "optional": true
                         },
                         "@sigstore/bundle": {
                             "version": "1.1.0",
+                            "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-1.1.0.tgz",
+                            "integrity": "sha512-PFutXEy0SmQxYI4texPw3dd2KewuNqv7OuK1ZFtY2fM754yhvG2KdgwIhRnoEE2uHdtdGNQ8s0lb94dW9sELog==",
                             "requires": {
                                 "@sigstore/protobuf-specs": "^0.2.0"
                             }
                         },
                         "@sigstore/protobuf-specs": {
-                            "version": "0.2.1"
+                            "version": "0.2.1",
+                            "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.2.1.tgz",
+                            "integrity": "sha512-XTWVxnWJu+c1oCshMLwnKvz8ZQJJDVOlciMfgpJBQbThVjKTCG8dwyhgLngBD2KN0ap9F/gOV8rFDEx8uh7R2A=="
                         },
                         "@sigstore/sign": {
                             "version": "1.0.0",
+                            "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-1.0.0.tgz",
+                            "integrity": "sha512-INxFVNQteLtcfGmcoldzV6Je0sbbfh9I16DM4yJPw3j5+TFP8X6uIiA18mvpEa9yyeycAKgPmOA3X9hVdVTPUA==",
                             "requires": {
                                 "@sigstore/bundle": "^1.1.0",
                                 "@sigstore/protobuf-specs": "^0.2.0",
@@ -8713,69 +8884,99 @@
                         },
                         "@sigstore/tuf": {
                             "version": "1.0.3",
+                            "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-1.0.3.tgz",
+                            "integrity": "sha512-2bRovzs0nJZFlCN3rXirE4gwxCn97JNjMmwpecqlbgV9WcxX7WRuIrgzx/X7Ib7MYRbyUTpBYE0s2x6AmZXnlg==",
                             "requires": {
                                 "@sigstore/protobuf-specs": "^0.2.0",
                                 "tuf-js": "^1.1.7"
                             }
                         },
                         "@tootallnate/once": {
-                            "version": "2.0.0"
+                            "version": "2.0.0",
+                            "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
+                            "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A=="
                         },
                         "@tufjs/canonical-json": {
-                            "version": "1.0.0"
+                            "version": "1.0.0",
+                            "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz",
+                            "integrity": "sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ=="
                         },
                         "@tufjs/models": {
                             "version": "1.0.4",
+                            "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-1.0.4.tgz",
+                            "integrity": "sha512-qaGV9ltJP0EO25YfFUPhxRVK0evXFIAGicsVXuRim4Ed9cjPxYhNnNJ49SFmbeLgtxpslIkX317IgpfcHPVj/A==",
                             "requires": {
                                 "@tufjs/canonical-json": "1.0.0",
                                 "minimatch": "^9.0.0"
                             }
                         },
                         "abbrev": {
-                            "version": "2.0.0"
+                            "version": "2.0.0",
+                            "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
+                            "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ=="
                         },
                         "agent-base": {
                             "version": "6.0.2",
+                            "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+                            "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
                             "requires": {
                                 "debug": "4"
                             }
                         },
                         "agentkeepalive": {
                             "version": "4.5.0",
+                            "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
+                            "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==",
                             "requires": {
                                 "humanize-ms": "^1.2.1"
                             }
                         },
                         "aggregate-error": {
                             "version": "3.1.0",
+                            "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
+                            "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
                             "requires": {
                                 "clean-stack": "^2.0.0",
                                 "indent-string": "^4.0.0"
                             }
                         },
                         "ansi-regex": {
-                            "version": "5.0.1"
+                            "version": "5.0.1",
+                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
                         },
                         "ansi-styles": {
                             "version": "4.3.0",
+                            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+                            "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
                             "requires": {
                                 "color-convert": "^2.0.1"
                             }
                         },
                         "aproba": {
-                            "version": "2.0.0"
+                            "version": "2.0.0",
+                            "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
+                            "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
                         },
                         "archy": {
-                            "version": "1.0.0"
+                            "version": "1.0.0",
+                            "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
+                            "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw=="
                         },
                         "are-we-there-yet": {
-                            "version": "4.0.2"
+                            "version": "4.0.2",
+                            "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-4.0.2.tgz",
+                            "integrity": "sha512-ncSWAawFhKMJDTdoAeOV+jyW1VCMj5QIAwULIBV0SSR7B/RLPPEQiknKcg/RIIZlUQrxELpsxMiTUoAQ4sIUyg=="
                         },
                         "balanced-match": {
-                            "version": "1.0.2"
+                            "version": "1.0.2",
+                            "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+                            "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
                         },
                         "bin-links": {
                             "version": "4.0.3",
+                            "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-4.0.3.tgz",
+                            "integrity": "sha512-obsRaULtJurnfox/MDwgq6Yo9kzbv1CPTk/1/s7Z/61Lezc8IKkFCOXNeVLXz0456WRzBQmSsDWlai2tIhBsfA==",
                             "requires": {
                                 "cmd-shim": "^6.0.0",
                                 "npm-normalize-package-bin": "^3.0.0",
@@ -8784,22 +8985,30 @@
                             }
                         },
                         "binary-extensions": {
-                            "version": "2.2.0"
+                            "version": "2.2.0",
+                            "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+                            "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA=="
                         },
                         "brace-expansion": {
                             "version": "2.0.1",
+                            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+                            "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
                             "requires": {
                                 "balanced-match": "^1.0.0"
                             }
                         },
                         "builtins": {
                             "version": "5.0.1",
+                            "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz",
+                            "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==",
                             "requires": {
                                 "semver": "^7.0.0"
                             }
                         },
                         "cacache": {
                             "version": "17.1.4",
+                            "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.4.tgz",
+                            "integrity": "sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==",
                             "requires": {
                                 "@npmcli/fs": "^3.1.0",
                                 "fs-minipass": "^3.0.0",
@@ -8816,25 +9025,37 @@
                             }
                         },
                         "chalk": {
-                            "version": "5.3.0"
+                            "version": "5.3.0",
+                            "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
+                            "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="
                         },
                         "chownr": {
-                            "version": "2.0.0"
+                            "version": "2.0.0",
+                            "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
+                            "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="
                         },
                         "ci-info": {
-                            "version": "4.0.0"
+                            "version": "4.0.0",
+                            "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.0.0.tgz",
+                            "integrity": "sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg=="
                         },
                         "cidr-regex": {
                             "version": "3.1.1",
+                            "resolved": "https://registry.npmjs.org/cidr-regex/-/cidr-regex-3.1.1.tgz",
+                            "integrity": "sha512-RBqYd32aDwbCMFJRL6wHOlDNYJsPNTt8vC82ErHF5vKt8QQzxm1FrkW8s/R5pVrXMf17sba09Uoy91PKiddAsw==",
                             "requires": {
                                 "ip-regex": "^4.1.0"
                             }
                         },
                         "clean-stack": {
-                            "version": "2.2.0"
+                            "version": "2.2.0",
+                            "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
+                            "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="
                         },
                         "cli-columns": {
                             "version": "4.0.0",
+                            "resolved": "https://registry.npmjs.org/cli-columns/-/cli-columns-4.0.0.tgz",
+                            "integrity": "sha512-XW2Vg+w+L9on9wtwKpyzluIPCWXjaBahI7mTcYjx+BVIYD9c3yqcv/yKC7CmdCZat4rq2yiE1UMSJC5ivKfMtQ==",
                             "requires": {
                                 "string-width": "^4.2.3",
                                 "strip-ansi": "^6.0.1"
@@ -8842,47 +9063,69 @@
                         },
                         "cli-table3": {
                             "version": "0.6.3",
+                            "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz",
+                            "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==",
                             "requires": {
                                 "@colors/colors": "1.5.0",
                                 "string-width": "^4.2.0"
                             }
                         },
                         "clone": {
-                            "version": "1.0.4"
+                            "version": "1.0.4",
+                            "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+                            "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="
                         },
                         "cmd-shim": {
-                            "version": "6.0.2"
+                            "version": "6.0.2",
+                            "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.2.tgz",
+                            "integrity": "sha512-+FFYbB0YLaAkhkcrjkyNLYDiOsFSfRjwjY19LXk/psmMx1z00xlCv7hhQoTGXXIKi+YXHL/iiFo8NqMVQX9nOw=="
                         },
                         "color-convert": {
                             "version": "2.0.1",
+                            "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+                            "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
                             "requires": {
                                 "color-name": "~1.1.4"
                             }
                         },
                         "color-name": {
-                            "version": "1.1.4"
+                            "version": "1.1.4",
+                            "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+                            "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
                         },
                         "color-support": {
-                            "version": "1.1.3"
+                            "version": "1.1.3",
+                            "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
+                            "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg=="
                         },
                         "columnify": {
                             "version": "1.6.0",
+                            "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.6.0.tgz",
+                            "integrity": "sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q==",
                             "requires": {
                                 "strip-ansi": "^6.0.1",
                                 "wcwidth": "^1.0.0"
                             }
                         },
                         "common-ancestor-path": {
-                            "version": "1.0.1"
+                            "version": "1.0.1",
+                            "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz",
+                            "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="
                         },
                         "concat-map": {
-                            "version": "0.0.1"
+                            "version": "0.0.1",
+                            "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+                            "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
                         },
                         "console-control-strings": {
-                            "version": "1.1.0"
+                            "version": "1.1.0",
+                            "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+                            "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
                         },
                         "cross-spawn": {
                             "version": "7.0.3",
+                            "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+                            "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
                             "requires": {
                                 "path-key": "^3.1.0",
                                 "shebang-command": "^2.0.0",
@@ -8891,6 +9134,8 @@
                             "dependencies": {
                                 "which": {
                                     "version": "2.0.2",
+                                    "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+                                    "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
                                     "requires": {
                                         "isexe": "^2.0.0"
                                     }
@@ -8898,58 +9143,86 @@
                             }
                         },
                         "cssesc": {
-                            "version": "3.0.0"
+                            "version": "3.0.0",
+                            "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+                            "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="
                         },
                         "debug": {
                             "version": "4.3.4",
+                            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+                            "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
                             "requires": {
                                 "ms": "2.1.2"
                             },
                             "dependencies": {
                                 "ms": {
-                                    "version": "2.1.2"
+                                    "version": "2.1.2",
+                                    "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+                                    "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
                                 }
                             }
                         },
                         "defaults": {
                             "version": "1.0.4",
+                            "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
+                            "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
                             "requires": {
                                 "clone": "^1.0.2"
                             }
                         },
                         "delegates": {
-                            "version": "1.0.0"
+                            "version": "1.0.0",
+                            "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+                            "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
                         },
                         "diff": {
-                            "version": "5.2.0"
+                            "version": "5.2.0",
+                            "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
+                            "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="
                         },
                         "eastasianwidth": {
-                            "version": "0.2.0"
+                            "version": "0.2.0",
+                            "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+                            "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
                         },
                         "emoji-regex": {
-                            "version": "8.0.0"
+                            "version": "8.0.0",
+                            "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+                            "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
                         },
                         "encoding": {
                             "version": "0.1.13",
+                            "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
+                            "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
                             "optional": true,
                             "requires": {
                                 "iconv-lite": "^0.6.2"
                             }
                         },
                         "env-paths": {
-                            "version": "2.2.1"
+                            "version": "2.2.1",
+                            "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+                            "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="
                         },
                         "err-code": {
-                            "version": "2.0.3"
+                            "version": "2.0.3",
+                            "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
+                            "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="
                         },
                         "exponential-backoff": {
-                            "version": "3.1.1"
+                            "version": "3.1.1",
+                            "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz",
+                            "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw=="
                         },
                         "fastest-levenshtein": {
-                            "version": "1.0.16"
+                            "version": "1.0.16",
+                            "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz",
+                            "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg=="
                         },
                         "foreground-child": {
                             "version": "3.1.1",
+                            "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
+                            "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
                             "requires": {
                                 "cross-spawn": "^7.0.0",
                                 "signal-exit": "^4.0.1"
@@ -8957,18 +9230,26 @@
                         },
                         "fs-minipass": {
                             "version": "3.0.3",
+                            "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz",
+                            "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==",
                             "requires": {
                                 "minipass": "^7.0.3"
                             }
                         },
                         "fs.realpath": {
-                            "version": "1.0.0"
+                            "version": "1.0.0",
+                            "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+                            "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
                         },
                         "function-bind": {
-                            "version": "1.1.2"
+                            "version": "1.1.2",
+                            "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+                            "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
                         },
                         "gauge": {
                             "version": "5.0.1",
+                            "resolved": "https://registry.npmjs.org/gauge/-/gauge-5.0.1.tgz",
+                            "integrity": "sha512-CmykPMJGuNan/3S4kZOpvvPYSNqSHANiWnh9XcMU2pSjtBfF0XzZ2p1bFAxTbnFxyBuPxQYHhzwaoOmUdqzvxQ==",
                             "requires": {
                                 "aproba": "^1.0.3 || ^2.0.0",
                                 "color-support": "^1.1.3",
@@ -8982,6 +9263,8 @@
                         },
                         "glob": {
                             "version": "10.3.10",
+                            "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
+                            "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
                             "requires": {
                                 "foreground-child": "^3.1.0",
                                 "jackspeak": "^2.3.5",
@@ -8991,28 +9274,40 @@
                             }
                         },
                         "graceful-fs": {
-                            "version": "4.2.11"
+                            "version": "4.2.11",
+                            "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+                            "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
                         },
                         "has-unicode": {
-                            "version": "2.0.1"
+                            "version": "2.0.1",
+                            "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+                            "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
                         },
                         "hasown": {
                             "version": "2.0.1",
+                            "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
+                            "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
                             "requires": {
                                 "function-bind": "^1.1.2"
                             }
                         },
                         "hosted-git-info": {
                             "version": "6.1.1",
+                            "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz",
+                            "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==",
                             "requires": {
                                 "lru-cache": "^7.5.1"
                             }
                         },
                         "http-cache-semantics": {
-                            "version": "4.1.1"
+                            "version": "4.1.1",
+                            "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
+                            "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ=="
                         },
                         "http-proxy-agent": {
                             "version": "5.0.0",
+                            "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
+                            "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
                             "requires": {
                                 "@tootallnate/once": "2",
                                 "agent-base": "6",
@@ -9021,6 +9316,8 @@
                         },
                         "https-proxy-agent": {
                             "version": "5.0.1",
+                            "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+                            "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
                             "requires": {
                                 "agent-base": "6",
                                 "debug": "4"
@@ -9028,12 +9325,16 @@
                         },
                         "humanize-ms": {
                             "version": "1.2.1",
+                            "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
+                            "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
                             "requires": {
                                 "ms": "^2.0.0"
                             }
                         },
                         "iconv-lite": {
                             "version": "0.6.3",
+                            "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+                            "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
                             "optional": true,
                             "requires": {
                                 "safer-buffer": ">= 2.1.2 < 3.0.0"
@@ -9041,34 +9342,50 @@
                         },
                         "ignore-walk": {
                             "version": "6.0.4",
+                            "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.4.tgz",
+                            "integrity": "sha512-t7sv42WkwFkyKbivUCglsQW5YWMskWtbEf4MNKX5u/CCWHKSPzN4FtBQGsQZgCLbxOzpVlcbWVK5KB3auIOjSw==",
                             "requires": {
                                 "minimatch": "^9.0.0"
                             }
                         },
                         "imurmurhash": {
-                            "version": "0.1.4"
+                            "version": "0.1.4",
+                            "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+                            "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="
                         },
                         "indent-string": {
-                            "version": "4.0.0"
+                            "version": "4.0.0",
+                            "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+                            "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="
                         },
                         "infer-owner": {
-                            "version": "1.0.4"
+                            "version": "1.0.4",
+                            "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
+                            "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A=="
                         },
                         "inflight": {
                             "version": "1.0.6",
+                            "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+                            "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
                             "requires": {
                                 "once": "^1.3.0",
                                 "wrappy": "1"
                             }
                         },
                         "inherits": {
-                            "version": "2.0.4"
+                            "version": "2.0.4",
+                            "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+                            "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
                         },
                         "ini": {
-                            "version": "4.1.1"
+                            "version": "4.1.1",
+                            "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz",
+                            "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g=="
                         },
                         "init-package-json": {
                             "version": "5.0.0",
+                            "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-5.0.0.tgz",
+                            "integrity": "sha512-kBhlSheBfYmq3e0L1ii+VKe3zBTLL5lDCDWR+f9dLmEGSB3MqLlMlsolubSsyI88Bg6EA+BIMlomAnQ1SwgQBw==",
                             "requires": {
                                 "npm-package-arg": "^10.0.0",
                                 "promzard": "^1.0.0",
@@ -9081,67 +9398,99 @@
                         },
                         "ip-address": {
                             "version": "9.0.5",
+                            "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
+                            "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
                             "requires": {
                                 "jsbn": "1.1.0",
                                 "sprintf-js": "^1.1.3"
                             },
                             "dependencies": {
                                 "sprintf-js": {
-                                    "version": "1.1.3"
+                                    "version": "1.1.3",
+                                    "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
+                                    "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="
                                 }
                             }
                         },
                         "ip-regex": {
-                            "version": "4.3.0"
+                            "version": "4.3.0",
+                            "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz",
+                            "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q=="
                         },
                         "is-cidr": {
                             "version": "4.0.2",
+                            "resolved": "https://registry.npmjs.org/is-cidr/-/is-cidr-4.0.2.tgz",
+                            "integrity": "sha512-z4a1ENUajDbEl/Q6/pVBpTR1nBjjEE1X7qb7bmWYanNnPoKAvUCPFKeXV6Fe4mgTkWKBqiHIcwsI3SndiO5FeA==",
                             "requires": {
                                 "cidr-regex": "^3.1.1"
                             }
                         },
                         "is-core-module": {
                             "version": "2.13.1",
+                            "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
+                            "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
                             "requires": {
                                 "hasown": "^2.0.0"
                             }
                         },
                         "is-fullwidth-code-point": {
-                            "version": "3.0.0"
+                            "version": "3.0.0",
+                            "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+                            "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
                         },
                         "is-lambda": {
-                            "version": "1.0.1"
+                            "version": "1.0.1",
+                            "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz",
+                            "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="
                         },
                         "isexe": {
-                            "version": "2.0.0"
+                            "version": "2.0.0",
+                            "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+                            "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
                         },
                         "jackspeak": {
                             "version": "2.3.6",
+                            "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
+                            "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
                             "requires": {
                                 "@isaacs/cliui": "^8.0.2",
                                 "@pkgjs/parseargs": "^0.11.0"
                             }
                         },
                         "jsbn": {
-                            "version": "1.1.0"
+                            "version": "1.1.0",
+                            "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
+                            "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="
                         },
                         "json-parse-even-better-errors": {
-                            "version": "3.0.1"
+                            "version": "3.0.1",
+                            "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz",
+                            "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg=="
                         },
                         "json-stringify-nice": {
-                            "version": "1.1.4"
+                            "version": "1.1.4",
+                            "resolved": "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz",
+                            "integrity": "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw=="
                         },
                         "jsonparse": {
-                            "version": "1.3.1"
+                            "version": "1.3.1",
+                            "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
+                            "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="
                         },
                         "just-diff": {
-                            "version": "6.0.2"
+                            "version": "6.0.2",
+                            "resolved": "https://registry.npmjs.org/just-diff/-/just-diff-6.0.2.tgz",
+                            "integrity": "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA=="
                         },
                         "just-diff-apply": {
-                            "version": "5.5.0"
+                            "version": "5.5.0",
+                            "resolved": "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-5.5.0.tgz",
+                            "integrity": "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw=="
                         },
                         "libnpmaccess": {
                             "version": "7.0.3",
+                            "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-7.0.3.tgz",
+                            "integrity": "sha512-It+fk/NRdRfv5giLhaVeyebGi/0S2LDSAwuZ0AGQ4x//PtCVb2Hj29wgSHe+XEL+RUkvLBkxbRV+DqLtOzuVTQ==",
                             "requires": {
                                 "npm-package-arg": "^10.1.0",
                                 "npm-registry-fetch": "^14.0.3"
@@ -9149,6 +9498,8 @@
                         },
                         "libnpmdiff": {
                             "version": "5.0.21",
+                            "resolved": "https://registry.npmjs.org/libnpmdiff/-/libnpmdiff-5.0.21.tgz",
+                            "integrity": "sha512-Zx+o/qnGoX46osnInyQQ5KI8jn2wIqXXiu4TJzE8GFd+o6kbyblJf+ihG81M1+yHK3AzkD1m4KK3+UTPXh/hBw==",
                             "requires": {
                                 "@npmcli/arborist": "^6.5.0",
                                 "@npmcli/disparity-colors": "^3.0.0",
@@ -9163,6 +9514,8 @@
                         },
                         "libnpmexec": {
                             "version": "6.0.5",
+                            "resolved": "https://registry.npmjs.org/libnpmexec/-/libnpmexec-6.0.5.tgz",
+                            "integrity": "sha512-yN/7uJ3iYCPaKagHfrqXuCFLKn2ddcnYpEyC/tVhisHULC95uCy8AhUdNkThRXzhFqqptejO25ZfoWOGrdqnxA==",
                             "requires": {
                                 "@npmcli/arborist": "^6.5.0",
                                 "@npmcli/run-script": "^6.0.0",
@@ -9179,12 +9532,16 @@
                         },
                         "libnpmfund": {
                             "version": "4.2.2",
+                            "resolved": "https://registry.npmjs.org/libnpmfund/-/libnpmfund-4.2.2.tgz",
+                            "integrity": "sha512-qnkP09tpryxD/iPYasHM7+yG4ZVe0e91sBVI/R8HJ1+ajeR9poWDckwiN2LEWGvtV/T/dqB++6A1NLrA5NPryw==",
                             "requires": {
                                 "@npmcli/arborist": "^6.5.0"
                             }
                         },
                         "libnpmhook": {
                             "version": "9.0.4",
+                            "resolved": "https://registry.npmjs.org/libnpmhook/-/libnpmhook-9.0.4.tgz",
+                            "integrity": "sha512-bYD8nJiPnqeMtSsRc5bztqSh6/v16M0jQjLeO959HJqf9ZRWKRpVnFx971Rz5zbPGOB2BrQa6iopsh5vons5ww==",
                             "requires": {
                                 "aproba": "^2.0.0",
                                 "npm-registry-fetch": "^14.0.3"
@@ -9192,6 +9549,8 @@
                         },
                         "libnpmorg": {
                             "version": "5.0.5",
+                            "resolved": "https://registry.npmjs.org/libnpmorg/-/libnpmorg-5.0.5.tgz",
+                            "integrity": "sha512-0EbtEIFthVlmaj0hhC3LlEEXUZU3vKfJwfWL//iAqKjHreMhCD3cgdkld+UeWYDgsZzwzvXmopoY0l38I0yx9Q==",
                             "requires": {
                                 "aproba": "^2.0.0",
                                 "npm-registry-fetch": "^14.0.3"
@@ -9199,6 +9558,8 @@
                         },
                         "libnpmpack": {
                             "version": "5.0.21",
+                            "resolved": "https://registry.npmjs.org/libnpmpack/-/libnpmpack-5.0.21.tgz",
+                            "integrity": "sha512-mQd3pPx7Xf6i2A6QnYcCmgq34BmfVG3HJvpl422B5dLKfi9acITqcJiJ2K7adhxPKZMF5VbP2+j391cs5w+xww==",
                             "requires": {
                                 "@npmcli/arborist": "^6.5.0",
                                 "@npmcli/run-script": "^6.0.0",
@@ -9208,6 +9569,8 @@
                         },
                         "libnpmpublish": {
                             "version": "7.5.2",
+                            "resolved": "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-7.5.2.tgz",
+                            "integrity": "sha512-azAxjEjAgBkbPHUGsGdMbTScyiLcTKdEnNYwGS+9yt+fUsNyiYn8hNH3+HeWKaXzFjvxi50MrHw1yp1gg5pumQ==",
                             "requires": {
                                 "ci-info": "^4.0.0",
                                 "normalize-package-data": "^5.0.0",
@@ -9221,12 +9584,16 @@
                         },
                         "libnpmsearch": {
                             "version": "6.0.3",
+                            "resolved": "https://registry.npmjs.org/libnpmsearch/-/libnpmsearch-6.0.3.tgz",
+                            "integrity": "sha512-4FLTFsygxRKd+PL32WJlFN1g6gkfx3d90PjgSgd6kl9nJ55sZQAqNyi1M7QROKB4kN8JCNCphK8fQYDMg5bCcg==",
                             "requires": {
                                 "npm-registry-fetch": "^14.0.3"
                             }
                         },
                         "libnpmteam": {
                             "version": "5.0.4",
+                            "resolved": "https://registry.npmjs.org/libnpmteam/-/libnpmteam-5.0.4.tgz",
+                            "integrity": "sha512-yN2zxNb8Urvvo7fTWRcP3E/KPtpZJXFweDWcl+H/s3zopGDI9ahpidddGVG98JhnPl3vjqtZvFGU3/sqVTfuIw==",
                             "requires": {
                                 "aproba": "^2.0.0",
                                 "npm-registry-fetch": "^14.0.3"
@@ -9234,6 +9601,8 @@
                         },
                         "libnpmversion": {
                             "version": "4.0.3",
+                            "resolved": "https://registry.npmjs.org/libnpmversion/-/libnpmversion-4.0.3.tgz",
+                            "integrity": "sha512-eD1O5zr0ko5pjOdz+2NyTEzP0kzKG8VIVyU+hIsz61cRmTrTxFRJhVBNOI1Q/inifkcM/UTl8EMfa0vX48zfoQ==",
                             "requires": {
                                 "@npmcli/git": "^4.0.1",
                                 "@npmcli/run-script": "^6.0.0",
@@ -9243,10 +9612,14 @@
                             }
                         },
                         "lru-cache": {
-                            "version": "7.18.3"
+                            "version": "7.18.3",
+                            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+                            "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="
                         },
                         "make-fetch-happen": {
                             "version": "11.1.1",
+                            "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz",
+                            "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==",
                             "requires": {
                                 "agentkeepalive": "^4.2.1",
                                 "cacache": "^17.0.0",
@@ -9266,27 +9639,37 @@
                             },
                             "dependencies": {
                                 "minipass": {
-                                    "version": "5.0.0"
+                                    "version": "5.0.0",
+                                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+                                    "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="
                                 }
                             }
                         },
                         "minimatch": {
                             "version": "9.0.3",
+                            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
+                            "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
                             "requires": {
                                 "brace-expansion": "^2.0.1"
                             }
                         },
                         "minipass": {
-                            "version": "7.0.4"
+                            "version": "7.0.4",
+                            "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+                            "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ=="
                         },
                         "minipass-collect": {
                             "version": "1.0.2",
+                            "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz",
+                            "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==",
                             "requires": {
                                 "minipass": "^3.0.0"
                             },
                             "dependencies": {
                                 "minipass": {
                                     "version": "3.3.6",
+                                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                                    "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
                                     "requires": {
                                         "yallist": "^4.0.0"
                                     }
@@ -9295,6 +9678,8 @@
                         },
                         "minipass-fetch": {
                             "version": "3.0.4",
+                            "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz",
+                            "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==",
                             "requires": {
                                 "encoding": "^0.1.13",
                                 "minipass": "^7.0.3",
@@ -9304,12 +9689,16 @@
                         },
                         "minipass-flush": {
                             "version": "1.0.5",
+                            "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz",
+                            "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==",
                             "requires": {
                                 "minipass": "^3.0.0"
                             },
                             "dependencies": {
                                 "minipass": {
                                     "version": "3.3.6",
+                                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                                    "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
                                     "requires": {
                                         "yallist": "^4.0.0"
                                     }
@@ -9318,6 +9707,8 @@
                         },
                         "minipass-json-stream": {
                             "version": "1.0.1",
+                            "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz",
+                            "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==",
                             "requires": {
                                 "jsonparse": "^1.3.1",
                                 "minipass": "^3.0.0"
@@ -9325,6 +9716,8 @@
                             "dependencies": {
                                 "minipass": {
                                     "version": "3.3.6",
+                                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                                    "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
                                     "requires": {
                                         "yallist": "^4.0.0"
                                     }
@@ -9333,12 +9726,16 @@
                         },
                         "minipass-pipeline": {
                             "version": "1.2.4",
+                            "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
+                            "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
                             "requires": {
                                 "minipass": "^3.0.0"
                             },
                             "dependencies": {
                                 "minipass": {
                                     "version": "3.3.6",
+                                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                                    "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
                                     "requires": {
                                         "yallist": "^4.0.0"
                                     }
@@ -9347,12 +9744,16 @@
                         },
                         "minipass-sized": {
                             "version": "1.0.3",
+                            "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz",
+                            "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==",
                             "requires": {
                                 "minipass": "^3.0.0"
                             },
                             "dependencies": {
                                 "minipass": {
                                     "version": "3.3.6",
+                                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                                    "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
                                     "requires": {
                                         "yallist": "^4.0.0"
                                     }
@@ -9361,6 +9762,8 @@
                         },
                         "minizlib": {
                             "version": "2.1.2",
+                            "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
+                            "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
                             "requires": {
                                 "minipass": "^3.0.0",
                                 "yallist": "^4.0.0"
@@ -9368,6 +9771,8 @@
                             "dependencies": {
                                 "minipass": {
                                     "version": "3.3.6",
+                                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                                    "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
                                     "requires": {
                                         "yallist": "^4.0.0"
                                     }
@@ -9375,19 +9780,29 @@
                             }
                         },
                         "mkdirp": {
-                            "version": "1.0.4"
+                            "version": "1.0.4",
+                            "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+                            "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
                         },
                         "ms": {
-                            "version": "2.1.3"
+                            "version": "2.1.3",
+                            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+                            "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
                         },
                         "mute-stream": {
-                            "version": "1.0.0"
+                            "version": "1.0.0",
+                            "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz",
+                            "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA=="
                         },
                         "negotiator": {
-                            "version": "0.6.3"
+                            "version": "0.6.3",
+                            "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+                            "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
                         },
                         "node-gyp": {
                             "version": "9.4.1",
+                            "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz",
+                            "integrity": "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==",
                             "requires": {
                                 "env-paths": "^2.2.0",
                                 "exponential-backoff": "^3.1.1",
@@ -9404,16 +9819,22 @@
                             "dependencies": {
                                 "@npmcli/fs": {
                                     "version": "2.1.2",
+                                    "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz",
+                                    "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==",
                                     "requires": {
                                         "@gar/promisify": "^1.1.3",
                                         "semver": "^7.3.5"
                                     }
                                 },
                                 "abbrev": {
-                                    "version": "1.1.1"
+                                    "version": "1.1.1",
+                                    "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+                                    "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
                                 },
                                 "are-we-there-yet": {
                                     "version": "3.0.1",
+                                    "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz",
+                                    "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==",
                                     "requires": {
                                         "delegates": "^1.0.0",
                                         "readable-stream": "^3.6.0"
@@ -9421,6 +9842,8 @@
                                 },
                                 "brace-expansion": {
                                     "version": "1.1.11",
+                                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+                                    "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
                                     "requires": {
                                         "balanced-match": "^1.0.0",
                                         "concat-map": "0.0.1"
@@ -9428,6 +9851,8 @@
                                 },
                                 "cacache": {
                                     "version": "16.1.3",
+                                    "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz",
+                                    "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==",
                                     "requires": {
                                         "@npmcli/fs": "^2.1.0",
                                         "@npmcli/move-file": "^2.0.0",
@@ -9451,12 +9876,16 @@
                                     "dependencies": {
                                         "brace-expansion": {
                                             "version": "2.0.1",
+                                            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+                                            "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
                                             "requires": {
                                                 "balanced-match": "^1.0.0"
                                             }
                                         },
                                         "glob": {
                                             "version": "8.1.0",
+                                            "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+                                            "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
                                             "requires": {
                                                 "fs.realpath": "^1.0.0",
                                                 "inflight": "^1.0.4",
@@ -9467,6 +9896,8 @@
                                         },
                                         "minimatch": {
                                             "version": "5.1.6",
+                                            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+                                            "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
                                             "requires": {
                                                 "brace-expansion": "^2.0.1"
                                             }
@@ -9475,12 +9906,16 @@
                                 },
                                 "fs-minipass": {
                                     "version": "2.1.0",
+                                    "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
+                                    "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
                                     "requires": {
                                         "minipass": "^3.0.0"
                                     }
                                 },
                                 "gauge": {
                                     "version": "4.0.4",
+                                    "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz",
+                                    "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==",
                                     "requires": {
                                         "aproba": "^1.0.3 || ^2.0.0",
                                         "color-support": "^1.1.3",
@@ -9494,6 +9929,8 @@
                                 },
                                 "glob": {
                                     "version": "7.2.3",
+                                    "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+                                    "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
                                     "requires": {
                                         "fs.realpath": "^1.0.0",
                                         "inflight": "^1.0.4",
@@ -9505,6 +9942,8 @@
                                 },
                                 "make-fetch-happen": {
                                     "version": "10.2.1",
+                                    "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz",
+                                    "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==",
                                     "requires": {
                                         "agentkeepalive": "^4.2.1",
                                         "cacache": "^16.1.0",
@@ -9526,18 +9965,24 @@
                                 },
                                 "minimatch": {
                                     "version": "3.1.2",
+                                    "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+                                    "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
                                     "requires": {
                                         "brace-expansion": "^1.1.7"
                                     }
                                 },
                                 "minipass": {
                                     "version": "3.3.6",
+                                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                                    "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
                                     "requires": {
                                         "yallist": "^4.0.0"
                                     }
                                 },
                                 "minipass-fetch": {
                                     "version": "2.1.2",
+                                    "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz",
+                                    "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==",
                                     "requires": {
                                         "encoding": "^0.1.13",
                                         "minipass": "^3.1.6",
@@ -9547,12 +9992,16 @@
                                 },
                                 "nopt": {
                                     "version": "6.0.0",
+                                    "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz",
+                                    "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==",
                                     "requires": {
                                         "abbrev": "^1.0.0"
                                     }
                                 },
                                 "npmlog": {
                                     "version": "6.0.2",
+                                    "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz",
+                                    "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==",
                                     "requires": {
                                         "are-we-there-yet": "^3.0.0",
                                         "console-control-strings": "^1.1.0",
@@ -9561,28 +10010,38 @@
                                     }
                                 },
                                 "signal-exit": {
-                                    "version": "3.0.7"
+                                    "version": "3.0.7",
+                                    "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+                                    "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
                                 },
                                 "ssri": {
                                     "version": "9.0.1",
+                                    "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz",
+                                    "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==",
                                     "requires": {
                                         "minipass": "^3.1.1"
                                     }
                                 },
                                 "unique-filename": {
                                     "version": "2.0.1",
+                                    "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz",
+                                    "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==",
                                     "requires": {
                                         "unique-slug": "^3.0.0"
                                     }
                                 },
                                 "unique-slug": {
                                     "version": "3.0.0",
+                                    "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz",
+                                    "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==",
                                     "requires": {
                                         "imurmurhash": "^0.1.4"
                                     }
                                 },
                                 "which": {
                                     "version": "2.0.2",
+                                    "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+                                    "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
                                     "requires": {
                                         "isexe": "^2.0.0"
                                     }
@@ -9591,12 +10050,16 @@
                         },
                         "nopt": {
                             "version": "7.2.0",
+                            "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz",
+                            "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==",
                             "requires": {
                                 "abbrev": "^2.0.0"
                             }
                         },
                         "normalize-package-data": {
                             "version": "5.0.0",
+                            "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz",
+                            "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==",
                             "requires": {
                                 "hosted-git-info": "^6.0.0",
                                 "is-core-module": "^2.8.1",
@@ -9605,25 +10068,35 @@
                             }
                         },
                         "npm-audit-report": {
-                            "version": "5.0.0"
+                            "version": "5.0.0",
+                            "resolved": "https://registry.npmjs.org/npm-audit-report/-/npm-audit-report-5.0.0.tgz",
+                            "integrity": "sha512-EkXrzat7zERmUhHaoren1YhTxFwsOu5jypE84k6632SXTHcQE1z8V51GC6GVZt8LxkC+tbBcKMUBZAgk8SUSbw=="
                         },
                         "npm-bundled": {
                             "version": "3.0.0",
+                            "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.0.tgz",
+                            "integrity": "sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ==",
                             "requires": {
                                 "npm-normalize-package-bin": "^3.0.0"
                             }
                         },
                         "npm-install-checks": {
                             "version": "6.3.0",
+                            "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz",
+                            "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==",
                             "requires": {
                                 "semver": "^7.1.1"
                             }
                         },
                         "npm-normalize-package-bin": {
-                            "version": "3.0.1"
+                            "version": "3.0.1",
+                            "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz",
+                            "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ=="
                         },
                         "npm-package-arg": {
                             "version": "10.1.0",
+                            "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz",
+                            "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==",
                             "requires": {
                                 "hosted-git-info": "^6.0.0",
                                 "proc-log": "^3.0.0",
@@ -9633,12 +10106,16 @@
                         },
                         "npm-packlist": {
                             "version": "7.0.4",
+                            "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz",
+                            "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==",
                             "requires": {
                                 "ignore-walk": "^6.0.0"
                             }
                         },
                         "npm-pick-manifest": {
                             "version": "8.0.2",
+                            "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.2.tgz",
+                            "integrity": "sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==",
                             "requires": {
                                 "npm-install-checks": "^6.0.0",
                                 "npm-normalize-package-bin": "^3.0.0",
@@ -9648,6 +10125,8 @@
                         },
                         "npm-profile": {
                             "version": "7.0.1",
+                            "resolved": "https://registry.npmjs.org/npm-profile/-/npm-profile-7.0.1.tgz",
+                            "integrity": "sha512-VReArOY/fCx5dWL66cbJ2OMogTQAVVQA//8jjmjkarboki3V7UJ0XbGFW+khRwiAJFQjuH0Bqr/yF7Y5RZdkMQ==",
                             "requires": {
                                 "npm-registry-fetch": "^14.0.0",
                                 "proc-log": "^3.0.0"
@@ -9655,6 +10134,8 @@
                         },
                         "npm-registry-fetch": {
                             "version": "14.0.5",
+                            "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.5.tgz",
+                            "integrity": "sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA==",
                             "requires": {
                                 "make-fetch-happen": "^11.0.0",
                                 "minipass": "^5.0.0",
@@ -9666,15 +10147,21 @@
                             },
                             "dependencies": {
                                 "minipass": {
-                                    "version": "5.0.0"
+                                    "version": "5.0.0",
+                                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+                                    "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="
                                 }
                             }
                         },
                         "npm-user-validate": {
-                            "version": "2.0.0"
+                            "version": "2.0.0",
+                            "resolved": "https://registry.npmjs.org/npm-user-validate/-/npm-user-validate-2.0.0.tgz",
+                            "integrity": "sha512-sSWeqAYJ2dUPStJB+AEj0DyLRltr/f6YNcvCA7phkB8/RMLMnVsQ41GMwHo/ERZLYNDsyB2wPm7pZo1mqPOl7Q=="
                         },
                         "npmlog": {
                             "version": "7.0.1",
+                            "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-7.0.1.tgz",
+                            "integrity": "sha512-uJ0YFk/mCQpLBt+bxN88AKd+gyqZvZDbtiNxk6Waqcj2aPRyfVx8ITawkyQynxUagInjdYT1+qj4NfA5KJJUxg==",
                             "requires": {
                                 "are-we-there-yet": "^4.0.0",
                                 "console-control-strings": "^1.1.0",
@@ -9684,18 +10171,24 @@
                         },
                         "once": {
                             "version": "1.4.0",
+                            "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+                            "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
                             "requires": {
                                 "wrappy": "1"
                             }
                         },
                         "p-map": {
                             "version": "4.0.0",
+                            "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
+                            "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
                             "requires": {
                                 "aggregate-error": "^3.0.0"
                             }
                         },
                         "pacote": {
                             "version": "15.2.0",
+                            "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.2.0.tgz",
+                            "integrity": "sha512-rJVZeIwHTUta23sIZgEIM62WYwbmGbThdbnkt81ravBplQv+HjyroqnLRNH2+sLJHcGZmLRmhPwACqhfTcOmnA==",
                             "requires": {
                                 "@npmcli/git": "^4.0.0",
                                 "@npmcli/installed-package-contents": "^2.0.1",
@@ -9718,12 +10211,16 @@
                             },
                             "dependencies": {
                                 "minipass": {
-                                    "version": "5.0.0"
+                                    "version": "5.0.0",
+                                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+                                    "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="
                                 }
                             }
                         },
                         "parse-conflict-json": {
                             "version": "3.0.1",
+                            "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-3.0.1.tgz",
+                            "integrity": "sha512-01TvEktc68vwbJOtWZluyWeVGWjP+bZwXtPDMQVbBKzbJ/vZBif0L69KH1+cHv1SZ6e0FKLvjyHe8mqsIqYOmw==",
                             "requires": {
                                 "json-parse-even-better-errors": "^3.0.0",
                                 "just-diff": "^6.0.0",
@@ -9731,44 +10228,64 @@
                             }
                         },
                         "path-is-absolute": {
-                            "version": "1.0.1"
+                            "version": "1.0.1",
+                            "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+                            "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="
                         },
                         "path-key": {
-                            "version": "3.1.1"
+                            "version": "3.1.1",
+                            "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+                            "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
                         },
                         "path-scurry": {
                             "version": "1.10.1",
+                            "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
+                            "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
                             "requires": {
                                 "lru-cache": "^9.1.1 || ^10.0.0",
                                 "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
                             },
                             "dependencies": {
                                 "lru-cache": {
-                                    "version": "10.2.0"
+                                    "version": "10.2.0",
+                                    "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
+                                    "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q=="
                                 }
                             }
                         },
                         "postcss-selector-parser": {
                             "version": "6.0.15",
+                            "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz",
+                            "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==",
                             "requires": {
                                 "cssesc": "^3.0.0",
                                 "util-deprecate": "^1.0.2"
                             }
                         },
                         "proc-log": {
-                            "version": "3.0.0"
+                            "version": "3.0.0",
+                            "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz",
+                            "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A=="
                         },
                         "promise-all-reject-late": {
-                            "version": "1.0.1"
+                            "version": "1.0.1",
+                            "resolved": "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz",
+                            "integrity": "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw=="
                         },
                         "promise-call-limit": {
-                            "version": "1.0.2"
+                            "version": "1.0.2",
+                            "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-1.0.2.tgz",
+                            "integrity": "sha512-1vTUnfI2hzui8AEIixbdAJlFY4LFDXqQswy/2eOlThAscXCY4It8FdVuI0fMJGAB2aWGbdQf/gv0skKYXmdrHA=="
                         },
                         "promise-inflight": {
-                            "version": "1.0.1"
+                            "version": "1.0.1",
+                            "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
+                            "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g=="
                         },
                         "promise-retry": {
                             "version": "2.0.1",
+                            "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
+                            "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
                             "requires": {
                                 "err-code": "^2.0.2",
                                 "retry": "^0.12.0"
@@ -9776,24 +10293,34 @@
                         },
                         "promzard": {
                             "version": "1.0.0",
+                            "resolved": "https://registry.npmjs.org/promzard/-/promzard-1.0.0.tgz",
+                            "integrity": "sha512-KQVDEubSUHGSt5xLakaToDFrSoZhStB8dXLzk2xvwR67gJktrHFvpR63oZgHyK19WKbHFLXJqCPXdVR3aBP8Ig==",
                             "requires": {
                                 "read": "^2.0.0"
                             }
                         },
                         "qrcode-terminal": {
-                            "version": "0.12.0"
+                            "version": "0.12.0",
+                            "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz",
+                            "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ=="
                         },
                         "read": {
                             "version": "2.1.0",
+                            "resolved": "https://registry.npmjs.org/read/-/read-2.1.0.tgz",
+                            "integrity": "sha512-bvxi1QLJHcaywCAEsAk4DG3nVoqiY2Csps3qzWalhj5hFqRn1d/OixkFXtLO1PrgHUcAP0FNaSY/5GYNfENFFQ==",
                             "requires": {
                                 "mute-stream": "~1.0.0"
                             }
                         },
                         "read-cmd-shim": {
-                            "version": "4.0.0"
+                            "version": "4.0.0",
+                            "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz",
+                            "integrity": "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q=="
                         },
                         "read-package-json": {
                             "version": "6.0.4",
+                            "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.4.tgz",
+                            "integrity": "sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw==",
                             "requires": {
                                 "glob": "^10.2.2",
                                 "json-parse-even-better-errors": "^3.0.0",
@@ -9803,6 +10330,8 @@
                         },
                         "read-package-json-fast": {
                             "version": "3.0.2",
+                            "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz",
+                            "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==",
                             "requires": {
                                 "json-parse-even-better-errors": "^3.0.0",
                                 "npm-normalize-package-bin": "^3.0.0"
@@ -9810,6 +10339,8 @@
                         },
                         "readable-stream": {
                             "version": "3.6.2",
+                            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+                            "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
                             "requires": {
                                 "inherits": "^2.0.3",
                                 "string_decoder": "^1.1.1",
@@ -9817,16 +10348,22 @@
                             }
                         },
                         "retry": {
-                            "version": "0.12.0"
+                            "version": "0.12.0",
+                            "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+                            "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="
                         },
                         "rimraf": {
                             "version": "3.0.2",
+                            "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+                            "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
                             "requires": {
                                 "glob": "^7.1.3"
                             },
                             "dependencies": {
                                 "brace-expansion": {
                                     "version": "1.1.11",
+                                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+                                    "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
                                     "requires": {
                                         "balanced-match": "^1.0.0",
                                         "concat-map": "0.0.1"
@@ -9834,6 +10371,8 @@
                                 },
                                 "glob": {
                                     "version": "7.2.3",
+                                    "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+                                    "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
                                     "requires": {
                                         "fs.realpath": "^1.0.0",
                                         "inflight": "^1.0.4",
@@ -9845,6 +10384,8 @@
                                 },
                                 "minimatch": {
                                     "version": "3.1.2",
+                                    "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+                                    "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
                                     "requires": {
                                         "brace-expansion": "^1.1.7"
                                     }
@@ -9852,20 +10393,28 @@
                             }
                         },
                         "safe-buffer": {
-                            "version": "5.2.1"
+                            "version": "5.2.1",
+                            "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+                            "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
                         },
                         "safer-buffer": {
                             "version": "2.1.2",
+                            "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+                            "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
                             "optional": true
                         },
                         "semver": {
                             "version": "7.6.0",
+                            "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
+                            "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
                             "requires": {
                                 "lru-cache": "^6.0.0"
                             },
                             "dependencies": {
                                 "lru-cache": {
                                     "version": "6.0.0",
+                                    "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+                                    "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
                                     "requires": {
                                         "yallist": "^4.0.0"
                                     }
@@ -9873,22 +10422,32 @@
                             }
                         },
                         "set-blocking": {
-                            "version": "2.0.0"
+                            "version": "2.0.0",
+                            "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+                            "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
                         },
                         "shebang-command": {
                             "version": "2.0.0",
+                            "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+                            "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
                             "requires": {
                                 "shebang-regex": "^3.0.0"
                             }
                         },
                         "shebang-regex": {
-                            "version": "3.0.0"
+                            "version": "3.0.0",
+                            "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+                            "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
                         },
                         "signal-exit": {
-                            "version": "4.1.0"
+                            "version": "4.1.0",
+                            "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+                            "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="
                         },
                         "sigstore": {
                             "version": "1.9.0",
+                            "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-1.9.0.tgz",
+                            "integrity": "sha512-0Zjz0oe37d08VeOtBIuB6cRriqXse2e8w+7yIy2XSXjshRKxbc2KkhXjL229jXSxEm7UbcjS76wcJDGQddVI9A==",
                             "requires": {
                                 "@sigstore/bundle": "^1.1.0",
                                 "@sigstore/protobuf-specs": "^0.2.0",
@@ -9898,10 +10457,14 @@
                             }
                         },
                         "smart-buffer": {
-                            "version": "4.2.0"
+                            "version": "4.2.0",
+                            "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
+                            "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="
                         },
                         "socks": {
                             "version": "2.8.1",
+                            "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.1.tgz",
+                            "integrity": "sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ==",
                             "requires": {
                                 "ip-address": "^9.0.5",
                                 "smart-buffer": "^4.2.0"
@@ -9909,6 +10472,8 @@
                         },
                         "socks-proxy-agent": {
                             "version": "7.0.0",
+                            "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz",
+                            "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==",
                             "requires": {
                                 "agent-base": "^6.0.2",
                                 "debug": "^4.3.3",
@@ -9917,40 +10482,44 @@
                         },
                         "spdx-correct": {
                             "version": "3.2.0",
+                            "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
+                            "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
                             "requires": {
                                 "spdx-expression-parse": "^3.0.0",
                                 "spdx-license-ids": "^3.0.0"
                             }
                         },
                         "spdx-exceptions": {
-                            "version": "2.5.0"
+                            "version": "2.5.0",
+                            "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
+                            "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="
                         },
                         "spdx-expression-parse": {
                             "version": "3.0.1",
+                            "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
+                            "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
                             "requires": {
                                 "spdx-exceptions": "^2.1.0",
                                 "spdx-license-ids": "^3.0.0"
                             }
                         },
                         "spdx-license-ids": {
-                            "version": "3.0.17"
+                            "version": "3.0.17",
+                            "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz",
+                            "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg=="
                         },
                         "ssri": {
                             "version": "10.0.5",
+                            "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.5.tgz",
+                            "integrity": "sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A==",
                             "requires": {
                                 "minipass": "^7.0.3"
                             }
                         },
                         "string-width": {
-                            "version": "4.2.3",
-                            "requires": {
-                                "emoji-regex": "^8.0.0",
-                                "is-fullwidth-code-point": "^3.0.0",
-                                "strip-ansi": "^6.0.1"
-                            }
-                        },
-                        "string-width-cjs": {
                             "version": "npm:string-width@4.2.3",
+                            "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+                            "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
                             "requires": {
                                 "emoji-regex": "^8.0.0",
                                 "is-fullwidth-code-point": "^3.0.0",
@@ -9959,27 +10528,29 @@
                         },
                         "string_decoder": {
                             "version": "1.3.0",
+                            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+                            "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
                             "requires": {
                                 "safe-buffer": "~5.2.0"
                             }
                         },
                         "strip-ansi": {
-                            "version": "6.0.1",
-                            "requires": {
-                                "ansi-regex": "^5.0.1"
-                            }
-                        },
-                        "strip-ansi-cjs": {
                             "version": "npm:strip-ansi@6.0.1",
+                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
                             "requires": {
                                 "ansi-regex": "^5.0.1"
                             }
                         },
                         "supports-color": {
-                            "version": "9.4.0"
+                            "version": "9.4.0",
+                            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz",
+                            "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw=="
                         },
                         "tar": {
                             "version": "6.2.0",
+                            "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz",
+                            "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==",
                             "requires": {
                                 "chownr": "^2.0.0",
                                 "fs-minipass": "^2.0.0",
@@ -9991,12 +10562,16 @@
                             "dependencies": {
                                 "fs-minipass": {
                                     "version": "2.1.0",
+                                    "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
+                                    "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
                                     "requires": {
                                         "minipass": "^3.0.0"
                                     },
                                     "dependencies": {
                                         "minipass": {
                                             "version": "3.3.6",
+                                            "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+                                            "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
                                             "requires": {
                                                 "yallist": "^4.0.0"
                                             }
@@ -10004,21 +10579,31 @@
                                     }
                                 },
                                 "minipass": {
-                                    "version": "5.0.0"
+                                    "version": "5.0.0",
+                                    "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+                                    "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="
                                 }
                             }
                         },
                         "text-table": {
-                            "version": "0.2.0"
+                            "version": "0.2.0",
+                            "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+                            "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="
                         },
                         "tiny-relative-date": {
-                            "version": "1.3.0"
+                            "version": "1.3.0",
+                            "resolved": "https://registry.npmjs.org/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz",
+                            "integrity": "sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A=="
                         },
                         "treeverse": {
-                            "version": "3.0.0"
+                            "version": "3.0.0",
+                            "resolved": "https://registry.npmjs.org/treeverse/-/treeverse-3.0.0.tgz",
+                            "integrity": "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ=="
                         },
                         "tuf-js": {
                             "version": "1.1.7",
+                            "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.7.tgz",
+                            "integrity": "sha512-i3P9Kgw3ytjELUfpuKVDNBJvk4u5bXL6gskv572mcevPbSKCV3zt3djhmlEQ65yERjIbOSncy7U4cQJaB1CBCg==",
                             "requires": {
                                 "@tufjs/models": "1.0.4",
                                 "debug": "^4.3.4",
@@ -10027,21 +10612,29 @@
                         },
                         "unique-filename": {
                             "version": "3.0.0",
+                            "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz",
+                            "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==",
                             "requires": {
                                 "unique-slug": "^4.0.0"
                             }
                         },
                         "unique-slug": {
                             "version": "4.0.0",
+                            "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz",
+                            "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==",
                             "requires": {
                                 "imurmurhash": "^0.1.4"
                             }
                         },
                         "util-deprecate": {
-                            "version": "1.0.2"
+                            "version": "1.0.2",
+                            "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+                            "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
                         },
                         "validate-npm-package-license": {
                             "version": "3.0.4",
+                            "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+                            "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
                             "requires": {
                                 "spdx-correct": "^3.0.0",
                                 "spdx-expression-parse": "^3.0.0"
@@ -10049,66 +10642,45 @@
                         },
                         "validate-npm-package-name": {
                             "version": "5.0.0",
+                            "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz",
+                            "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==",
                             "requires": {
                                 "builtins": "^5.0.0"
                             }
                         },
                         "walk-up-path": {
-                            "version": "3.0.1"
+                            "version": "3.0.1",
+                            "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz",
+                            "integrity": "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA=="
                         },
                         "wcwidth": {
                             "version": "1.0.1",
+                            "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+                            "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
                             "requires": {
                                 "defaults": "^1.0.3"
                             }
                         },
                         "which": {
                             "version": "3.0.1",
+                            "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz",
+                            "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==",
                             "requires": {
                                 "isexe": "^2.0.0"
                             }
                         },
                         "wide-align": {
                             "version": "1.1.5",
+                            "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
+                            "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
                             "requires": {
                                 "string-width": "^1.0.2 || 2 || 3 || 4"
                             }
                         },
                         "wrap-ansi": {
-                            "version": "8.1.0",
-                            "requires": {
-                                "ansi-styles": "^6.1.0",
-                                "string-width": "^5.0.1",
-                                "strip-ansi": "^7.0.1"
-                            },
-                            "dependencies": {
-                                "ansi-regex": {
-                                    "version": "6.0.1"
-                                },
-                                "ansi-styles": {
-                                    "version": "6.2.1"
-                                },
-                                "emoji-regex": {
-                                    "version": "9.2.2"
-                                },
-                                "string-width": {
-                                    "version": "5.1.2",
-                                    "requires": {
-                                        "eastasianwidth": "^0.2.0",
-                                        "emoji-regex": "^9.2.2",
-                                        "strip-ansi": "^7.0.1"
-                                    }
-                                },
-                                "strip-ansi": {
-                                    "version": "7.1.0",
-                                    "requires": {
-                                        "ansi-regex": "^6.0.1"
-                                    }
-                                }
-                            }
-                        },
-                        "wrap-ansi-cjs": {
                             "version": "npm:wrap-ansi@7.0.0",
+                            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+                            "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
                             "requires": {
                                 "ansi-styles": "^4.0.0",
                                 "string-width": "^4.1.0",
@@ -10116,17 +10688,23 @@
                             }
                         },
                         "wrappy": {
-                            "version": "1.0.2"
+                            "version": "1.0.2",
+                            "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+                            "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
                         },
                         "write-file-atomic": {
                             "version": "5.0.1",
+                            "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
+                            "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==",
                             "requires": {
                                 "imurmurhash": "^0.1.4",
                                 "signal-exit": "^4.0.1"
                             }
                         },
                         "yallist": {
-                            "version": "4.0.0"
+                            "version": "4.0.0",
+                            "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+                            "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
                         }
                     }
                 },
@@ -10134,6 +10712,52 @@
                     "version": "4.12.0",
                     "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz",
                     "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw=="
+                },
+                "string-width": {
+                    "version": "4.2.3",
+                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+                    "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+                    "requires": {
+                        "emoji-regex": "^8.0.0",
+                        "is-fullwidth-code-point": "^3.0.0",
+                        "strip-ansi": "^6.0.1"
+                    }
+                },
+                "string-width-cjs": {
+                    "version": "npm:string-width@4.2.3",
+                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+                    "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+                    "requires": {
+                        "emoji-regex": "^8.0.0",
+                        "is-fullwidth-code-point": "^3.0.0",
+                        "strip-ansi": "^6.0.1"
+                    }
+                },
+                "strip-ansi": {
+                    "version": "6.0.1",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                    "requires": {
+                        "ansi-regex": "^5.0.1"
+                    }
+                },
+                "strip-ansi-cjs": {
+                    "version": "npm:strip-ansi@6.0.1",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                    "requires": {
+                        "ansi-regex": "^5.0.1"
+                    }
+                },
+                "wrap-ansi-cjs": {
+                    "version": "npm:wrap-ansi@7.0.0",
+                    "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+                    "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+                    "requires": {
+                        "ansi-styles": "^4.0.0",
+                        "string-width": "^4.1.0",
+                        "strip-ansi": "^6.0.0"
+                    }
                 }
             }
         },
@@ -18430,6 +19054,14 @@
                             "version": "6.0.1",
                             "bundled": true
                         },
+                        "ansi-styles": {
+                            "version": "4.3.0",
+                            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+                            "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+                            "requires": {
+                                "color-convert": "^2.0.1"
+                            }
+                        },
                         "emoji-regex": {
                             "version": "9.2.2",
                             "bundled": true
@@ -18443,12 +19075,97 @@
                                 "strip-ansi": "^7.0.1"
                             }
                         },
+                        "string-width-cjs": {
+                            "version": "npm:string-width@4.2.3",
+                            "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+                            "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+                            "requires": {
+                                "emoji-regex": "^8.0.0",
+                                "is-fullwidth-code-point": "^3.0.0",
+                                "strip-ansi": "^6.0.1"
+                            },
+                            "dependencies": {
+                                "ansi-regex": {
+                                    "version": "5.0.1",
+                                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                                    "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                                },
+                                "emoji-regex": {
+                                    "version": "8.0.0",
+                                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+                                    "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+                                },
+                                "strip-ansi": {
+                                    "version": "6.0.1",
+                                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                                    "requires": {
+                                        "ansi-regex": "^5.0.1"
+                                    }
+                                }
+                            }
+                        },
                         "strip-ansi": {
                             "version": "7.1.0",
                             "bundled": true,
                             "requires": {
                                 "ansi-regex": "^6.0.1"
                             }
+                        },
+                        "strip-ansi-cjs": {
+                            "version": "npm:strip-ansi@6.0.1",
+                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                            "requires": {
+                                "ansi-regex": "^5.0.1"
+                            },
+                            "dependencies": {
+                                "ansi-regex": {
+                                    "version": "5.0.1",
+                                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                                    "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                                }
+                            }
+                        },
+                        "wrap-ansi-cjs": {
+                            "version": "npm:wrap-ansi@7.0.0",
+                            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+                            "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+                            "requires": {
+                                "ansi-styles": "^4.0.0",
+                                "string-width": "^4.1.0",
+                                "strip-ansi": "^6.0.0"
+                            },
+                            "dependencies": {
+                                "ansi-regex": {
+                                    "version": "5.0.1",
+                                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                                    "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                                },
+                                "emoji-regex": {
+                                    "version": "8.0.0",
+                                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+                                    "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+                                },
+                                "string-width": {
+                                    "version": "4.2.3",
+                                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+                                    "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+                                    "requires": {
+                                        "emoji-regex": "^8.0.0",
+                                        "is-fullwidth-code-point": "^3.0.0",
+                                        "strip-ansi": "^6.0.1"
+                                    }
+                                },
+                                "strip-ansi": {
+                                    "version": "6.0.1",
+                                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                                    "requires": {
+                                        "ansi-regex": "^5.0.1"
+                                    }
+                                }
+                            }
                         }
                     }
                 },
@@ -19667,7 +20384,7 @@
                     }
                 },
                 "string-width-cjs": {
-                    "version": "npm:string-width@4.2.3",
+                    "version": "npm:string-width-cjs@4.2.3",
                     "bundled": true,
                     "requires": {
                         "emoji-regex": "^8.0.0",
@@ -19683,7 +20400,7 @@
                     }
                 },
                 "strip-ansi-cjs": {
-                    "version": "npm:strip-ansi@6.0.1",
+                    "version": "npm:strip-ansi-cjs@6.0.1",
                     "bundled": true,
                     "requires": {
                         "ansi-regex": "^5.0.1"
@@ -19841,7 +20558,7 @@
                     }
                 },
                 "wrap-ansi-cjs": {
-                    "version": "npm:wrap-ansi@7.0.0",
+                    "version": "npm:wrap-ansi-cjs@7.0.0",
                     "bundled": true,
                     "requires": {
                         "ansi-styles": "^4.0.0",
@@ -23006,36 +23723,6 @@
                 "strip-ansi": "^7.0.1"
             }
         },
-        "string-width-cjs": {
-            "version": "npm:string-width@4.2.3",
-            "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
-            "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-            "requires": {
-                "emoji-regex": "^8.0.0",
-                "is-fullwidth-code-point": "^3.0.0",
-                "strip-ansi": "^6.0.1"
-            },
-            "dependencies": {
-                "ansi-regex": {
-                    "version": "5.0.1",
-                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-                    "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
-                },
-                "emoji-regex": {
-                    "version": "8.0.0",
-                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-                    "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
-                },
-                "strip-ansi": {
-                    "version": "6.0.1",
-                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-                    "requires": {
-                        "ansi-regex": "^5.0.1"
-                    }
-                }
-            }
-        },
         "string.prototype.codepointat": {
             "version": "0.2.1",
             "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
@@ -23142,21 +23829,6 @@
                 "ansi-regex": "^6.0.1"
             }
         },
-        "strip-ansi-cjs": {
-            "version": "npm:strip-ansi@6.0.1",
-            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-            "requires": {
-                "ansi-regex": "^5.0.1"
-            },
-            "dependencies": {
-                "ansi-regex": {
-                    "version": "5.0.1",
-                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-                    "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
-                }
-            }
-        },
         "strip-bom": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
@@ -25138,67 +25810,6 @@
                 }
             }
         },
-        "wrap-ansi-cjs": {
-            "version": "npm:wrap-ansi@7.0.0",
-            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
-            "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
-            "requires": {
-                "ansi-styles": "^4.0.0",
-                "string-width": "^4.1.0",
-                "strip-ansi": "^6.0.0"
-            },
-            "dependencies": {
-                "ansi-regex": {
-                    "version": "5.0.1",
-                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-                    "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
-                },
-                "ansi-styles": {
-                    "version": "4.3.0",
-                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-                    "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-                    "requires": {
-                        "color-convert": "^2.0.1"
-                    }
-                },
-                "color-convert": {
-                    "version": "2.0.1",
-                    "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-                    "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-                    "requires": {
-                        "color-name": "~1.1.4"
-                    }
-                },
-                "color-name": {
-                    "version": "1.1.4",
-                    "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-                    "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
-                },
-                "emoji-regex": {
-                    "version": "8.0.0",
-                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-                    "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
-                },
-                "string-width": {
-                    "version": "4.2.3",
-                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
-                    "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-                    "requires": {
-                        "emoji-regex": "^8.0.0",
-                        "is-fullwidth-code-point": "^3.0.0",
-                        "strip-ansi": "^6.0.1"
-                    }
-                },
-                "strip-ansi": {
-                    "version": "6.0.1",
-                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-                    "requires": {
-                        "ansi-regex": "^5.0.1"
-                    }
-                }
-            }
-        },
         "wrappy": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
diff --git a/package.json b/package.json
index f46fe81a5..e83e6b01b 100644
--- a/package.json
+++ b/package.json
@@ -272,6 +272,7 @@
         "react-compound-slider": "^3.4.0",
         "react-datepicker": "^6.1.0",
         "react-dom": "^18.2.0",
+        "react-draggable": "^4.4.6",
         "react-grid-layout": "^1.4.4",
         "react-icons": "^5.0.1",
         "react-jsx-parser": "^1.29.0",
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 5dc818db8..585398204 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -43,7 +43,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
         model: 'gpt-4o',
         maxTokens: 2048,
         temp: 0.5,
-        prompt: "I'm going to give you a list of descriptions. Each one is seperated by ====== on either side. They will vary in length, so make sure to only seperate when you see ======. Sort them into lists by shared content. MAKE SURE EACH DESCRIPTOR IS IN ONLY ONE LIST. Generate only the list with each list seperated by ====== with the elements seperated by ~~~~~~. Try to do around 4 groups, but a little more or less is ok.",
+        prompt: "I'm going to give you a list of descriptions. Each one is separated by `======` on either side. Descriptions will vary in length, so make sure to only separate when you see `======`. Sort them into lists by shared content. Make sure each description is in only one list. Each list should be separated by `======` with the elements within it separated by `~~~~~~`. Immediately afterward, surrounded by `------` on BOTH SIDES, provide some insight into your reasoning for the way you sorted. It is VERY important that you format it exactly as described, ensuring the proper number of `=` `~` and `-` (6 of each) and no commas.Try to create around 4 groups, but a little more or less is ok. Also, I may provide some more insight after this colon:"
     },
     describe: { model: 'gpt-4-vision-preview', maxTokens: 2048, temp: 0, prompt: 'Describe these images in 3-5 words' },
     chatcard: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Answer the following question as a short flashcard response. Do not include a label.' },
diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
index 8a721a626..5f7d1b901 100644
--- a/src/client/views/collections/CollectionCardDeckView.scss
+++ b/src/client/views/collections/CollectionCardDeckView.scss
@@ -26,23 +26,41 @@
     display: flex;
     padding: 3px;
     // width: 300px;
+    // height:100px;
+    pointer-events: none; /* This ensures the container does not capture hover events */
+
     background-color: rgb(218, 218, 218); /* Background color of the container */
     border-radius: 50px; /* Rounds the corners of the container */
     transform: translateY(25px);
     // box-shadow: 0 4px 8px rgba(0,0,0,0.1); /* Optional: Adds shadow for depth */
     align-items: center; /* Centers buttons vertically */
     justify-content: start; /* Centers buttons horizontally */
+
+    button {
+        pointer-events: auto; /* Re-enable pointer events for the buttons */
+    
+        width: 70px;
+        height: 70px;
+        border-radius: 50%;
+        background-color: $dark-gray;
+        // border-color: $medium-blue;
+        margin: 5px; // transform: translateY(-50px);
+        background-color: transparent;
+    }
 }
 
-button {
-    width: 35px;
-    height: 35px;
-    border-radius: 50%;
-    background-color: $dark-gray;
-    // border-color: $medium-blue;
-    margin: 5px; // transform: translateY(-50px);
+.no-card-span{
+    position: relative;
+    width: fit-content;
+    text-align: center;
+    font-size: 65px;
+    
+
+
 }
 
+
+
 // button:hover {
 //     transform: translateY(-50px);
 // }
@@ -74,6 +92,13 @@ button {
     flex-direction: column;
 }
 
+// .card-item:hover {
+//     box-shadow: 0 20px 20px $medium-blue;
+//     transform: scale(1.05);
+    
+
+// }
+
 .card-item-inactive {
     opacity: 0.5;
 }
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index d68fb57f8..7ea345e10 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -20,6 +20,13 @@ 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';
+
 
 enum cardSortings {
     Time = 'time',
@@ -72,8 +79,24 @@ export class CollectionCardView extends CollectionSubView() {
     constructor(props: any) {
         super(props);
         makeObservable(this);
+        this.setRegenerateCallback();
+
+    }
+
+    setRegenerateCallback() {
+        GPTPopup.Instance.setRegenerateCallback(this.childPairStringListAndUpdateSortDesc);
     }
 
+    @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) => this.processGptOutput(sortResult);
+
+    };
+
+
+
     componentDidMount(): void {
         this._disposers.sort = reaction(
             () => ({ cardSort: this.cardSort, field: this.cardSort_customField }),
@@ -331,7 +354,8 @@ export class CollectionCardView extends CollectionSubView() {
      */
     calculateTranslateY = (isHovered: boolean, isSelected: boolean, realIndex: number, amCards: number, calcRowIndex: number) => {
         if (isSelected || amCards == 1) return 50 * this.fitContentScale;
-        const trans = isHovered ? this.translateHover(realIndex) : 0;
+        // const trans = isHovered ? this.translateHover(realIndex) : 0;
+        const trans = 0
         return trans + this.translateY(amCards, calcRowIndex, realIndex);
     };
 
@@ -433,26 +457,50 @@ export class CollectionCardView extends CollectionSubView() {
         if (cardSort !== cardSortings.Custom) return '';
         const amButtons = Math.max(4, this.childDocs?.reduce((set, d) => this.cardSort_customField && set.add(NumCast(d[this.cardSort_customField])), new Set<number>()).size ?? 0);
         const activeButtonIndex = CollectionCardView.getButtonGroup(this.cardSort_customField, doc);
-        const totalWidth = amButtons * 35 + amButtons * 2 * 5 + 6;
+        const totalWidth = amButtons * 72 + amButtons * 2 * 5 + 6;
         return (
-            <div className="card-button-container" style={{ width: `${totalWidth}px` }}>
+            <div className="card-button-container" style={{ width: `${totalWidth}px`, fontSize: '50px' }} >
                 {numberRange(amButtons).map(i => (
                     // eslint-disable-next-line jsx-a11y/control-has-associated-label
-                    <button
-                        key={i}
-                        type="button"
-                        style={{ backgroundColor: activeButtonIndex === i ? '#4476f7' : '#323232' }} //
-                        onClick={() => this.toggleButton(i, doc)}
-                    />
+                    <Tooltip title={<div key = {i} className="dash-tooltip">Click to add/ remove this card from group {i +1}</div>}>
+
+                        <button key={i} type="button" 
+                        // style={{ backgroundColor: activeButtonIndex === i ? '#4476f7' : '#323232' }} 
+                        onClick={() => this.toggleButton(i, doc)}>
+                            {this.getButtonIcons(activeButtonIndex === i)}
+                        </button>
+
+                    </Tooltip>
+
                 ))}
             </div>
         );
     };
+
+    getButtonIcons = (isActive: boolean) => {
+        switch (this.cardSort_customField) {
+            case 'like':
+                return <FontAwesomeIcon icon = 'heart' size= 'lg' style = {{color: isActive ? '#4476f7' : '#323232'}}/>;
+            case 'chat':
+                return <FontAwesomeIcon icon = 'robot' size= 'lg' style = {{color: isActive ? '#4476f7' : '#323232'}}/>;
+            case 'idea':
+                return <FontAwesomeIcon icon = 'cloud' size= 'lg' style = {{color: isActive ? '#4476f7' : '#323232'}}/>;
+            default:
+                return <FontAwesomeIcon icon = 'star' size= 'lg' style = {{color: isActive ? '#4476f7' : '#323232'}}/>;
+        }
+    };
     /**
      * Actually renders all the cards
      */
     renderCards = () => {
         const anySelected = this.childDocs.some(doc => DocumentView.SelectedDocs().includes(doc));
+        const isEmpty = this.childDocsWithoutLinks.length === 0;
+
+        if (isEmpty) {
+            return <span className = 'no-card-span' style= {{width: `100%`}}> 
+            Sorry ! There are no cards in this group</span>
+        }
+
         // Map sorted documents to their rendered components
         return this.sortedDocs.map((doc, index) => {
             const realIndex = this.sortedDocs.filter(sortDoc => !DocumentView.SelectedDocs().includes(sortDoc)).indexOf(doc);
@@ -486,23 +534,27 @@ export class CollectionCardView extends CollectionSubView() {
                     }}
                     style={{
                         width: this.panelWidth(),
-                        height: 'max-content', // this.panelHeight(childPair.layout)(),
+                        height: 'max-content',
                         transform: `translateY(${this.calculateTranslateY(this._hoveredNodeIndex === index, isSelected, realIndex, amCards, calcRowIndex)}px)
                                     translateX(${isSelected ? this.translateSelected(calcRowIndex) : this.translateOverflowX(realIndex, amCards)}px)
                                     rotate(${!isSelected ? this.rotate(amCards, calcRowIndex) : 0}deg)
-                                    scale(${isSelected ? 1.25 : 1})`,
-                    }}
+                                    scale(${isSelected ? 1.25 : this._hoveredNodeIndex === index ? 1.05 : 1})`,
+                                }}
                     onMouseEnter={() => this.setHoveredNodeIndex(index)}>
                     {this.displayDoc(doc, childScreenToLocal)}
                     {this.renderButtons(doc, this.cardSort)}
                 </div>
             );
-        });
+        }
+        
+        
+        );
     };
 
 
 
     render() {
+    
         return (
             <div
                 className="collectionCardView-outer"
@@ -533,4 +585,5 @@ export class CollectionCardView extends CollectionSubView() {
             </div>
         );
     }
+    
 }
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 549cb5c8f..cef913837 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -352,24 +352,23 @@ ScriptingGlobals.add(function setCardSort(value?: any, checkResult?: boolean) {
         switch(value){
             case 'Custom 1':
                 doc.cardSort_customField = "like";
-                doc.cardSort_visibleSortGroups = new List<number>();
                 break;
             case 'Custom 2':
                 doc.cardSort_customField = "star";
-                doc.cardSort_visibleSortGroups = new List<number>();
                 break;
             case 'Custom 3':
                 doc.cardSort_customField = "idea";
-                doc.cardSort_visibleSortGroups = new List<number>();
                 break;
             case 'Chat GPT':
                 doc.cardSort = "custom";
                 doc.cardSort_customField = "chat";
-                doc.cardSort_visibleSortGroups = new List<number>();
                 break;
             default:
                 break;
         }
+
+        doc.cardSort_visibleSortGroups = new List<number>();
+
     }
 
     // const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index 6d8793f82..042b1dea5 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -84,6 +84,7 @@ $highlightedText: #82e0ff;
         font-size: 9px;
         padding: 10px;
         color: #ffffff;
+        width: 100%;
         background-color: $button;
         border-radius: 5px;
     }
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index c0f17ba4e..468922532 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -20,6 +20,7 @@ import './GPTPopup.scss';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 
+
 export enum GPTPopupMode {
     SUMMARY,
     EDIT,
@@ -149,6 +150,28 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         this.cardsDoneLoading = done;
     }
 
+    @observable sortRespText: string = ''
+
+    @action setSortRespText(resp: string) {
+        this.sortRespText = resp
+    }
+
+
+    @observable chatSortPrompt: string = ""
+
+    sortPromptChanged = action((e: React.ChangeEvent<HTMLInputElement>) => {
+        this.chatSortPrompt = e.target.value;
+    });
+
+    @observable private regenerateCallback: (() => Promise<void>) | null = null;
+
+    @action public setRegenerateCallback(callback: () => Promise<void>) {
+        this.regenerateCallback = callback;
+    }
+
+
+
+
     public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false;
     public createFilteredDoc: (axes?: any) => boolean = () => false;
     public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined;
@@ -160,21 +183,34 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         this.setLoading(true);
         this.setSortDone(false);
 
+        if (this.regenerateCallback) {
+            await this.regenerateCallback();
+        }
+    
         try {
-            const res = await gptAPICall(this.sortDesc, GPTCallType.SORT);
+            const res = await gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt);
             // Trigger the callback with the result
             if (this.onSortComplete) {
                 this.onSortComplete(res || 'Something went wrong :(');
+    
+                // Extract explanation surrounded by ------ at the top or both at the top and bottom
+                const explanationMatch = res.match(/------\s*([\s\S]*?)\s*(?:------|$)/) || [];
+                const explanation = explanationMatch[1] ? explanationMatch[1].trim() : 'No explanation found';
+    
+                // Set the extracted explanation to sortRespText
+                this.setSortRespText(explanation);
+    
                 console.log(res);
             }
         } catch (err) {
             console.error(err);
         }
-
+    
         this.setLoading(false);
         this.setSortDone(true);
     };
-
+    
+    
     /**
      * Generates a Dalle image and uploads it to the server.
      */
@@ -315,49 +351,75 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     };
 
     sortBox = () => (
-        <>
-            <div>
+    <>
+        <div>
             {this.heading('SORTING')}
-                    <>
-                        {!this.cardsDoneLoading || this.loading ? (
-                            <div className="content-wrapper">
-                                <div className="loading-spinner">
-                                    <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} />
-                                    {this.loading ? <span>Loading...</span> : <span>Reading Cards...</span>}
-                                </div>
+            <>
+                {!this.cardsDoneLoading || this.loading ? (
+                    <div className="content-wrapper">
+                        <div className="loading-spinner">
+                            <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} />
+                            {this.loading ? <span>Loading...</span> : <span>Reading Cards...</span>}
+                        </div>
+                    </div>
+                ) : (
+                    !this.sortDone && (
+                        <>
+                            <div className="btns-wrapper-gpt">
+                                <input
+                                    className="searchBox-input"
+                                    defaultValue=""
+                                    autoComplete="off"
+                                    onChange={this.sortPromptChanged}
+                                    onKeyDown={e => {
+                                        if (e.key === 'Enter') {
+                                            this.generateSort();
+                                        }
+                                        e.stopPropagation();
+                                    }}
+                                    type="text"
+                                    placeholder="How do you want to sort your cards ?"
+                                    id="search-input"
+                                    style={{ width: '100%' }}
+                                />
                             </div>
-                        ) : (
-                            !this.sortDone && (
-                                <div className="btns-wrapper-gpt">
-                                    <Button
-                                        tooltip="Have ChatGPT sort your cards for you!"
-                                        text="Sort!"
-                                        onClick={this.generateSort}
-                                        color={StrCast(Doc.UserDoc().userVariantColor)}
-                                        type={Type.TERT}
-                                        style={{
-                                            width: '90%',
-                                            textAlign: 'center',
-                                            color: '#ffffff', 
-                                            fontSize: '16px', 
-                                        }}
-                                    />
-                                </div>
-                            )
-                        )}
-
-                        {this.sortDone && (
-                            <div>
-                                <div className="content-wrapper">
-                                    <p>{this.text === 'Something went wrong :(' ? 'Something went wrong :(' : 'Sorting done! Feel free to move things around / regenerate :) !'}</p>
-                                    <IconButton tooltip="Generate Again" onClick={() => this.setSortDone(false)} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />
-                                </div>
+                            <div className="btns-wrapper-gpt">
+                                <Button
+                                    tooltip="Have ChatGPT sort your cards for you!"
+                                    text="Sort!"
+                                    onClick={this.generateSort}
+                                    color={StrCast(Doc.UserDoc().userVariantColor)}
+                                    type={Type.TERT}
+                                    style={{
+                                        width: '90%',
+                                        textAlign: 'center',
+                                        color: '#ffffff',
+                                        fontSize: '16px',
+                                    }}
+                                />
                             </div>
-                        )}
-                    </>
-            </div>
-        </>
-    );
+                        </>
+                    )
+                )}
+
+                {this.sortDone && (
+                    <div>
+                        <div className="content-wrapper">
+                            <p>{this.text === 'Something went wrong :(' ? 'Something went wrong :(' : `${this.sortRespText}`}</p>
+                            <IconButton
+                                tooltip="Generate Again"
+                                onClick={() => this.setSortDone(false)}
+                                icon={<FontAwesomeIcon icon="redo-alt" size="lg" />}
+                                color={StrCast(Doc.UserDoc().userVariantColor)}
+                            />
+                        </div>
+                    </div>
+                )}
+            </>
+        </div>
+    </>
+);
+
     imageBox = () => (
         <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
             {this.heading('GENERATED IMAGE')}
@@ -509,9 +571,12 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
 
     render() {
         return (
+
             <div className="summary-box" style={{ display: this.visible ? 'flex' : 'none' }}>
                 {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.DATA ? this.dataAnalysisBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : this.mode === GPTPopupMode.SORT ? this.sortBox() : null}
             </div>
+
+
         );
     }
 }
-- 
cgit v1.2.3-70-g09d2


From ad4d67f2ace0b658917ff63e2ff83f95022cc2d4 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Mon, 17 Jun 2024 12:02:14 -0400
Subject: things are getting silly

---
 src/client/views/collections/CollectionCardDeckView.tsx | 3 +++
 1 file changed, 3 insertions(+)

(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 7ea345e10..bf814cb31 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -46,7 +46,9 @@ export class CollectionCardView extends CollectionSubView() {
     @observable _isLoading = false;
     @observable _hoveredNodeIndex = -1;
     @observable _docRefs = new ObservableMap<Doc, DocumentView>();
+    _draggerRef = React.createRef<HTMLDivElement>();
     @observable _maxRowCount = 10;
+    @observable docsBeingDragged: number[] = [];
 
     static getButtonGroup(groupFieldKey: 'chat' | 'star' | 'idea' | 'like', doc: Doc): number | undefined {
         return Cast(doc[groupFieldKey], 'number', null);
@@ -508,6 +510,7 @@ export class CollectionCardView extends CollectionSubView() {
             const amCards = this.overflowAmCardsCalc(realIndex);
             const isSelected = DocumentView.SelectedDocs().includes(doc);
 
+
             const childScreenToLocal = () => {
                 this._forceChildXf;
                 const dref = this._docRefs.get(doc);
-- 
cgit v1.2.3-70-g09d2


From d6ad264a940b4d3db87c10d9a872968d83d63b10 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Mon, 17 Jun 2024 12:33:51 -0400
Subject: sigh

---
 .../views/collections/CollectionCardDeckView.tsx   | 33 +++++++++++++++++++++-
 1 file changed, 32 insertions(+), 1 deletion(-)

(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 bf814cb31..7362a65ae 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -49,6 +49,8 @@ export class CollectionCardView extends CollectionSubView() {
     _draggerRef = React.createRef<HTMLDivElement>();
     @observable _maxRowCount = 10;
     @observable docsBeingDragged: number[] = [];
+    @observable draggedIndex: number = -1;
+    @observable overIndex: number = -1;
 
     static getButtonGroup(groupFieldKey: 'chat' | 'star' | 'idea' | 'like', doc: Doc): number | undefined {
         return Cast(doc[groupFieldKey], 'number', null);
@@ -491,6 +493,35 @@ export class CollectionCardView extends CollectionSubView() {
                 return <FontAwesomeIcon icon = 'star' size= 'lg' style = {{color: isActive ? '#4476f7' : '#323232'}}/>;
         }
     };
+
+    @action
+onDragStart = (index: number) => {
+    this.draggedIndex = index;
+};
+
+@action
+onDragOver = (index: number) => {
+    if (this.draggedIndex !== index) {
+        this.overIndex = index;
+    }
+};
+
+@action
+onDrop = () => {
+    if (this.draggedIndex !== -1 && this.overIndex !== -1) {
+        const draggedDoc = this.sortedDocs[this.draggedIndex];
+        this.sortedDocs.splice(this.draggedIndex, 1);
+        this.sortedDocs.splice(this.overIndex, 0, draggedDoc);
+        this.draggedIndex = -1;
+        this.overIndex = -1;
+    }
+};
+
+@action
+onDragEnd = () => {
+    this.draggedIndex = -1;
+    this.overIndex = -1;
+};
     /**
      * Actually renders all the cards
      */
@@ -523,7 +554,7 @@ export class CollectionCardView extends CollectionSubView() {
             return (
                 <div
                     key={doc[Id]}
-                    className={`card-item${isSelected ? '-active' : anySelected ? '-inactive' : ''}`}
+                    className={`card-item${isSelected ? '-active' : anySelected ? '-inactive' : ''}`}                    
                     onPointerUp={() => {
                         // this turns off documentDecorations during a transition, then turns them back on afterward.
                         SnappingManager.SetIsResizing(this.Document[Id]);
-- 
cgit v1.2.3-70-g09d2


From 6a3eaaea335440371698bec409952d4c82d7d628 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Mon, 17 Jun 2024 13:05:48 -0400
Subject: yay concise code

---
 .../views/collections/CollectionCardDeckView.tsx   | 114 +++++++++++----------
 1 file changed, 59 insertions(+), 55 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 7362a65ae..f50ca6349 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -451,77 +451,81 @@ export class CollectionCardView extends CollectionSubView() {
         GPTPopup.Instance.onSortComplete = (sortResult: string) => this.processGptOutput(sortResult);
     };
 
-    /**
+  /**
      * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups
-     * @param childPairIndex
      * @param doc
+     * @param cardSort
      * @returns
      */
-    renderButtons = (doc: Doc, cardSort: cardSortings) => {
-        if (cardSort !== cardSortings.Custom) return '';
-        const amButtons = Math.max(4, this.childDocs?.reduce((set, d) => this.cardSort_customField && set.add(NumCast(d[this.cardSort_customField])), new Set<number>()).size ?? 0);
-        const activeButtonIndex = CollectionCardView.getButtonGroup(this.cardSort_customField, doc);
-        const totalWidth = amButtons * 72 + amButtons * 2 * 5 + 6;
-        return (
-            <div className="card-button-container" style={{ width: `${totalWidth}px`, fontSize: '50px' }} >
-                {numberRange(amButtons).map(i => (
-                    // eslint-disable-next-line jsx-a11y/control-has-associated-label
-                    <Tooltip title={<div key = {i} className="dash-tooltip">Click to add/ remove this card from group {i +1}</div>}>
+  renderButtons = (doc: Doc, cardSort: cardSortings): JSX.Element | null => {
+    if (cardSort !== cardSortings.Custom) return null;
+
+    const amButtons = Math.max(4, this.childDocs?.reduce((set, d) => {
+        if (this.cardSort_customField) {
+            set.add(NumCast(d[this.cardSort_customField]));
+        }
+        return set;
+    }, new Set<number>()).size ?? 0);
+
+    const activeButtonIndex = CollectionCardView.getButtonGroup(this.cardSort_customField, doc);
+    const totalWidth = amButtons * 72 + amButtons * 2 * 5 + 6;
+
+    return (
+        <div className="card-button-container" style={{ width: `${totalWidth}px`, fontSize: '50px' }}>
+            {numberRange(amButtons).map(i => (
+                <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from group {i + 1}</div>}>
+                    <button type="button" onClick={() => this.toggleButton(i, doc)}>
+                        {this.getButtonIcon(activeButtonIndex === i)}
+                    </button>
+                </Tooltip>
+            ))}
+        </div>
+    );
+};
 
-                        <button key={i} type="button" 
-                        // style={{ backgroundColor: activeButtonIndex === i ? '#4476f7' : '#323232' }} 
-                        onClick={() => this.toggleButton(i, doc)}>
-                            {this.getButtonIcons(activeButtonIndex === i)}
-                        </button>
+    getButtonIcon = (isActive: boolean): JSX.Element => {
+        const iconMap: { [key: string]: any } = {
+            like: 'heart',
+            chat: 'robot',
+            idea: 'cloud'
+        };
 
-                    </Tooltip>
+        const icon = iconMap[this.cardSort_customField ?? ''] || 'star';
+        const color = isActive ? '#4476f7' : '#323232';
 
-                ))}
-            </div>
-        );
+        return <FontAwesomeIcon icon={icon} size='lg' style={{ color }} />;
     };
 
-    getButtonIcons = (isActive: boolean) => {
-        switch (this.cardSort_customField) {
-            case 'like':
-                return <FontAwesomeIcon icon = 'heart' size= 'lg' style = {{color: isActive ? '#4476f7' : '#323232'}}/>;
-            case 'chat':
-                return <FontAwesomeIcon icon = 'robot' size= 'lg' style = {{color: isActive ? '#4476f7' : '#323232'}}/>;
-            case 'idea':
-                return <FontAwesomeIcon icon = 'cloud' size= 'lg' style = {{color: isActive ? '#4476f7' : '#323232'}}/>;
-            default:
-                return <FontAwesomeIcon icon = 'star' size= 'lg' style = {{color: isActive ? '#4476f7' : '#323232'}}/>;
-        }
+
+
+        @action
+    onDragStart = (index: number) => {
+        this.draggedIndex = index;
     };
 
     @action
-onDragStart = (index: number) => {
-    this.draggedIndex = index;
-};
+    onDragOver = (index: number) => {
+        if (this.draggedIndex !== index) {
+            this.overIndex = index;
+        }
+    };
 
-@action
-onDragOver = (index: number) => {
-    if (this.draggedIndex !== index) {
-        this.overIndex = index;
-    }
-};
+    @action
+    onDrop = () => {
+        if (this.draggedIndex !== -1 && this.overIndex !== -1) {
+            const draggedDoc = this.sortedDocs[this.draggedIndex];
+            this.sortedDocs.splice(this.draggedIndex, 1);
+            this.sortedDocs.splice(this.overIndex, 0, draggedDoc);
+            this.draggedIndex = -1;
+            this.overIndex = -1;
+        }
+    };
 
-@action
-onDrop = () => {
-    if (this.draggedIndex !== -1 && this.overIndex !== -1) {
-        const draggedDoc = this.sortedDocs[this.draggedIndex];
-        this.sortedDocs.splice(this.draggedIndex, 1);
-        this.sortedDocs.splice(this.overIndex, 0, draggedDoc);
+    @action
+    onDragEnd = () => {
         this.draggedIndex = -1;
         this.overIndex = -1;
-    }
-};
-
-@action
-onDragEnd = () => {
-    this.draggedIndex = -1;
-    this.overIndex = -1;
-};
+    };
     /**
      * Actually renders all the cards
      */
-- 
cgit v1.2.3-70-g09d2


From 8003d7f8de2a888f81b294cb7a713990c8a0e907 Mon Sep 17 00:00:00 2001
From: geireann <geireann.lindfield@gmail.com>
Date: Mon, 17 Jun 2024 15:15:33 -0400
Subject: changed selected card to be relative to improve animation.

---
 .../views/collections/CollectionCardDeckView.scss  |  2 +-
 .../views/collections/CollectionCardDeckView.tsx   | 49 ++++++++++++----------
 2 files changed, 29 insertions(+), 22 deletions(-)

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

diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
index 5f7d1b901..eb1b456f5 100644
--- a/src/client/views/collections/CollectionCardDeckView.scss
+++ b/src/client/views/collections/CollectionCardDeckView.scss
@@ -104,6 +104,6 @@
 }
 
 .card-item-active {
-    position: absolute;
+  //  position: absolute;
     z-index: 100;
 }
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 7ea345e10..9ce0873bd 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -2,7 +2,7 @@ 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 { numberRange } from '../../../Utils';
+import { emptyFunction, numberRange } from '../../../Utils';
 import { Doc, NumListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
@@ -124,15 +124,6 @@ export class CollectionCardView extends CollectionSubView() {
         return (this._childDocumentWidth * length) / this._props.PanelWidth();
     }
 
-    @computed get translateWrapperX() {
-        let translate = 0;
-
-        if (this.inactiveDocs().length !== this.childDocsWithoutLinks.length && this.inactiveDocs().length < 10) {
-            translate += this.panelWidth() / 2;
-        }
-        return translate;
-    }
-
     /**
      * The child documents to be rendered-- either all of them except the Links or the docs in the currently active
      * custom group
@@ -161,6 +152,11 @@ export class CollectionCardView extends CollectionSubView() {
         return this.sort(this.childDocsWithoutLinks, this.cardSort, BoolCast(this.layoutDoc.sortDesc));
     }
 
+    /**
+     * Number of rows of cards to be rendered
+     */
+    @computed get numRows() { return Math.ceil(this.sortedDocs.length / 10); }
+
     @action
     setHoveredNodeIndex = (index: number) => {
         if (!DocumentView.SelectedDocs().includes(this.childDocs[index])) {
@@ -268,8 +264,8 @@ export class CollectionCardView extends CollectionSubView() {
                                 return   [DateCast(docA.author_date)?.date ?? Date.now(),
                                           DateCast(docB.author_date)?.date ?? Date.now()];
                     case cardSortings.Color: 
-                                return [ClientUtils.hexToHsv(StrCast(docA.backgroundColor)) ?? 9999, // If docA.type is undefined, use an empty string
-                                        ClientUtils.hexToHsv(StrCast(docB.backgroundColor))] ?? 9999; // If docB.type is undefined, use an empty string
+                                return [ClientUtils.hexToHsv(StrCast(docA.backgroundColor)), // If docA.type is undefined, use an empty string
+                                        ClientUtils.hexToHsv(StrCast(docB.backgroundColor))]; // If docB.type is undefined, use an empty string
                     case cardSortings.Custom:
                                   return [CollectionCardView.getButtonGroup(this.cardSort_customField, docA)??9999,
                                           CollectionCardView.getButtonGroup(this.cardSort_customField, docB)??9999];
@@ -303,7 +299,7 @@ export class CollectionCardView extends CollectionSubView() {
             LayoutTemplate={this._props.childLayoutTemplate}
             LayoutTemplateString={this._props.childLayoutString}
             ScreenToLocalTransform={screenToLocalTransform} // makes sure the box wrapper thing is in the right spot
-            isContentActive={this.isChildContentActive}
+            isContentActive={emptyFunction}
             isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
             PanelWidth={this.panelWidth}
             PanelHeight={this.panelHeight(doc)}
@@ -316,11 +312,11 @@ export class CollectionCardView extends CollectionSubView() {
      * @returns
      */
     overflowAmCardsCalc = (index: number) => {
-        if (this.inactiveDocs().length < this._maxRowCount) {
-            return this.inactiveDocs().length;
+        if (this.sortedDocs.length < this._maxRowCount) {
+            return this.sortedDocs.length;
         }
         // 13 - 3 = 10
-        const totalCards = this.inactiveDocs().length;
+        const totalCards = this.sortedDocs.length;
         // if 9 or less
         if (index < totalCards - (totalCards % 10)) {
             return this._maxRowCount;
@@ -353,7 +349,11 @@ export class CollectionCardView extends CollectionSubView() {
      * @returns
      */
     calculateTranslateY = (isHovered: boolean, isSelected: boolean, realIndex: number, amCards: number, calcRowIndex: number) => {
-        if (isSelected || amCards == 1) return 50 * this.fitContentScale;
+        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);
@@ -503,7 +503,7 @@ export class CollectionCardView extends CollectionSubView() {
 
         // Map sorted documents to their rendered components
         return this.sortedDocs.map((doc, index) => {
-            const realIndex = this.sortedDocs.filter(sortDoc => !DocumentView.SelectedDocs().includes(sortDoc)).indexOf(doc);
+            const realIndex = this.sortedDocs.indexOf(doc);
             const calcRowIndex = this.overflowIndexCalc(realIndex);
             const amCards = this.overflowAmCardsCalc(realIndex);
             const isSelected = DocumentView.SelectedDocs().includes(doc);
@@ -517,6 +517,12 @@ export class CollectionCardView extends CollectionSubView() {
                           .scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, calcRowIndex) : 0); // prettier-ignore
             };
 
+            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;
+                return (rowCenterIndex - indexInRow) * 100;
+            }
             return (
                 <div
                     key={doc[Id]}
@@ -536,9 +542,9 @@ export class CollectionCardView extends CollectionSubView() {
                         width: this.panelWidth(),
                         height: 'max-content',
                         transform: `translateY(${this.calculateTranslateY(this._hoveredNodeIndex === index, isSelected, realIndex, amCards, calcRowIndex)}px)
-                                    translateX(${isSelected ? this.translateSelected(calcRowIndex) : 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 ? 1.25 : this._hoveredNodeIndex === index ? 1.05 : 1})`,
+                                    scale(${isSelected ? 2 : this._hoveredNodeIndex === index ? 1.05 : 1})`,
                                 }}
                     onMouseEnter={() => this.setHoveredNodeIndex(index)}>
                     {this.displayDoc(doc, childScreenToLocal)}
@@ -566,8 +572,9 @@ export class CollectionCardView extends CollectionSubView() {
                 <div
                     className="card-wrapper"
                     style={{
-                        transform: ` scale(${1 / this.fitContentScale}) translateX(${this.translateWrapperX}px)`,
+                        transform: ` scale(${1 / this.fitContentScale})`,
                         height: `${100 * this.fitContentScale}%`,
+                        gridAutoRows: `${100/this.numRows}%`
                     }}
                     onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
                     {this.renderCards()}
-- 
cgit v1.2.3-70-g09d2


From 15d7ccabc927a216ace693a18c2246323c6d8df6 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Mon, 17 Jun 2024 15:16:39 -0400
Subject: hi

---
 .../views/collections/CollectionCardDeckView.tsx   | 117 ++++++++++-----------
 1 file changed, 58 insertions(+), 59 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 f50ca6349..4a4e5a498 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -2,7 +2,7 @@ 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 { numberRange } from '../../../Utils';
+import { emptyFunction, numberRange } from '../../../Utils';
 import { Doc, NumListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
@@ -26,7 +26,7 @@ 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';
 
 enum cardSortings {
     Time = 'time',
@@ -84,7 +84,6 @@ export class CollectionCardView extends CollectionSubView() {
         super(props);
         makeObservable(this);
         this.setRegenerateCallback();
-
     }
 
     setRegenerateCallback() {
@@ -96,11 +95,8 @@ export class CollectionCardView extends CollectionSubView() {
         const sortDesc = await this.childPairStringList(); // Await the promise to get the string result
         GPTPopup.Instance.setSortDesc(sortDesc.join());
         GPTPopup.Instance.onSortComplete = (sortResult: string) => this.processGptOutput(sortResult);
-
     };
 
-
-
     componentDidMount(): void {
         this._disposers.sort = reaction(
             () => ({ cardSort: this.cardSort, field: this.cardSort_customField }),
@@ -198,7 +194,7 @@ export class CollectionCardView extends CollectionSubView() {
      * @returns
      */
     rotate = (amCards: number, index: number) => {
-        if (amCards ==1) return 0
+        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)));
@@ -285,7 +281,6 @@ export class CollectionCardView extends CollectionSubView() {
             // console.log(ClientUtils.hexToHsv(StrCast(docA.backgroundColor)) ?? 9999 + "docA col")
             // console.log(ClientUtils.hexToHsv(StrCast(docB.backgroundColor)) ?? 9999 + "docB col")
 
-
             const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0;
             return isDesc ? -out : out; // Reverse the sort order if descending is true
         });
@@ -298,7 +293,7 @@ export class CollectionCardView extends CollectionSubView() {
             // 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}
@@ -307,10 +302,13 @@ export class CollectionCardView extends CollectionSubView() {
             LayoutTemplate={this._props.childLayoutTemplate}
             LayoutTemplateString={this._props.childLayoutString}
             ScreenToLocalTransform={screenToLocalTransform} // makes sure the box wrapper thing is in the right spot
-            isContentActive={this.isChildContentActive}
+            isContentActive={emptyFunction}
             isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
             PanelWidth={this.panelWidth}
             PanelHeight={this.panelHeight(doc)}
+            dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType}
+            dontHideOnDrag
+            // pointerEvents={this.blockPointerEventsWhenDragging(doc)}
         />
     );
 
@@ -359,7 +357,7 @@ export class CollectionCardView extends CollectionSubView() {
     calculateTranslateY = (isHovered: boolean, isSelected: boolean, realIndex: number, amCards: number, calcRowIndex: number) => {
         if (isSelected || amCards == 1) return 50 * this.fitContentScale;
         // const trans = isHovered ? this.translateHover(realIndex) : 0;
-        const trans = 0
+        const trans = 0;
         return trans + this.translateY(amCards, calcRowIndex, realIndex);
     };
 
@@ -414,7 +412,7 @@ export class CollectionCardView extends CollectionSubView() {
         } catch (error) {
             console.log('bad things have happened');
 
-            console.log(error)
+            console.log(error);
         }
         return '';
     };
@@ -451,54 +449,55 @@ export class CollectionCardView extends CollectionSubView() {
         GPTPopup.Instance.onSortComplete = (sortResult: string) => this.processGptOutput(sortResult);
     };
 
-  /**
+    /**
      * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups
      * @param doc
      * @param cardSort
      * @returns
      */
-  renderButtons = (doc: Doc, cardSort: cardSortings): JSX.Element | null => {
-    if (cardSort !== cardSortings.Custom) return null;
+    renderButtons = (doc: Doc, cardSort: cardSortings): JSX.Element | null => {
+        if (cardSort !== cardSortings.Custom) return null;
+
+        const amButtons = Math.max(
+            4,
+            this.childDocs?.reduce((set, d) => {
+                if (this.cardSort_customField) {
+                    set.add(NumCast(d[this.cardSort_customField]));
+                }
+                return set;
+            }, new Set<number>()).size ?? 0
+        );
 
-    const amButtons = Math.max(4, this.childDocs?.reduce((set, d) => {
-        if (this.cardSort_customField) {
-            set.add(NumCast(d[this.cardSort_customField]));
-        }
-        return set;
-    }, new Set<number>()).size ?? 0);
-
-    const activeButtonIndex = CollectionCardView.getButtonGroup(this.cardSort_customField, doc);
-    const totalWidth = amButtons * 72 + amButtons * 2 * 5 + 6;
-
-    return (
-        <div className="card-button-container" style={{ width: `${totalWidth}px`, fontSize: '50px' }}>
-            {numberRange(amButtons).map(i => (
-                <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from group {i + 1}</div>}>
-                    <button type="button" onClick={() => this.toggleButton(i, doc)}>
-                        {this.getButtonIcon(activeButtonIndex === i)}
-                    </button>
-                </Tooltip>
-            ))}
-        </div>
-    );
-};
+        const activeButtonIndex = CollectionCardView.getButtonGroup(this.cardSort_customField, doc);
+        const totalWidth = amButtons * 72 + amButtons * 2 * 5 + 6;
+
+        return (
+            <div className="card-button-container" style={{ width: `${totalWidth}px`, fontSize: '50px' }}>
+                {numberRange(amButtons).map(i => (
+                    <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from group {i + 1}</div>}>
+                        <button type="button" onClick={() => this.toggleButton(i, doc)}>
+                            {this.getButtonIcon(activeButtonIndex === i)}
+                        </button>
+                    </Tooltip>
+                ))}
+            </div>
+        );
+    };
 
     getButtonIcon = (isActive: boolean): JSX.Element => {
         const iconMap: { [key: string]: any } = {
             like: 'heart',
             chat: 'robot',
-            idea: 'cloud'
+            idea: 'cloud',
         };
 
         const icon = iconMap[this.cardSort_customField ?? ''] || 'star';
         const color = isActive ? '#4476f7' : '#323232';
 
-        return <FontAwesomeIcon icon={icon} size='lg' style={{ color }} />;
+        return <FontAwesomeIcon icon={icon} size="lg" style={{ color }} />;
     };
 
-
-
-        @action
+    @action
     onDragStart = (index: number) => {
         this.draggedIndex = index;
     };
@@ -526,16 +525,21 @@ export class CollectionCardView extends CollectionSubView() {
         this.draggedIndex = -1;
         this.overIndex = -1;
     };
+
     /**
      * Actually renders all the cards
      */
     renderCards = () => {
+        console.log(DragManager.docsBeingDragged.length + 'drag ha');
         const anySelected = this.childDocs.some(doc => DocumentView.SelectedDocs().includes(doc));
         const isEmpty = this.childDocsWithoutLinks.length === 0;
 
         if (isEmpty) {
-            return <span className = 'no-card-span' style= {{width: `100%`}}> 
-            Sorry ! There are no cards in this group</span>
+            return (
+                <span className="no-card-span" style={{ width: `100%` }}>
+                    Sorry ! There are no cards in this group
+                </span>
+            );
         }
 
         // Map sorted documents to their rendered components
@@ -544,7 +548,7 @@ export class CollectionCardView extends CollectionSubView() {
             const calcRowIndex = this.overflowIndexCalc(realIndex);
             const amCards = this.overflowAmCardsCalc(realIndex);
             const isSelected = DocumentView.SelectedDocs().includes(doc);
-
+            const isDragging = DragManager.docsBeingDragged.includes(doc);
 
             const childScreenToLocal = () => {
                 this._forceChildXf;
@@ -558,7 +562,7 @@ export class CollectionCardView extends CollectionSubView() {
             return (
                 <div
                     key={doc[Id]}
-                    className={`card-item${isSelected ? '-active' : anySelected ? '-inactive' : ''}`}                    
+                    className={`card-item${isSelected ? '-active' : anySelected ? '-inactive' : ''}`}
                     onPointerUp={() => {
                         // this turns off documentDecorations during a transition, then turns them back on afterward.
                         SnappingManager.SetIsResizing(this.Document[Id]);
@@ -573,26 +577,22 @@ export class CollectionCardView extends CollectionSubView() {
                     style={{
                         width: this.panelWidth(),
                         height: 'max-content',
-                        transform: `translateY(${this.calculateTranslateY(this._hoveredNodeIndex === index, isSelected, realIndex, amCards, calcRowIndex)}px)
-                                    translateX(${isSelected ? this.translateSelected(calcRowIndex) : this.translateOverflowX(realIndex, amCards)}px)
-                                    rotate(${!isSelected ? this.rotate(amCards, calcRowIndex) : 0}deg)
-                                    scale(${isSelected ? 1.25 : this._hoveredNodeIndex === index ? 1.05 : 1})`,
-                                }}
+                        transform: !isDragging
+                            ? `translateY(${this.calculateTranslateY(this._hoveredNodeIndex === index, isSelected, realIndex, amCards, calcRowIndex)}px)
+                                            translateX(${isSelected ? this.translateSelected(calcRowIndex) : this.translateOverflowX(realIndex, amCards)}px)
+                                            rotate(${!isSelected ? this.rotate(amCards, calcRowIndex) : 0}deg)
+                                            scale(${isSelected ? 1.25 : this._hoveredNodeIndex === index ? 1.05 : 1})`
+                            : 'none',
+                    }}
                     onMouseEnter={() => this.setHoveredNodeIndex(index)}>
                     {this.displayDoc(doc, childScreenToLocal)}
                     {this.renderButtons(doc, this.cardSort)}
                 </div>
             );
-        }
-        
-        
-        );
+        });
     };
 
-
-
     render() {
-    
         return (
             <div
                 className="collectionCardView-outer"
@@ -623,5 +623,4 @@ export class CollectionCardView extends CollectionSubView() {
             </div>
         );
     }
-    
 }
-- 
cgit v1.2.3-70-g09d2


From bd43d64f833c14b988e6bbf8f43dcd4fcb5b4f97 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Tue, 18 Jun 2024 11:42:12 -0400
Subject: lol

---
 .../views/collections/CollectionCardDeckView.tsx   | 87 +++++++++++-----------
 1 file changed, 43 insertions(+), 44 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 20644715d..76536ed48 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -3,7 +3,6 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { ClientUtils, DashColor, returnFalse, returnZero } from '../../../ClientUtils';
 import { emptyFunction, numberRange } from '../../../Utils';
-import { emptyFunction, numberRange } from '../../../Utils';
 import { Doc, NumListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
@@ -49,8 +48,8 @@ export class CollectionCardView extends CollectionSubView() {
     @observable _docRefs = new ObservableMap<Doc, DocumentView>();
     _draggerRef = React.createRef<HTMLDivElement>();
     @observable _maxRowCount = 10;
-    @observable docsBeingDragged: number[] = [];
-    @observable draggedIndex: number = -1;
+    @observable _docsDraggedIndex: number[] = [];
+    @observable _isACardBeingDragged: boolean = false;
     @observable overIndex: number = -1;
 
     static getButtonGroup(groupFieldKey: 'chat' | 'star' | 'idea' | 'like', doc: Doc): number | undefined {
@@ -232,21 +231,21 @@ export class CollectionCardView extends CollectionSubView() {
      * @param index
      * @returns
      */
-    translateSelected = (index: number): number => {
-        // if (this.isSelected(index)) {
-        const middleOfPanel = this._props.PanelWidth() / 2;
-        const scaledNodeWidth = this.panelWidth() * 1.25;
+    // translateSelected = (index: number): number => {
+    //     // if (this.isSelected(index)) {
+    //     const middleOfPanel = this._props.PanelWidth() / 2;
+    //     const scaledNodeWidth = this.panelWidth() * 1.25;
 
-        // Calculate the position of the node's left edge before scaling
-        const nodeLeftEdge = index * this.panelWidth();
-        // Find the center of the node after scaling
-        const scaledNodeCenter = nodeLeftEdge + scaledNodeWidth / 2;
+    //     // Calculate the position of the node's left edge before scaling
+    //     const nodeLeftEdge = index * this.panelWidth();
+    //     // Find the center of the node after scaling
+    //     const scaledNodeCenter = nodeLeftEdge + scaledNodeWidth / 2;
 
-        // Calculate the translation needed to align the scaled node's center with the panel's center
-        const translation = middleOfPanel - scaledNodeCenter - scaledNodeWidth - scaledNodeWidth / 4;
+    //     // Calculate the translation needed to align the scaled node's center with the panel's center
+    //     const translation = middleOfPanel - scaledNodeCenter - scaledNodeWidth - scaledNodeWidth / 4;
 
-        return translation;
-    };
+    //     return translation;
+    // };
 
     /**
      * Called in the sortedDocsType method. Compares the cards' value in regards to the desired sort type-- earlier cards are move to the
@@ -498,34 +497,34 @@ export class CollectionCardView extends CollectionSubView() {
         return <FontAwesomeIcon icon={icon} size="lg" style={{ color }} />;
     };
 
-    @action
-    onDragStart = (index: number) => {
-        this.draggedIndex = index;
-    };
-
-    @action
-    onDragOver = (index: number) => {
-        if (this.draggedIndex !== index) {
-            this.overIndex = index;
-        }
-    };
-
-    @action
-    onDrop = () => {
-        if (this.draggedIndex !== -1 && this.overIndex !== -1) {
-            const draggedDoc = this.sortedDocs[this.draggedIndex];
-            this.sortedDocs.splice(this.draggedIndex, 1);
-            this.sortedDocs.splice(this.overIndex, 0, draggedDoc);
-            this.draggedIndex = -1;
-            this.overIndex = -1;
-        }
-    };
-
-    @action
-    onDragEnd = () => {
-        this.draggedIndex = -1;
-        this.overIndex = -1;
-    };
+    // @action
+    // onDragStart = (index: number) => {
+    //     this.draggedIndex = index;
+    // };
+
+    // @action
+    // onDragOver = (index: number) => {
+    //     if (this.draggedIndex !== index) {
+    //         this.overIndex = index;
+    //     }
+    // };
+
+    // @action
+    // onDrop = () => {
+    //     if (this.draggedIndex !== -1 && this.overIndex !== -1) {
+    //         const draggedDoc = this.sortedDocs[this.draggedIndex];
+    //         this.sortedDocs.splice(this.draggedIndex, 1);
+    //         this.sortedDocs.splice(this.overIndex, 0, draggedDoc);
+    //         this.draggedIndex = -1;
+    //         this.overIndex = -1;
+    //     }
+    // };
+
+    // @action
+    // onDragEnd = () => {
+    //     this.draggedIndex = -1;
+    //     this.overIndex = -1;
+    // };
 
     /**
      * Actually renders all the cards
@@ -564,7 +563,7 @@ export class CollectionCardView extends CollectionSubView() {
                 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;
-                return (rowCenterIndex - indexInRow) * 100;
+                return (rowCenterIndex - indexInRow) * 100 - 50;
             }
             return (
                 <div
-- 
cgit v1.2.3-70-g09d2


From 3f940bd45fd1bf72f6c16edc188f754b01f689ba Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Fri, 21 Jun 2024 15:11:23 -0400
Subject: yay dragging

---
 src/client/documents/Documents.ts                  |   3 +-
 src/client/util/CurrentUserUtils.ts                |  12 +-
 src/client/views/MainView.tsx                      |   3 +-
 .../views/collections/CollectionCardDeckView.tsx   | 231 ++++++++++++---------
 4 files changed, 149 insertions(+), 100 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 34407c641..a190fe11b 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -479,8 +479,9 @@ export class DocumentOptions {
     userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)');
 
     cardSort?: STRt = new StrInfo('way cards are sorted in deck view');
-    cardSortForDropDown?: STRt = new StrInfo('needed for dropdown and i dont know why')
+    // cardSortForDropDown?: STRt = new StrInfo('needed for dropdown and i dont know why')
     cardSort_customField?: STRt = new StrInfo('field key used for sorting cards');
+    cardSort_activeIcons?: List<string>;
     cardSort_visibleSortGroups?: List<number>; // which sorting values are being filtered (shown)
 }
 
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 2ca2ce9c9..94eee6711 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -660,13 +660,15 @@ pie title Minerals in my tap water
     }
     static cardTools(): Button[] {
         return [
-            { btnList: new List<string>(["Time", "Type", "Color", "Chat GPT", "Custom 1", "Custom 2", "Custom 3" ]),
-                title: "Sort Type",  toolTip: "Card Sort Type",   btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script:  '{ return setCardSort(value, _readOnly_); }'}},
+            // { btnList: new List<string>(["Time", "Type", "Color", "Chat GPT", "Custom 1", "Custom 2", "Custom 3" ]),
+            //     title: "Sort Type",  toolTip: "Card Sort Type",   btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script:  '{ return setCardSort(value, _readOnly_); }'}},
             // { title: "Font",     toolTip: "Font", width: 100,     btnType: ButtonType.DropdownList,                      toolType:"font",     ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'},
             //   btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
-            // { title: "Time",      icon:"hourglass-half", toolTip:"Sort by most recent document creation",      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"time",   funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            // { title: "Type",      icon:"eye",            toolTip:"Sort by document type",                      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"docType",funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            // { title: "Color",     icon:"palette",        toolTip:"Sort by document color",                     btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Time",      icon:"hourglass-half", toolTip:"Sort by most recent document creation",      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"time",   funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Type",      icon:"eye",            toolTip:"Sort by document type",                      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"docType",funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Color",     icon:"palette",        toolTip:"Sort by document color",                     btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "AI Sort",     icon:"palette",        toolTip:"Have Chat GPT sort your cards for you !",   btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"chat",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+
             { title: "AIs",   icon:"Visibility",   toolTip:"Filter AI labels",        subMenu: this.cardGroupTools("robot"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("chat", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
             // { title: "Custom",  icon:"heart",     toolTip:"Add Like labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"like",                  funcs: {hidden:`showFreeform ("like", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "1st", icon:"Visibility",     toolTip:"Filter likes", width: 150, subMenu: this.cardGroupTools("heart"),     expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("like", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f61c784df..b06d785e1 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -548,7 +548,8 @@ export class MainView extends ObservableReactComponent<{}> {
                 fa.faRobot,
                 fa.faSatellite,
                 fa.faStar,
-                fa.faCloud
+                fa.faCloud,
+                fa.faBolt
             ]
         );
     }
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 76536ed48..2ad7f0ed1 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -3,7 +3,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { ClientUtils, DashColor, returnFalse, returnZero } from '../../../ClientUtils';
 import { emptyFunction, numberRange } from '../../../Utils';
-import { Doc, NumListCast } from '../../../fields/Doc';
+import { Doc, NumListCast, StrListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { BoolCast, Cast, DateCast, NumCast, RTFCast, ScriptCast, StrCast } from '../../../fields/Types';
@@ -27,12 +27,14 @@ 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';
 
 enum cardSortings {
     Time = 'time',
     Type = 'type',
     Color = 'color',
     Custom = 'custom',
+    Drag = 'drag',
     None = '',
 }
 @observer
@@ -48,7 +50,7 @@ export class CollectionCardView extends CollectionSubView() {
     @observable _docRefs = new ObservableMap<Doc, DocumentView>();
     _draggerRef = React.createRef<HTMLDivElement>();
     @observable _maxRowCount = 10;
-    @observable _docsDraggedIndex: number[] = [];
+    @observable _docDraggedIndex: number = -1;
     @observable _isACardBeingDragged: boolean = false;
     @observable overIndex: number = -1;
 
@@ -73,12 +75,6 @@ export class CollectionCardView extends CollectionSubView() {
         }
     };
 
-    protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
-        this._dropDisposer?.();
-        if (ele) {
-            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
-        }
-    };
 
     constructor(props: any) {
         super(props);
@@ -145,12 +141,7 @@ export class CollectionCardView extends CollectionSubView() {
         return regularDocs;
     }
 
-    /**
-     * Determines the order in which the cards will be rendered depending on the current sort type
-     */
-    @computed get sortedDocs() {
-        return this.sort(this.childDocsWithoutLinks, this.cardSort, BoolCast(this.layoutDoc.sortDesc));
-    }
+   
 
     /**
      * Number of rows of cards to be rendered
@@ -226,27 +217,71 @@ export class CollectionCardView extends CollectionSubView() {
         return Math.abs(stepMag * (apex - index - 1)) - rowOffset;
     };
 
-    /**
-     * Translates the selected node to the middle fo the screen
-     * @param index
-     * @returns
-     */
-    // translateSelected = (index: number): number => {
-    //     // if (this.isSelected(index)) {
-    //     const middleOfPanel = this._props.PanelWidth() / 2;
-    //     const scaledNodeWidth = this.panelWidth() * 1.25;
+     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;
+    
+        // Calculate the adjusted X position accounting for the initial offset
+        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
+
+        if (adjustedX < 0) {
+            return 0; // Before the first column
+        }
+
+        if (amCardsTotal < this._maxRowCount)   { 
+            index = Math.floor(adjustedX / cardWidth);
+        }
+
+        else if (currRow != amRows -1 ){
+            index = Math.floor(adjustedX / cardWidth) + (currRow * this._maxRowCount)
+        }
 
-    //     // Calculate the position of the node's left edge before scaling
-    //     const nodeLeftEdge = index * this.panelWidth();
-    //     // Find the center of the node after scaling
-    //     const scaledNodeCenter = nodeLeftEdge + scaledNodeWidth / 2;
+        else {
+            // console.log(amRows + "am rows")
+            const rowAmCards = amCardsTotal - (currRow * this._maxRowCount)
+            const offset = ((this._maxRowCount - rowAmCards ) / 2) * cardWidth
+            adjustedX = mouseX - offset
 
-    //     // Calculate the translation needed to align the scaled node's center with the panel's center
-    //     const translation = middleOfPanel - scaledNodeCenter - scaledNodeWidth - scaledNodeWidth / 4;
+            index = Math.floor(adjustedX / cardWidth) + (currRow * this._maxRowCount);
+        }
+        return index;
+    };
 
-    //     return translation;
-    // };
+    @action
+    onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
+        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;
+                }
+        }
+    };
 
+
+    onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
+        if (de.complete.docDragData) {
+            this._isACardBeingDragged = false;
+            this._docDraggedIndex = -1;
+            e.stopPropagation()
+            const draggedDocs = de.complete.docDragData?.draggedDocuments;
+            return true;
+        }
+        return false;
+    };
+
+     @computed get sortedDocs() {
+        // console.log("hi hi hi")
+        return this.sort(this.childDocsWithoutLinks, this.cardSort, BoolCast(this.layoutDoc.sortDesc), this._docDraggedIndex);
+    }
     /**
      * Called in the sortedDocsType method. Compares the cards' value in regards to the desired sort type-- earlier cards are move to the
      * front, latter cards to the back
@@ -255,7 +290,8 @@ export class CollectionCardView extends CollectionSubView() {
      * @param isDesc
      * @returns
      */
-    sort = (docs: Doc[], sortType: cardSortings, isDesc: boolean) => {
+    sort = (docs: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => {
+        // console.log('HIIIIIIIII')
         if (sortType === cardSortings.None) return docs;
         docs.sort((docA, docB) => {
             const [typeA, typeB] = (() => {
@@ -281,9 +317,25 @@ export class CollectionCardView extends CollectionSubView() {
             return isDesc ? -out : out; // Reverse the sort order if descending is true
         });
 
+        if (dragIndex != -1){
+            // console.log("what what")
+
+            const draggedDoc = DragManager.docsBeingDragged[0]
+            const originalIndex = docs.findIndex(doc => doc === draggedDoc);
+
+                    
+            // Remove the dragged document from its original position
+            docs.splice(originalIndex, 1);
+            // Insert the dragged document at the new position
+            docs.splice(dragIndex, 0, draggedDoc);
+                // }
+        }
+
         return docs;
     };
 
+
+
     displayDoc = (doc: Doc, screenToLocalTransform: () => Transform) => (
         <DocumentView
             // eslint-disable-next-line react/jsx-props-no-spreading
@@ -361,16 +413,7 @@ export class CollectionCardView extends CollectionSubView() {
         return trans + this.translateY(amCards, calcRowIndex, realIndex);
     };
 
-    /**
-     * Toggles the buttons between on and off when creating custom sort groupings/changing those created by gpt
-     * @param childPairIndex
-     * @param buttonID
-     * @param doc
-     */
-    toggleButton = undoable((buttonID: number, doc: Doc) => {
-        this.cardSort_customField && (doc[this.cardSort_customField] = buttonID);
-    }, 'toggle custom button');
-
+  
     /**
      * 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
@@ -456,27 +499,37 @@ export class CollectionCardView extends CollectionSubView() {
      * @returns
      */
     renderButtons = (doc: Doc, cardSort: cardSortings): JSX.Element | null => {
-        if (cardSort !== cardSortings.Custom) return null;
+        // if (cardSort !== cardSortings.Custom) return null;
 
-        const amButtons = Math.max(
-            4,
-            this.childDocs?.reduce((set, d) => {
-                if (this.cardSort_customField) {
-                    set.add(NumCast(d[this.cardSort_customField]));
-                }
-                return set;
-            }, new Set<number>()).size ?? 0
-        );
+        const amButtons = 4
+
+        // const amButtons = Math.max(
+        //     4,
+        //     this.childDocs?.reduce((set, d) => {
+        //         if (this.cardSort_customField) {
+        //             set.add(NumCast(d[this.cardSort_customField]));
+        //         }
+        //         return set;
+        //     }, new Set<number>()).size ?? 0
+        // );
 
         const activeButtonIndex = CollectionCardView.getButtonGroup(this.cardSort_customField, doc);
+
         const totalWidth = amButtons * 72 + amButtons * 2 * 5 + 6;
 
+        const iconMap: { [key: number]: any } = {
+            0: 'star',
+            1: 'heart',
+            2: 'cloud',
+            3: 'bolt'
+        };
+
         return (
             <div className="card-button-container" style={{ width: `${totalWidth}px`, fontSize: '50px' }}>
                 {numberRange(amButtons).map(i => (
-                    <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from group {i + 1}</div>}>
-                        <button type="button" onClick={() => this.toggleButton(i, doc)}>
-                            {this.getButtonIcon(activeButtonIndex === i)}
+                    <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from the {iconMap[i]} group</div>}>
+                        <button type="button" onClick={() => this.toggleButton(doc, iconMap[i] )}>
+                            {this.getButtonIcon(doc, iconMap[i])}
                         </button>
                     </Tooltip>
                 ))}
@@ -484,53 +537,44 @@ export class CollectionCardView extends CollectionSubView() {
         );
     };
 
-    getButtonIcon = (isActive: boolean): JSX.Element => {
-        const iconMap: { [key: string]: any } = {
-            like: 'heart',
-            chat: 'robot',
-            idea: 'cloud',
-        };
+      /**
+     * Toggles the buttons between on and off when creating custom sort groupings/changing those created by gpt
+     * @param childPairIndex
+     * @param buttonID
+     * @param doc
+     */
+      toggleButton = undoable((doc: Doc, icon: string) => {
+
+
+
+        // this.cardSort_customField && (doc[this.cardSort_customField] = buttonID);
+
+        // doc.cardSort_activeIcons = new List<string>()
+
 
-        const icon = iconMap[this.cardSort_customField ?? ''] || 'star';
+        const list = StrListCast(doc.cardSort_activeIcons);
+        doc.cardSort_activeIcons = new List<string>(list.includes(icon) ? list.filter(d => d !== icon) : [...list, icon]);
+
+
+
+        // StrListCast(doc.cardSort_activeIcons).push(iconMap[buttonID])
+    }, 'toggle card tag');
+
+
+    getButtonIcon = (doc: Doc, icon: any): JSX.Element => {
+
+        const isActive = StrListCast(doc.cardSort_activeIcons).includes(icon)
+
+        console.log(StrListCast(doc.cardSort_activeIcons))
         const color = isActive ? '#4476f7' : '#323232';
 
         return <FontAwesomeIcon icon={icon} size="lg" style={{ color }} />;
     };
 
-    // @action
-    // onDragStart = (index: number) => {
-    //     this.draggedIndex = index;
-    // };
-
-    // @action
-    // onDragOver = (index: number) => {
-    //     if (this.draggedIndex !== index) {
-    //         this.overIndex = index;
-    //     }
-    // };
-
-    // @action
-    // onDrop = () => {
-    //     if (this.draggedIndex !== -1 && this.overIndex !== -1) {
-    //         const draggedDoc = this.sortedDocs[this.draggedIndex];
-    //         this.sortedDocs.splice(this.draggedIndex, 1);
-    //         this.sortedDocs.splice(this.overIndex, 0, draggedDoc);
-    //         this.draggedIndex = -1;
-    //         this.overIndex = -1;
-    //     }
-    // };
-
-    // @action
-    // onDragEnd = () => {
-    //     this.draggedIndex = -1;
-    //     this.overIndex = -1;
-    // };
-
     /**
      * Actually renders all the cards
      */
     renderCards = () => {
-        console.log(DragManager.docsBeingDragged.length + 'drag ha');
         const anySelected = this.childDocs.some(doc => DocumentView.SelectedDocs().includes(doc));
         const isEmpty = this.childDocsWithoutLinks.length === 0;
 
@@ -599,8 +643,9 @@ export class CollectionCardView extends CollectionSubView() {
     render() {
         return (
             <div
+                onPointerMove={e => this.onPointerMove(e)}
                 className="collectionCardView-outer"
-                ref={this.createDashEventsTarget}
+                ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)}
                 style={{
                     background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor),
                     color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color),
-- 
cgit v1.2.3-70-g09d2


From 2f92d924a8dc1675cd12b08bbac7f57b2335a7b2 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Mon, 24 Jun 2024 12:04:43 -0400
Subject: fixing

---
 src/client/util/CurrentUserUtils.ts                     |  5 +++--
 src/client/views/collections/CollectionCardDeckView.tsx | 10 ----------
 src/client/views/global/globalScripts.ts                | 10 +++++-----
 3 files changed, 8 insertions(+), 17 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 94eee6711..9cf3bfe9b 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -667,9 +667,10 @@ pie title Minerals in my tap water
             { title: "Time",      icon:"hourglass-half", toolTip:"Sort by most recent document creation",      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"time",   funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Type",      icon:"eye",            toolTip:"Sort by document type",                      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"docType",funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Color",     icon:"palette",        toolTip:"Sort by document color",                     btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "AI Sort",     icon:"palette",        toolTip:"Have Chat GPT sort your cards for you !",   btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"chat",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "AI Sort",     icon:"robot",        toolTip:"Have Chat GPT sort your cards for you !",   btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"chat",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            
 
-            { title: "AIs",   icon:"Visibility",   toolTip:"Filter AI labels",        subMenu: this.cardGroupTools("robot"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("chat", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
+            // { title: "AIs",   icon:"Visibility",   toolTip:"Filter AI labels",        subMenu: this.cardGroupTools("robot"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("chat", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
             // { title: "Custom",  icon:"heart",     toolTip:"Add Like labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"like",                  funcs: {hidden:`showFreeform ("like", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "1st", icon:"Visibility",     toolTip:"Filter likes", width: 150, subMenu: this.cardGroupTools("heart"),     expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("like", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
             // { title: "Custom",  icon:"star",      toolTip:"Add Star labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"star",                  funcs: {hidden:`showFreeform ("star", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 2ad7f0ed1..09cddda19 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -660,16 +660,6 @@ export class CollectionCardView extends CollectionSubView() {
                     onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
                     {this.renderCards()}
                 </div>
-                <div className="collectionNotetaking-pivotField" style={{ right: 0, top: 0, position: 'absolute' }}>
-                    <FieldsDropdown
-                        Document={this.Document}
-                        selectFunc={undoable(fieldKey => {
-                            this.layoutDoc._pivotField = fieldKey;
-                            // this.removeEmptyColumns();
-                        }, 'change pivot field')}
-                        placeholder={StrCast(this.layoutDoc._pivotField)}
-                    />
-                </div>
             </div>
         );
     }
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index cef913837..1344dd071 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -175,15 +175,15 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
         }],
         ['time', {
             checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "time",
-            setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "time",
+            setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort === "time" ? doc.cardSort = '' : doc.cardSort = 'time',
         }],
         ['docType', {
             checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "type",
-            setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "type",
+            setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort === "type" ? doc.cardSort = '' : doc.cardSort = 'type',
         }],
         ['color', {
             checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "color",
-            setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "color",
+            setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort === "color" ? doc.cardSort = '' : doc.cardSort = 'color',
         }],
         ['links', {
             checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "links",
@@ -216,8 +216,8 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
         ['chat', {
             checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom"  && StrCast(doc?.cardSort_customField) === "chat",
             setDoc: (doc: Doc, dv: DocumentView) => {
-                doc.cardSort = "custom";
-                doc.cardSort_customField = "chat";
+                doc.cardSort === "custom" ? doc.cardSort = '' : doc.cardSort = 'custom';
+                doc.cardSort_customField === "chat" ? doc.cardSort_customField = '' : doc.cardSort_customField = 'chat';
                 doc.cardSort_visibleSortGroups = new List<number>();
             },
         }],
-- 
cgit v1.2.3-70-g09d2


From 3a3d4433e0b4bfc134370ba4938f22cbed61428b Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Wed, 26 Jun 2024 14:17:18 -0400
Subject: sigh

---
 src/client/apis/gpt/GPT.ts                         |   7 +-
 src/client/documents/Documents.ts                  |   8 +-
 src/client/util/CurrentUserUtils.ts                |  46 ++++---
 .../views/collections/CollectionCardDeckView.tsx   | 147 ++++++++++++---------
 src/client/views/global/globalScripts.ts           |  94 +++++++------
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |   1 +
 6 files changed, 182 insertions(+), 121 deletions(-)

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

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 585398204..40af70d99 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -42,8 +42,9 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
     sort: {
         model: 'gpt-4o',
         maxTokens: 2048,
-        temp: 0.5,
-        prompt: "I'm going to give you a list of descriptions. Each one is separated by `======` on either side. Descriptions will vary in length, so make sure to only separate when you see `======`. Sort them into lists by shared content. Make sure each description is in only one list. Each list should be separated by `======` with the elements within it separated by `~~~~~~`. Immediately afterward, surrounded by `------` on BOTH SIDES, provide some insight into your reasoning for the way you sorted. It is VERY important that you format it exactly as described, ensuring the proper number of `=` `~` and `-` (6 of each) and no commas.Try to create around 4 groups, but a little more or less is ok. Also, I may provide some more insight after this colon:"
+        temp: 0.25,
+        prompt: "The user is going to give you a list of descriptions. Each one is separated by `======` on either side. Descriptions will vary in length, so make sure to only separate when you see `======`. Sort them by shared content. Make sure each description is only in the list once. Each item should be separated by `======`. Immediately afterward, surrounded by `------` on BOTH SIDES, provide some insight into your reasoning for the way you sorted (and mention nothing about the formatting details given in this description). It is VERY important that you format it exactly as described, ensuring the proper number of `=` and `-` (6 of each) and no commas"
+        // prompt: "I'm going to give you a list of descriptions. Each one is separated by `======` on either side. Descriptions will vary in length, so make sure to only separate when you see `======`. Sort them into lists by shared content. Make sure each description is in only one list. Each list should be separated by `======` with the elements within it separated by `~~~~~~`. Immediately afterward, surrounded by `------` on BOTH SIDES, provide some insight into your reasoning for the way you sorted. It is VERY important that you format it exactly as described, ensuring the proper number of `=` `~` and `-` (6 of each) and no commas.Try to create around 4 groups, but a little more or less is ok. Also, I may provide some more insight after this colon:"
     },
     describe: { model: 'gpt-4-vision-preview', maxTokens: 2048, temp: 0, prompt: 'Describe these images in 3-5 words' },
     chatcard: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Answer the following question as a short flashcard response. Do not include a label.' },
@@ -75,7 +76,7 @@ const gptAPICall = async (inputTextIn: string, callType: GPTCallType, prompt?: a
         //     dangerouslyAllowBrowser: true,
         // };
 
-        const usePrompt = prompt ? opts.prompt + prompt : opts.prompt;
+        const usePrompt = prompt ? prompt + opts.prompt : opts.prompt;
         const messages: ChatCompletionMessageParam[] = [
             { role: 'system', content: usePrompt },
             { role: 'user', content: inputText },
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a190fe11b..53422b0c8 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -481,8 +481,12 @@ export class DocumentOptions {
     cardSort?: STRt = new StrInfo('way cards are sorted in deck view');
     // cardSortForDropDown?: STRt = new StrInfo('needed for dropdown and i dont know why')
     cardSort_customField?: STRt = new StrInfo('field key used for sorting cards');
-    cardSort_activeIcons?: List<string>;
-    cardSort_visibleSortGroups?: List<number>; // which sorting values are being filtered (shown)
+    cardSort_activeIcons?: List<string>; //icons each card is tagges with
+    cardSort_visibleSortGroups?: List<string>; // which sorting values are being filtered (shown)
+
+    // cardSort_visibleSortGroups?: List<number>; // which sorting values are being filtered (shown)
+    cardSort_isDesc?: BOOLt = new BoolInfo('whether the cards are sorted ascending or descending');
+    test?: STRt = new StrInfo('testing for filtering')
 }
 
 export const DocOptions = new DocumentOptions();
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 9cf3bfe9b..48e4441de 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -668,15 +668,25 @@ pie title Minerals in my tap water
             { title: "Type",      icon:"eye",            toolTip:"Sort by document type",                      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"docType",funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Color",     icon:"palette",        toolTip:"Sort by document color",                     btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "AI Sort",     icon:"robot",        toolTip:"Have Chat GPT sort your cards for you !",   btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"chat",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+
+            { title: "Sort",    toolTip: "Manage sort order / lock status",   icon: "sort" ,        btnType: ButtonType.MultiToggleButton,                 toolType:"alignment", ignoreClick: true,
+              subMenu: [
+                { title: "Ascending",     toolTip: "Sort the cards in ascending order",   btnType: ButtonType.ToggleButton, icon: "sort-up",  toolType:"up",  ignoreClick: true,  scripts: {onClick: '{ return showFreeform(this.toolType, _readOnly_);}' }},
+                { title: "Descending",   toolTip: "Sort the cards in descending order",btnType: ButtonType.ToggleButton, icon: "sort-down",toolType:"down",ignoreClick: true,  scripts: {onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
+            ]},
+            { title: "Filter", icon:"Filter",     toolTip:"Filter cards by tags", width: 150, subMenu: this.cardGroupTools(), expertMode: false, toolType:CollectionViewType.Card,  funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
+
+
+
             
 
-            // { title: "AIs",   icon:"Visibility",   toolTip:"Filter AI labels",        subMenu: this.cardGroupTools("robot"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("chat", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
-            // { title: "Custom",  icon:"heart",     toolTip:"Add Like labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"like",                  funcs: {hidden:`showFreeform ("like", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "1st", icon:"Visibility",     toolTip:"Filter likes", width: 150, subMenu: this.cardGroupTools("heart"),     expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("like", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
-            // { title: "Custom",  icon:"star",      toolTip:"Add Star labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"star",                  funcs: {hidden:`showFreeform ("star", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "2nd", icon:"Visibility",     toolTip:"Filter stars", width: 150, subMenu: this.cardGroupTools("star"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("star", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, 
-            // { title: "Custom",  icon:"cloud", toolTip:"Add Idea labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"idea",                  funcs: {hidden:`showFreeform ("idea", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "3rd", icon:"Visibility",     toolTip:"Filter ideas", width: 150, subMenu: this.cardGroupTools("cloud"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("idea", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
+            // // { title: "AIs",   icon:"Visibility",   toolTip:"Filter AI labels",        subMenu: this.cardGroupTools("robot"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("chat", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
+            // // { title: "Custom",  icon:"heart",     toolTip:"Add Like labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"like",                  funcs: {hidden:`showFreeform ("like", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            // { title: "1st", icon:"Visibility",     toolTip:"Filter likes", width: 150, subMenu: this.cardGroupTools("heart"),     expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("like", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
+            // // { title: "Custom",  icon:"star",      toolTip:"Add Star labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"star",                  funcs: {hidden:`showFreeform ("star", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            // { title: "2nd", icon:"Visibility",     toolTip:"Filter stars", width: 150, subMenu: this.cardGroupTools("star"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("star", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, 
+            // // { title: "Custom",  icon:"cloud", toolTip:"Add Idea labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"idea",                  funcs: {hidden:`showFreeform ("idea", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            // { title: "3rd", icon:"Visibility",     toolTip:"Filter ideas", width: 150, subMenu: this.cardGroupTools("cloud"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("idea", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
          ]
     }
     // static labelTools(): Button[] {
@@ -691,15 +701,21 @@ pie title Minerals in my tap water
     //         { title: "3rd", icon:"3rd",     toolTip:"Filter ideas", width: 80, subMenu: this.cardGroupTools("cloud"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("idea", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
     //     ]
     // }
-    static cardGroupTools(icon: string): Button[] {
+    static cardGroupTools(): Button[] {
         return [
-            { title: "Group 1", icon, toolTip:"Click to toggle group 1's visibility",  btnType: ButtonType.ToggleButton, width: 40,  expertMode: false, toolType:"1", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Group 2", icon, toolTip:"Click to toggle group 2's visibility",  btnType: ButtonType.ToggleButton, width: 40, expertMode: false, toolType:"2", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Group 3", icon, toolTip:"Click to toggle group 3's visibility",  btnType: ButtonType.ToggleButton, width: 40, expertMode: false, toolType:"3", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Group 4", icon, toolTip:"Click to toggle group 4's visibility",  btnType: ButtonType.ToggleButton,  width: 40, expertMode: false, toolType:"4", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "", icon, toolTip:"Click to toggle group 5's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"5", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "", icon, toolTip:"Click to toggle group 6's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"6", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "", icon, toolTip:"Click to toggle group 7's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"7", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Star", icon: "star", toolTip:"Click to toggle the star group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Heart", icon: "heart", toolTip:"Click to toggle the heart group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Bolt", icon: "bolt", toolTip:"Click to toggle the bolt group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Cloud", icon: "cloud", toolTip:"Click to toggle the cloud group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+
+
+            // { title: "Group 1", icon, toolTip:"Click to toggle group 1's visibility",  btnType: ButtonType.ToggleButton, width: 40,  expertMode: false, toolType:"1", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            // { title: "Group 2", icon, toolTip:"Click to toggle group 2's visibility",  btnType: ButtonType.ToggleButton, width: 40, expertMode: false, toolType:"2", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            // { title: "Group 3", icon, toolTip:"Click to toggle group 3's visibility",  btnType: ButtonType.ToggleButton, width: 40, expertMode: false, toolType:"3", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            // { title: "Group 4", icon, toolTip:"Click to toggle group 4's visibility",  btnType: ButtonType.ToggleButton,  width: 40, expertMode: false, toolType:"4", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            // { title: "", icon, toolTip:"Click to toggle group 5's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"5", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            // { title: "", icon, toolTip:"Click to toggle group 6's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"6", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            // { title: "", icon, toolTip:"Click to toggle group 7's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"7", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
         ]
     }
     static viewTools(): Button[] {
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 09cddda19..d8f4362f1 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -34,7 +34,7 @@ enum cardSortings {
     Type = 'type',
     Color = 'color',
     Custom = 'custom',
-    Drag = 'drag',
+    Chat = 'chat',
     None = '',
 }
 @observer
@@ -45,7 +45,7 @@ export class CollectionCardView extends CollectionSubView() {
     private _textToDoc = new Map<string, Doc>();
 
     @observable _forceChildXf = false;
-    @observable _isLoading = false;
+    // @observable _isLoading = false;
     @observable _hoveredNodeIndex = -1;
     @observable _docRefs = new ObservableMap<Doc, DocumentView>();
     _draggerRef = React.createRef<HTMLDivElement>();
@@ -94,9 +94,10 @@ export class CollectionCardView extends CollectionSubView() {
     };
 
     componentDidMount(): void {
+        this.Document.childFilters_boolean = 'OR'
         this._disposers.sort = reaction(
-            () => ({ cardSort: this.cardSort, field: this.cardSort_customField }),
-            ({ cardSort, field }) => (cardSort === cardSortings.Custom && field === 'chat' ? this.openChatPopup() : GPTPopup.Instance.setVisible(false))
+            () => ({ cardSort: this.cardSort }),
+            ({ cardSort}) => (cardSort === cardSortings.Chat  ? this.openChatPopup() : GPTPopup.Instance.setVisible(false))
         );
     }
 
@@ -112,6 +113,7 @@ export class CollectionCardView extends CollectionSubView() {
     @computed get cardSort() {
         return StrCast(this.Document.cardSort) as any as cardSortings;
     }
+    
     /**
      * how much to scale down the contents of the view so that everything will fit
      */
@@ -126,15 +128,13 @@ export class CollectionCardView extends CollectionSubView() {
      */
     @computed get childDocsWithoutLinks() {
         const regularDocs = this.childDocs.filter(l => l.type !== DocumentType.LINK);
-        const activeGroups = NumListCast(this.Document.cardSort_visibleSortGroups);
+        const activeGroups = StrListCast(this.Document.cardSort_visibleSortGroups);
 
-        if (activeGroups.length > 0 && this.cardSort === cardSortings.Custom) {
+        if (activeGroups.length > 0) {
             return regularDocs.filter(doc => {
-                // Get the group number for the current index
-                const groupNumber = CollectionCardView.getButtonGroup(this.cardSort_customField, doc);
-                // Check if the group number is in the active groups
-                return groupNumber !== undefined && activeGroups.includes(groupNumber);
-            });
+                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
@@ -217,7 +217,7 @@ export class CollectionCardView extends CollectionSubView() {
         return Math.abs(stepMag * (apex - index - 1)) - rowOffset;
     };
 
-     findCardDropIndex = (mouseX: number, mouseY: number) => {
+     findCardDropIndex = (mouseX: number, mouseY: number, direction: 'left' | 'right') => {
 
         const amCardsTotal = this.sortedDocs.length
         let index = 0;
@@ -257,8 +257,9 @@ export class CollectionCardView extends CollectionSubView() {
     onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
         if (DragManager.docsBeingDragged.length != 0 ) {
             this._isACardBeingDragged = true
-
-            const newIndex = this.findCardDropIndex(e.clientX, e.clientY);
+            
+            const direction = e.movementX > 0 ? 'right' : 'left';
+            const newIndex = this.findCardDropIndex(e.clientX, e.clientY, direction);
             
             if (newIndex !== this._docDraggedIndex && newIndex != -1) {
                 this._docDraggedIndex = newIndex;
@@ -278,9 +279,13 @@ export class CollectionCardView extends CollectionSubView() {
         return false;
     };
 
+
      @computed get sortedDocs() {
         // console.log("hi hi hi")
-        return this.sort(this.childDocsWithoutLinks, this.cardSort, BoolCast(this.layoutDoc.sortDesc), this._docDraggedIndex);
+        // console.log(this.layoutDoc.cardSort_isDesc + "layoutdoc desc")
+        console.log(this.cardSort + "card sort")
+  
+        return this.sort(this.childDocsWithoutLinks, this.cardSort, BoolCast(this.layoutDoc.cardSort_isDesc), this._docDraggedIndex);
     }
     /**
      * Called in the sortedDocsType method. Compares the cards' value in regards to the desired sort type-- earlier cards are move to the
@@ -291,48 +296,53 @@ export class CollectionCardView extends CollectionSubView() {
      * @returns
      */
     sort = (docs: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => {
-        // console.log('HIIIIIIIII')
-        if (sortType === cardSortings.None) return docs;
-        docs.sort((docA, docB) => {
-            const [typeA, typeB] = (() => {
+        // if (sortType === cardSortings.None) return docs;
+        
+        if(sortType !== cardSortings.None){
+            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()];
+                        return [DateCast(docA.author_date)?.date ?? Date.now(),
+                                DateCast(docB.author_date)?.date ?? Date.now()];
                     case cardSortings.Color: 
-                                return [ClientUtils.hexToHsv(StrCast(docA.backgroundColor)), // If docA.type is undefined, use an empty string
-                                        ClientUtils.hexToHsv(StrCast(docB.backgroundColor))]; // If docB.type is undefined, use an empty string
+                        return [ClientUtils.hexToHsv(StrCast(docA.backgroundColor)),
+                                ClientUtils.hexToHsv(StrCast(docB.backgroundColor))];
                     case cardSortings.Custom:
-                                  return [CollectionCardView.getButtonGroup(this.cardSort_customField, docA)??9999,
-                                          CollectionCardView.getButtonGroup(this.cardSort_customField, docB)??9999];
-                    default:      return [StrCast(docA.type), // If docA.type is undefined, use an empty string
-                                          StrCast(docB.type)]; // If docB.type is undefined, use an empty string
-                    } // prettier-ignore
+                        return [CollectionCardView.getButtonGroup(this.cardSort_customField, docA) ?? 9999,
+                                CollectionCardView.getButtonGroup(this.cardSort_customField, docB) ?? 9999];
+                    case cardSortings.Chat:
+                        return [NumCast(docA.chat) ?? 9999,
+                                NumCast(docB.chat) ?? 9999]
+                    default:
+                        return [StrCast(docA.type), StrCast(docB.type)]
+                }
             })();
-
-            // console.log(ClientUtils.hexToHsv(StrCast(docA.backgroundColor)) ?? 9999 + "docA col")
-            // console.log(ClientUtils.hexToHsv(StrCast(docB.backgroundColor)) ?? 9999 + "docB col")
-
+    
+            // console.log(`Sorting ${sortType}: ${typeA} vs ${typeB}`);
             const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0;
-            return isDesc ? -out : out; // Reverse the sort order if descending is true
-        });
+            // console.log(`Comparison result: ${out} (isDesc: ${isDesc})`);
 
-        if (dragIndex != -1){
-            // console.log("what what")
+            if (isDesc){
+                return out
+            }
 
-            const draggedDoc = DragManager.docsBeingDragged[0]
+            return -out
+    
+        });}
+    
+        if (dragIndex != -1) {
+            const draggedDoc = DragManager.docsBeingDragged[0];
             const originalIndex = docs.findIndex(doc => doc === draggedDoc);
-
-                    
-            // Remove the dragged document from its original position
+    
             docs.splice(originalIndex, 1);
-            // Insert the dragged document at the new position
             docs.splice(dragIndex, 0, draggedDoc);
-                // }
         }
-
+    
         return docs;
     };
+    
 
 
 
@@ -464,22 +474,35 @@ 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 ~~~~~~
      * @param gptOutput
      */
-    processGptOutput = (gptOutput: string) => {
+    @action processGptOutput = (gptOutput: string) => {
         // Split the string into individual list items
         const listItems = gptOutput.split('======').filter(item => item.trim() !== '');
+        // console.log(listItems + " LISTT");
+    
+        // Debug: print the map contents
+        // console.log("Map contents:", Array.from(this._textToDoc.entries()));
+    
         listItems.forEach((item, index) => {
-            // Split the item by '~~~~~~' to get all descriptors
-            const parts = item.split('~~~~~~').map(part => part.trim());
-
-            parts.forEach(part => {
-                // Find the corresponding Doc in the textToDoc map
-                const doc = this._textToDoc.get(part);
-                if (doc) {
-                    doc.chat = index;
-                }
-            });
+            // Normalize the item (trim whitespace)
+            const normalizedItem = item.trim();
+            // console.log("Normalized item:", normalizedItem);
+    
+            // Find the corresponding Doc in the textToDoc map
+            const doc = this._textToDoc.get(normalizedItem);
+            // console.log("DOC:", doc);
+            // console.log("ITEM:", normalizedItem);
+    
+            if (doc) {
+                doc.chat = index;
+            } else {
+                console.warn(`No matching document found for item: ${normalizedItem}`);
+            }
         });
-    };
+
+    }
+    
+        // );
+    // };
     /**
      * Opens up the chat popup and starts the process for smart sorting.
      */
@@ -513,7 +536,7 @@ export class CollectionCardView extends CollectionSubView() {
         //     }, new Set<number>()).size ?? 0
         // );
 
-        const activeButtonIndex = CollectionCardView.getButtonGroup(this.cardSort_customField, doc);
+        // const activeButtonIndex = CollectionCardView.getButtonGroup(this.cardSort_customField, doc);
 
         const totalWidth = amButtons * 72 + amButtons * 2 * 5 + 6;
 
@@ -552,8 +575,10 @@ export class CollectionCardView extends CollectionSubView() {
         // doc.cardSort_activeIcons = new List<string>()
 
 
-        const list = StrListCast(doc.cardSort_activeIcons);
-        doc.cardSort_activeIcons = new List<string>(list.includes(icon) ? list.filter(d => d !== icon) : [...list, icon]);
+        // const list = StrListCast(doc.cardSort_activeIcons);
+        // doc.cardSort_activeIcons = new List<string>(list.includes(icon) ? list.filter(d => d !== icon) : [...list, icon]);
+
+        BoolCast(doc[icon]) ? doc[icon] = false : doc[icon] = true
 
 
 
@@ -563,9 +588,10 @@ export class CollectionCardView extends CollectionSubView() {
 
     getButtonIcon = (doc: Doc, icon: any): JSX.Element => {
 
-        const isActive = StrListCast(doc.cardSort_activeIcons).includes(icon)
+        // const isActive = StrListCast(doc.cardSort_activeIcons).includes(icon)
+        const isActive = doc[icon]
 
-        console.log(StrListCast(doc.cardSort_activeIcons))
+        // console.log(StrListCast(doc.cardSort_activeIcons))
         const color = isActive ? '#4476f7' : '#323232';
 
         return <FontAwesomeIcon icon={icon} size="lg" style={{ color }} />;
@@ -577,6 +603,7 @@ export class CollectionCardView extends CollectionSubView() {
     renderCards = () => {
         const anySelected = this.childDocs.some(doc => DocumentView.SelectedDocs().includes(doc));
         const isEmpty = this.childDocsWithoutLinks.length === 0;
+        const isDesc = BoolCast(this.Document.cardSort_isDesc)
 
         if (isEmpty) {
             return (
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 1344dd071..4239329dd 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -36,7 +36,7 @@ import { ImageBox } from '../nodes/ImageBox';
 import { VideoBox } from '../nodes/VideoBox';
 import { WebBox } from '../nodes/WebBox';
 import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
-import { NumListCast } from '../../../fields/Doc';
+import { NumListCast, StrListCast } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 
 // import { InkTranscription } from '../InkTranscription';
@@ -137,20 +137,25 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce', checkResult?: boolean, persist?: boolean) {
     const selected = DocumentView.SelectedDocs().lastElement();
+
+    function isAttrFiltered(attr: string) {
+        return StrListCast(selected._childFilters).some(filter => filter.includes(attr))
+    }
+
     // prettier-ignore
-    const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'like' | 'star' | 'idea' | 'chat' | '1' | '2' | '3' | '4', 
+    const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'heart' | 'star' | 'cloud' | 'bolt' | 'chat' | '1' | '2' | '3' | '4' | 'up' | 'down', 
     { 
         waitForRender?: boolean; 
         checkResult: (doc: Doc) => any; 
         setDoc: (doc: Doc, dv: DocumentView) => void; 
     }> = new Map([
         ['grid', {
-            checkResult: (doc:Doc) => BoolCast(doc?._freeform_backgroundGrid, false),
-            setDoc: (doc:Doc) => { doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid; },
+            checkResult: (doc: Doc) => BoolCast(doc?._freeform_backgroundGrid, false),
+            setDoc: (doc: Doc) => { doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid; },
         }],
         ['snaplines', {
-            checkResult: (doc:Doc) => BoolCast(doc?._freeform_snapLines, false),
-            setDoc: (doc:Doc) => { doc._freeform_snapLines = !doc._freeform_snapLines; },
+            checkResult: (doc: Doc) => BoolCast(doc?._freeform_snapLines, false),
+            setDoc: (doc: Doc) => { doc._freeform_snapLines = !doc._freeform_snapLines; },
         }],
         ['viewAll', {
             checkResult: (doc: Doc) => BoolCast(doc?._freeform_fitContentsToBox, false),
@@ -161,13 +166,13 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
             },
         }],
         ['center', {
-            checkResult: (doc:Doc) => BoolCast(doc?._stacking_alignCenter, false),
-            setDoc: (doc:Doc) => { doc._stacking_alignCenter = !doc._stacking_alignCenter; },
+            checkResult: (doc: Doc) => BoolCast(doc?._stacking_alignCenter, false),
+            setDoc: (doc: Doc) => { doc._stacking_alignCenter = !doc._stacking_alignCenter; },
         }],
         ['clusters', {
             waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire
-            checkResult: (doc:Doc) => BoolCast(doc?._freeform_useClusters, false),
-            setDoc: (doc:Doc) => { doc._freeform_useClusters = !doc._freeform_useClusters; },
+            checkResult: (doc: Doc) => BoolCast(doc?._freeform_useClusters, false),
+            setDoc: (doc: Doc) => { doc._freeform_useClusters = !doc._freeform_useClusters; },
         }],
         ['flashcards', {
             checkResult: (doc: Doc) => BoolCast(Doc.UserDoc().defaultToFlashcards, false),
@@ -185,63 +190,70 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
             checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "color",
             setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort === "color" ? doc.cardSort = '' : doc.cardSort = 'color',
         }],
-        ['links', {
-            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "links",
-            setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "links",
-        }],
-        ['like', {
-            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && StrCast(doc?.cardSort_customField) === "like",
+        ['heart', {
+            checkResult: (doc: Doc) => isAttrFiltered('heart'),
             setDoc: (doc: Doc, dv: DocumentView) => {
-                doc.cardSort = "custom";
-                doc.cardSort_customField = "like";
-                doc.cardSort_visibleSortGroups = new List<number>();
+                isAttrFiltered('heart') ? Doc.setDocFilter(doc, 'heart', true, 'remove') : Doc.setDocFilter(doc, 'heart', true, 'match');
+
             }
         }],
         ['star', {
-            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && StrCast(doc?.cardSort_customField) === "star",
+            checkResult: (doc: Doc) => isAttrFiltered('star'),
+
             setDoc: (doc: Doc, dv: DocumentView) => {
-                doc.cardSort = "custom";
-                doc.cardSort_customField = "star";
-                doc.cardSort_visibleSortGroups = new List<number>();
+                isAttrFiltered('star') ? Doc.setDocFilter(doc, 'star', true, 'remove') : Doc.setDocFilter(doc, 'star', true, 'match');
             }
         }],
-        ['idea', {
-            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && StrCast(doc?.cardSort_customField) === "idea",
+        ['bolt', {
+            checkResult: (doc: Doc) => isAttrFiltered('bolt'),
             setDoc: (doc: Doc, dv: DocumentView) => {
-                doc.cardSort = "custom";
-                doc.cardSort_customField = "idea";
-                doc.cardSort_visibleSortGroups = new List<number>();
+                isAttrFiltered('bolt') ? Doc.setDocFilter(doc, 'bolt', true, 'remove') : Doc.setDocFilter(doc, 'bolt', true, 'match');
+
+            }
+        }],
+        ['cloud', {
+            checkResult: (doc: Doc) => isAttrFiltered('cloud'),
+            setDoc: (doc: Doc, dv: DocumentView) => {
+                isAttrFiltered('cloud') ? Doc.setDocFilter(doc, 'cloud', true, 'remove') : Doc.setDocFilter(doc, 'cloud', true, 'match');
+
             }
         }],
         ['chat', {
-            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom"  && StrCast(doc?.cardSort_customField) === "chat",
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "chat",
             setDoc: (doc: Doc, dv: DocumentView) => {
-                doc.cardSort === "custom" ? doc.cardSort = '' : doc.cardSort = 'custom';
-                doc.cardSort_customField === "chat" ? doc.cardSort_customField = '' : doc.cardSort_customField = 'chat';
-                doc.cardSort_visibleSortGroups = new List<number>();
+                doc.cardSort === "chat" ? doc.cardSort = '' : doc.cardSort = 'chat';
             },
         }],
-    ]);
-    for (let i = 0; i < 8; i++) {
-        map.set((i + 1 + '') as any, {
-            checkResult: (doc: Doc) => NumListCast(doc?.cardSort_visibleSortGroups).includes(i),
+        ['up', {
+            checkResult: (doc: Doc) => BoolCast(!doc?.cardSort_isDesc),
             setDoc: (doc: Doc, dv: DocumentView) => {
-                const list = NumListCast(doc.cardSort_visibleSortGroups);
-                doc.cardSort_visibleSortGroups = new List<number>(list.includes(i) ? list.filter(d => d !== i) : [...list, i]);
+                doc.cardSort_isDesc = false;
             },
-        });
-    }
+        }],
+        ['down', {
+            checkResult: (doc: Doc) => BoolCast(doc?.cardSort_isDesc),
+            setDoc: (doc: Doc, dv: DocumentView) => {
+                doc.cardSort_isDesc = true;
+            },
+        }],
+    ]);
 
     if (checkResult) {
-        // console.log("HIIIIII")
         return map.get(attr)?.checkResult(selected);
     }
+    const filters = StrListCast(selected._childFilters).concat(StrListCast(selected?._childFiltersByRanges).filter((filter, i) => !(i % 3)));
+
+    
+
+    // console.log(filters.some(filter => filter.includes('star'))+ "SUOOOOPPP")
+
     const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
     DocumentView.Selected().map(dv => map.get(attr)?.setDoc(dv.layoutDoc, dv));
     setTimeout(() => batch.end(), 100);
     return undefined;
 });
 
+
 ScriptingGlobals.add(function cardHasLabel(label: string) {
     const selected = DocumentView.SelectedDocs().lastElement();
     const labelNum = Number(label) - 1;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 468922532..6184a7b73 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -180,6 +180,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
      * Sorts cards in the CollectionCardDeckView
      */
     generateSort = async () => {
+        console.log(this.chatSortPrompt + "USER PROMPT")
         this.setLoading(true);
         this.setSortDone(false);
 
-- 
cgit v1.2.3-70-g09d2


From 9b0fa4d5581942c54375c10e3887c8010d3eb5e4 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Thu, 27 Jun 2024 14:06:22 -0400
Subject: time

---
 src/client/documents/Documents.ts                  |  6 +-
 src/client/util/CurrentUserUtils.ts                |  2 +
 .../views/collections/CollectionCardDeckView.tsx   |  8 ++-
 .../views/collections/CollectionCarouselView.tsx   |  2 +-
 src/client/views/global/globalScripts.ts           | 80 +++++++++++++++++-----
 src/client/views/pdf/AnchorMenu.tsx                |  2 +-
 6 files changed, 75 insertions(+), 25 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 53422b0c8..324d80581 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -480,13 +480,13 @@ export class DocumentOptions {
 
     cardSort?: STRt = new StrInfo('way cards are sorted in deck view');
     // cardSortForDropDown?: STRt = new StrInfo('needed for dropdown and i dont know why')
-    cardSort_customField?: STRt = new StrInfo('field key used for sorting cards');
+    // cardSort_customField?: STRt = new StrInfo('field key used for sorting cards');
     cardSort_activeIcons?: List<string>; //icons each card is tagges with
-    cardSort_visibleSortGroups?: List<string>; // which sorting values are being filtered (shown)
+    // cardSort_visibleSortGroups?: List<string>; // which sorting values are being filtered (shown)
 
     // cardSort_visibleSortGroups?: List<number>; // which sorting values are being filtered (shown)
     cardSort_isDesc?: BOOLt = new BoolInfo('whether the cards are sorted ascending or descending');
-    test?: STRt = new StrInfo('testing for filtering')
+    // test?: STRt = new StrInfo('testing for filtering')
 }
 
 export const DocOptions = new DocumentOptions();
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 48e4441de..f3b5e5b1d 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -668,12 +668,14 @@ pie title Minerals in my tap water
             { title: "Type",      icon:"eye",            toolTip:"Sort by document type",                      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"docType",funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Color",     icon:"palette",        toolTip:"Sort by document color",                     btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "AI Sort",     icon:"robot",        toolTip:"Have Chat GPT sort your cards for you !",   btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"chat",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Pile",     icon:"layer-group",        toolTip:"View the cards as a pile in the free form view !",   btnType: ButtonType.ClickButton,  expertMode: false, toolType:"pile",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
 
             { title: "Sort",    toolTip: "Manage sort order / lock status",   icon: "sort" ,        btnType: ButtonType.MultiToggleButton,                 toolType:"alignment", ignoreClick: true,
               subMenu: [
                 { title: "Ascending",     toolTip: "Sort the cards in ascending order",   btnType: ButtonType.ToggleButton, icon: "sort-up",  toolType:"up",  ignoreClick: true,  scripts: {onClick: '{ return showFreeform(this.toolType, _readOnly_);}' }},
                 { title: "Descending",   toolTip: "Sort the cards in descending order",btnType: ButtonType.ToggleButton, icon: "sort-down",toolType:"down",ignoreClick: true,  scripts: {onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
             ]},
+
             { title: "Filter", icon:"Filter",     toolTip:"Filter cards by tags", width: 150, subMenu: this.cardGroupTools(), expertMode: false, toolType:CollectionViewType.Card,  funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
 
 
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index d8f4362f1..d3e23c962 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -53,6 +53,7 @@ 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);
@@ -97,8 +98,10 @@ export class CollectionCardView extends CollectionSubView() {
         this.Document.childFilters_boolean = 'OR'
         this._disposers.sort = reaction(
             () => ({ cardSort: this.cardSort }),
-            ({ cardSort}) => (cardSort === cardSortings.Chat  ? this.openChatPopup() : GPTPopup.Instance.setVisible(false))
+            ({ cardSort}) => (cardSort === cardSortings.Chat ? this.openChatPopup() : GPTPopup.Instance.setVisible(false))
         );
+
+       
     }
 
     componentWillUnmount() {
@@ -475,6 +478,9 @@ export class CollectionCardView extends CollectionSubView() {
      * @param gptOutput
      */
     @action processGptOutput = (gptOutput: string) => {
+
+        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() !== '');
         // console.log(listItems + " LISTT");
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 2adad68e0..4884db709 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -33,7 +33,7 @@ enum practiceVal {
 export class CollectionCarouselView extends CollectionSubView() {
     private _dropDisposer?: DragManager.DragDropDisposer;
     get practiceField() { return this.fieldKey + "_practice"; } // prettier-ignore
-    get starField()     { return this.fieldKey + "_star"; } // prettier-ignore
+    get starField()     { return "star"; } // prettier-ignore
 
     constructor(props: any) {
         super(props);
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 4239329dd..2ecde0eee 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -38,8 +38,10 @@ import { WebBox } from '../nodes/WebBox';
 import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
 import { NumListCast, StrListCast } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
-
+import { CollectionViewType } from '../../documents/DocumentTypes';
 // import { InkTranscription } from '../InkTranscription';
+import { Docs } from '../../documents/Documents';
+import { CollectionSubView } from '../collections/CollectionSubView';
 
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function IsNoneSelected() {
@@ -139,11 +141,11 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
     const selected = DocumentView.SelectedDocs().lastElement();
 
     function isAttrFiltered(attr: string) {
-        return StrListCast(selected._childFilters).some(filter => filter.includes(attr))
+        return StrListCast(selected._childFilters).some(filter => filter.includes(attr));
     }
 
     // prettier-ignore
-    const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'heart' | 'star' | 'cloud' | 'bolt' | 'chat' | '1' | '2' | '3' | '4' | 'up' | 'down', 
+    const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'heart' | 'star' | 'cloud' | 'bolt' | 'chat' | '1' | '2' | '3' | '4' | 'up' | 'down' | 'pile', 
     { 
         waitForRender?: boolean; 
         checkResult: (doc: Doc) => any; 
@@ -219,7 +221,11 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
             }
         }],
         ['chat', {
-            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "chat",
+            checkResult: (doc: Doc) => {
+                
+                if (StrCast(doc?.cardSort) === "chat"){
+                    return true
+                }} ,
             setDoc: (doc: Doc, dv: DocumentView) => {
                 doc.cardSort === "chat" ? doc.cardSort = '' : doc.cardSort = 'chat';
             },
@@ -236,6 +242,46 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
                 doc.cardSort_isDesc = true;
             },
         }],
+        ['pile', {
+            checkResult: (doc: Doc) => doc._type_collection == CollectionViewType.Freeform,
+            setDoc: (doc: Doc, dv: DocumentView) => {
+                doc._type_collection = CollectionViewType.Freeform;
+                const newCol = Docs.Create.CarouselDocument(DocListCast(doc[Doc.LayoutFieldKey(doc)]), {
+                    _width: 250,
+                    _height: 200,
+                    _layout_fitWidth: false,
+                    _layout_autoHeight: true,
+                });
+
+
+                const iconMap: { [key: number]: any } = {
+                    0: 'star',
+                    1: 'heart',
+                    2: 'cloud',
+                    3: 'bolt'
+                };
+
+                for (let i=0; i<4; i++){
+                    if (isAttrFiltered(iconMap[i])){
+                        newCol[iconMap[i]] = true
+                    }
+                }
+
+                // if (doc.type == DocumentType.COL ) {
+                    newCol && dv.ComponentView?.addDocument?.(newCol);
+                    dv.ComponentView?._props.focus(newCol, {willPan: true , willZoomCentered: true, zoomScale: 200} )
+
+                // }
+                // doc as CollectionFreeFormView
+                // doc as typeof CollectionSubView
+                
+                // doc._props
+
+                // CollectionFreeFormView.addDocumemnt
+        
+                // doc?.addDocument; //this._props.addDocument(newCol)
+            },
+        }],
     ]);
 
     if (checkResult) {
@@ -243,8 +289,6 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
     }
     const filters = StrListCast(selected._childFilters).concat(StrListCast(selected?._childFiltersByRanges).filter((filter, i) => !(i % 3)));
 
-    
-
     // console.log(filters.some(filter => filter.includes('star'))+ "SUOOOOPPP")
 
     const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
@@ -253,7 +297,6 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
     return undefined;
 });
 
-
 ScriptingGlobals.add(function cardHasLabel(label: string) {
     const selected = DocumentView.SelectedDocs().lastElement();
     const labelNum = Number(label) - 1;
@@ -355,37 +398,36 @@ ScriptingGlobals.add(function setCardSort(value?: any, checkResult?: boolean) {
     if (checkResult) {
         // console.log(attr + "attricute")
         // console.log(map.get(attr)?.checkResult(selected) + "check result")
-        console.log(StrCast(selected?.cardSort) + "check")
-        const hi = StrCast(selected?.cardSort)
-        return StrCast(selected?.cardSortForDropDown) ?? "Time";
+        console.log(StrCast(selected?.cardSort) + 'check');
+        const hi = StrCast(selected?.cardSort);
+        return StrCast(selected?.cardSortForDropDown) ?? 'Time';
     }
-    
+
     function docFields(doc: Doc): void {
-        switch(value){
+        switch (value) {
             case 'Custom 1':
-                doc.cardSort_customField = "like";
+                doc.cardSort_customField = 'like';
                 break;
             case 'Custom 2':
-                doc.cardSort_customField = "star";
+                doc.cardSort_customField = 'star';
                 break;
             case 'Custom 3':
-                doc.cardSort_customField = "idea";
+                doc.cardSort_customField = 'idea';
                 break;
             case 'Chat GPT':
-                doc.cardSort = "custom";
-                doc.cardSort_customField = "chat";
+                doc.cardSort = 'custom';
+                doc.cardSort_customField = 'chat';
                 break;
             default:
                 break;
         }
 
         doc.cardSort_visibleSortGroups = new List<number>();
-
     }
 
     // const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
     DocumentView.Selected().map(dv => {
-        dv.Document.cardSortForDropDown = value
+        dv.Document.cardSortForDropDown = value;
 
         if (value != 'Chat GPT') {
             dv.Document.cardSort = value.trim().split(/\s+/)[0].toLowerCase();
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 2f6824466..96f025ca0 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -133,7 +133,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             _layout_autoHeight: true,
         });
 
-        this.addToCollection?.(newCol);
+        this.addToCollection?.(newCol); //this._props.addDocument(newCol)
     };
 
     pointerDown = (e: React.PointerEvent) => {
-- 
cgit v1.2.3-70-g09d2


From 4521e91a7fd9e7127cf21fdbeba2417881b227ae Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Fri, 28 Jun 2024 12:25:39 -0400
Subject: and so it continues

---
 src/client/views/collections/CollectionCardDeckView.tsx | 6 ++++--
 src/client/views/pdf/GPTPopup/GPTPopup.tsx              | 3 +++
 2 files changed, 7 insertions(+), 2 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 d3e23c962..60befebec 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -301,7 +301,7 @@ export class CollectionCardView extends CollectionSubView() {
     sort = (docs: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => {
         // if (sortType === cardSortings.None) return docs;
         
-        if(sortType !== cardSortings.None){
+        // if(sortType !== cardSortings.None){
             docs.sort((docA, docB) => {
             
                 const [typeA, typeB] = (() => {
@@ -318,6 +318,7 @@ export class CollectionCardView extends CollectionSubView() {
                     case cardSortings.Chat:
                         return [NumCast(docA.chat) ?? 9999,
                                 NumCast(docB.chat) ?? 9999]
+
                     default:
                         return [StrCast(docA.type), StrCast(docB.type)]
                 }
@@ -333,7 +334,8 @@ export class CollectionCardView extends CollectionSubView() {
 
             return -out
     
-        });}
+        });
+    // }
     
         if (dragIndex != -1) {
             const draggedDoc = DragManager.docsBeingDragged[0];
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 6184a7b73..5a5e80dc4 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -27,9 +27,12 @@ export enum GPTPopupMode {
     IMAGE,
     FLASHCARD,
     DATA,
+    CARD,
     SORT,
+    QUIZ
 }
 
+
 interface GPTPopupProps {}
 
 @observer
-- 
cgit v1.2.3-70-g09d2


From 3e46e59b3919458d945745f457d8c76f13094796 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Sun, 14 Jul 2024 14:06:10 -0400
Subject: in it to win it

---
 package-lock.json                                  | 2150 ++++++++++++++------
 src/client/util/CurrentUserUtils.ts                |    3 +-
 src/client/views/MainView.tsx                      |    3 +-
 .../views/collections/CollectionCardDeckView.tsx   |   15 +-
 src/client/views/global/globalScripts.ts           |   14 +-
 src/client/views/pdf/GPTPopup/GPTPopup.scss        |   19 +-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |   98 +-
 7 files changed, 1677 insertions(+), 625 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index 61eccb86e..0542a29cf 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3,382 +3,70 @@
     "version": "1.0.0",
     "lockfileVersion": 1,
     "requires": true,
-    "packages": {
-        "": {
-            "name": "dash",
-            "version": "1.0.0",
-            "dependencies": {
-                "@adobe/react-spectrum": "^3.32.2",
-                "@azure/storage-blob": "^12.17.0",
-                "@babel/preset-env": "^7.23.5",
-                "@babel/preset-react": "^7.23.3",
-                "@bundled-es-modules/pdfjs-dist": "^3.6.172-alpha.1",
-                "@emotion/react": "^11.11.1",
-                "@emotion/styled": "^11.11.0",
-                "@ffmpeg/core": "^0.12.5",
-                "@ffmpeg/ffmpeg": "^0.12.8",
-                "@fortawesome/fontawesome-svg-core": "^6.5.1",
-                "@fortawesome/free-brands-svg-icons": "^6.5.1",
-                "@fortawesome/free-regular-svg-icons": "^6.5.1",
-                "@fortawesome/free-solid-svg-icons": "^6.5.1",
-                "@fortawesome/react-fontawesome": "^0.2.0",
-                "@fullcalendar/core": "^6.1.10",
-                "@fullcalendar/daygrid": "^6.1.10",
-                "@fullcalendar/multimonth": "^6.1.10",
-                "@internationalized/date": "^3.5.0",
-                "@mui/icons-material": "^5.14.19",
-                "@mui/material": "^5.14.19",
-                "@octokit/core": "^6.0.1",
-                "@react-google-maps/api": "^2.19.2",
-                "@react-spring/web": "^9.7.3",
-                "@turf/turf": "^6.5.0",
-                "@types/bezier-js": "^4.1.3",
-                "@types/brotli": "^1.3.4",
-                "@types/cors": "^2.8.17",
-                "@types/d3-axis": "^3.0.6",
-                "@types/d3-color": "^3.1.3",
-                "@types/d3-scale": "^4.0.8",
-                "@types/d3-selection": "^3.0.10",
-                "@types/dom-speech-recognition": "0.0.4",
-                "@types/find-in-files": "^0.5.3",
-                "@types/fluent-ffmpeg": "^2.1.24",
-                "@types/formidable": "3.4.5",
-                "@types/geojson": "^7946.0.14",
-                "@types/google-maps": "^3.2.6",
-                "@types/mapbox-gl": "^3.1.0",
-                "@types/pdf-parse": "^1.1.4",
-                "@types/reveal": "^4.2.0",
-                "@types/supercluster": "^7.1.3",
-                "@types/web": "^0.0.147",
-                "@types/webpack-hot-middleware": "^2.25.9",
-                "@typescript-eslint/parser": "^7.8.0",
-                "@webscopeio/react-textarea-autocomplete": "^4.9.2",
-                "adm-zip": "^0.5.10",
-                "archiver": "^7.0.1",
-                "async": "^3.2.5",
-                "axios": "^1.6.2",
-                "babel": "^6.23.0",
-                "babel-loader": "^9.1.3",
-                "bcrypt-nodejs": "0.0.3",
-                "better-react-mathjax": "^2.0.3",
-                "bezier-curve": "^1.0.0",
-                "bezier-js": "^6.1.4",
-                "bingmaps-react": "^1.2.10",
-                "bluebird": "^3.7.2",
-                "body-parser": "^1.20.2",
-                "bootstrap": "^5.3.2",
-                "brotli": "^1.3.3",
-                "browndash-components": "^0.1.44",
-                "browser-assert": "^1.2.1",
-                "bson": "^6.2.0",
-                "canvas": "^2.11.2",
-                "chart.js": "^4.4.0",
-                "child_process": "^1.0.2",
-                "class-transformer": "^0.5.1",
-                "color": "^4.2.3",
-                "colors": "^1.4.0",
-                "compute-cosine-similarity": "^1.1.0",
-                "connect-flash": "^0.1.1",
-                "connect-mongo": "^5.1.0",
-                "cookie-parser": "^1.4.6",
-                "cookie-session": "^2.0.0",
-                "core-js": "^3.33.3",
-                "cors": "^2.8.5",
-                "css-loader": "^7.0.0",
-                "csstype": "^3.1.3",
-                "csv-parser": "^3.0.0",
-                "csv-stringify": "^6.4.4",
-                "csvtojson": "^2.0.10",
-                "D": "^1.0.0",
-                "d3": "^7.8.5",
-                "depcheck": "^1.4.7",
-                "dotenv": "^16.3.1",
-                "eslint-webpack-plugin": "^4.1.0",
-                "exif": "^0.6.0",
-                "exifr": "^7.1.3",
-                "express": "^4.18.2",
-                "express-flash": "0.0.2",
-                "express-session": "^1.17.3",
-                "express-validator": "^7.0.1",
-                "extract-colors": "^4.0.2",
-                "ffmpeg": "0.0.4",
-                "file-loader": "^6.2.0",
-                "file-saver": "^2.0.5",
-                "find-in-files": "^0.5.0",
-                "fit-curve": "^0.2.0",
-                "flexlayout-react": "^0.7.15",
-                "fluent-ffmpeg": "^2.1.2",
-                "forever-agent": "^0.6.1",
-                "fork-ts-checker-webpack-plugin": "^9.0.2",
-                "form-data": "^4.0.0",
-                "formidable": "3.5.1",
-                "function-plot": "^1.23.3",
-                "golden-layout": "^2.6.0",
-                "google-auth-library": "^9.4.1",
-                "googleapis": "^133.0.0",
-                "googlephotos": "^0.3.5",
-                "got": "^14.0.0",
-                "howler": "^2.2.4",
-                "html-to-image": "^1.11.11",
-                "html-to-text": "^9.0.5",
-                "html-webpack-plugin": "^5.5.3",
-                "http-browserify": "^1.7.0",
-                "https": "^1.0.0",
-                "https-browserify": "^1.0.0",
-                "i": "^0.3.7",
-                "image-data-uri": "^2.0.1",
-                "image-size": "^1.0.2",
-                "image-size-stream": "^1.1.0",
-                "is-plain-obj": "^4.1.0",
-                "jimp": "^0.22.10",
-                "jpeg-autorotate": "^9.0.0",
-                "jquery": "^3.7.1",
-                "js-datepicker": "^5.18.2",
-                "jsonschema": "^1.4.1",
-                "jszip": "^3.10.1",
-                "lodash": "^4.17.21",
-                "mapbox-gl": "^3.0.1",
-                "markdown-it": "^14.1.0",
-                "mathquill": "^0.10.1-a",
-                "md5-file": "^5.0.0",
-                "memorystream": "^0.3.1",
-                "mermaid": "^10.9.0",
-                "mobile-detect": "^1.4.5",
-                "mobx": "^6.12.0",
-                "mobx-react": "^9.1.0",
-                "mobx-utils": "^6.0.8",
-                "mongodb": "^6.3.0",
-                "mongoose": "^8.0.2",
-                "node-stream-zip": "^1.15.0",
-                "nodemailer": "^6.9.7",
-                "nodemon": "^3.0.2",
-                "npm": "^10.2.5",
-                "openai": "^4.26.0",
-                "p-limit": "^5.0.0",
-                "passport": "^0.7.0",
-                "passport-google-oauth20": "^2.0.0",
-                "passport-local": "^1.0.0",
-                "path-browserify": "^1.0.1",
-                "pdf-parse": "^1.1.1",
-                "pdfjs": "^2.4.7",
-                "pdfjs-dist": "^4.0.269",
-                "probe-image-size": "^7.2.3",
-                "process": "^0.11.10",
-                "prosemirror-commands": "^1.5.2",
-                "prosemirror-find-replace": "^0.9.0",
-                "prosemirror-history": "^1.3.2",
-                "prosemirror-inputrules": "^1.4.0",
-                "prosemirror-keymap": "^1.2.2",
-                "prosemirror-model": "^1.19.3",
-                "prosemirror-schema-list": "^1.3.0",
-                "prosemirror-state": "^1.4.3",
-                "prosemirror-transform": "^1.8.0",
-                "prosemirror-view": "^1.32.5",
-                "pug": "^3.0.2",
-                "query-string": "^7.1.3",
-                "querystring-es3": "^0.2.1",
-                "raw-loader": "^4.0.2",
-                "rc-switch": "^4.1.0",
-                "react": "^18.2.0",
-                "react-autosuggest": "^10.1.0",
-                "react-awesome-reveal": "^4.2.7",
-                "react-color": "^2.19.3",
-                "react-compound-slider": "^3.4.0",
-                "react-datepicker": "^6.1.0",
-                "react-dom": "^18.2.0",
-                "react-grid-layout": "^1.4.4",
-                "react-icons": "^5.0.1",
-                "react-jsx-parser": "^1.29.0",
-                "react-latex-next": "^3.0.0",
-                "react-loading": "^2.0.3",
-                "react-map-gl": "^7.1.6",
-                "react-markdown": "^9.0.1",
-                "react-measure": "^2.5.2",
-                "react-resizable": "^3.0.5",
-                "react-select": "^5.8.0",
-                "react-textarea-autosize": "^8.5.3",
-                "react-type-animation": "^3.2.0",
-                "react-xarrows": "^2.0.2",
-                "readline": "^1.3.0",
-                "recharts": "^2.10.3",
-                "rehype-katex": "^7.0.0",
-                "rehype-raw": "^7.0.0",
-                "remark-gfm": "^4.0.0",
-                "remark-math": "^6.0.0",
-                "request": "^2.88.2",
-                "request-promise": "^4.2.6",
-                "reveal.js": "^5.0.2",
-                "rimraf": "^5.0.5",
-                "sass": "^1.69.5",
-                "sass-loader": "^14.2.0",
-                "serializr": "^3.0.2",
-                "shelljs": "^0.8.5",
-                "socket.io": "^4.7.2",
-                "socket.io-client": "^4.7.2",
-                "standard-http-error": "^2.0.1",
-                "stream-browserify": "^3.0.0",
-                "styled-components": "^6.1.1",
-                "supercluster": "^8.0.1",
-                "textarea-caret": "^3.1.0",
-                "tough-cookie": "^4.1.3",
-                "tslint": "^6.1.3",
-                "tslint-loader": "^3.5.4",
-                "typescript": "^5.3.3",
-                "typescript-collections": "^1.3.3",
-                "typescript-language-server": "^4.1.3",
-                "uninstall": "^0.0.0",
-                "url": "^0.11.3",
-                "url-loader": "^4.1.1",
-                "util": "^0.12.5",
-                "uuid": "^9.0.1",
-                "valid-url": "^1.0.9",
-                "web-request": "^1.0.7",
-                "webpack": "^5.89.0",
-                "webpack-cli": "^5.1.4",
-                "webpack-dev-middleware": "^7.0.0",
-                "webpack-hot-middleware": "^2.25.4",
-                "wikijs": "^6.4.1",
-                "words-to-numbers": "^1.5.1",
-                "xoauth2": "^1.2.0",
-                "xregexp": "^5.1.1"
-            },
-            "devDependencies": {
-                "@eslint/js": "^9.1.1",
-                "@types/adm-zip": "^0.5.5",
-                "@types/animejs": "^3.1.12",
-                "@types/archiver": "^6.0.2",
-                "@types/async": "^3.2.24",
-                "@types/bcrypt-nodejs": "0.0.31",
-                "@types/bluebird": "^3.5.42",
-                "@types/body-parser": "^1.19.5",
-                "@types/chai": "^4.3.11",
-                "@types/color": "^3.0.6",
-                "@types/cookie-parser": "^1.4.6",
-                "@types/cookie-session": "^2.0.48",
-                "@types/d3": "^7.4.3",
-                "@types/exif": "^0.6.5",
-                "@types/express": "^4.17.21",
-                "@types/express-session": "^1.17.10",
-                "@types/file-saver": "^2.0.7",
-                "@types/howler": "^2.2.11",
-                "@types/html-to-text": "^9.0.4",
-                "@types/jquery": "^3.5.29",
-                "@types/libxmljs": "^0.18.12",
-                "@types/lodash": "^4.14.202",
-                "@types/mocha": "^10.0.6",
-                "@types/node": "^20.10.3",
-                "@types/nodemailer": "^6.4.14",
-                "@types/passport": "^1.0.16",
-                "@types/passport-google-oauth20": "^2.0.14",
-                "@types/passport-local": "^1.0.38",
-                "@types/rc-switch": "^1.9.5",
-                "@types/react": "^18.2.41",
-                "@types/react-autosuggest": "^10.1.10",
-                "@types/react-color": "^3.0.10",
-                "@types/react-datepicker": "^6.2.0",
-                "@types/react-dom": "^18.2.17",
-                "@types/react-grid-layout": "^1.3.5",
-                "@types/react-measure": "^2.0.12",
-                "@types/react-reconciler": "^0.28.8",
-                "@types/request": "^2.48.12",
-                "@types/request-promise": "^4.1.51",
-                "@types/shelljs": "^0.8.15",
-                "@types/uuid": "^9.0.7",
-                "@types/valid-url": "^1.0.7",
-                "@types/webpack": "^5.28.5",
-                "@types/youtube": "0.0.50",
-                "chai": "^5.0.0",
-                "cross-env": "^7.0.3",
-                "eslint": "^8.57.0",
-                "eslint-config-airbnb": "^19.0.4",
-                "eslint-config-node": "^4.1.0",
-                "eslint-config-prettier": "^9.1.0",
-                "eslint-import-resolver-typescript": "^3.6.1",
-                "eslint-plugin-import": "^2.29.1",
-                "eslint-plugin-jsx-a11y": "^6.8.0",
-                "eslint-plugin-node": "^11.1.0",
-                "eslint-plugin-prettier": "^5.0.1",
-                "eslint-plugin-react": "^7.34.1",
-                "eslint-plugin-react-hooks": "^4.6.0",
-                "globals": "^15.1.0",
-                "jsdom": "^24.0.0",
-                "mocha": "^10.2.0",
-                "prettier": "^3.1.0",
-                "scss-loader": "0.0.1",
-                "style-loader": "^4.0.0",
-                "ts-loader": "^9.5.1",
-                "ts-node": "^10.9.1",
-                "ts-node-dev": "^2.0.0",
-                "typescript-eslint": "^7.8.0",
-                "webpack-dev-server": "^5.0.4"
-            },
-            "engines": {
-                "node": ">=12.0.0"
-            }
-        },
-        "node_modules/@adobe/react-spectrum": {
-            "version": "3.35.1",
-            "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.35.1.tgz",
-            "integrity": "sha512-QNhsaEHv5S5Vqsk7b8aCV9F7qAnWw8VJ/Nep/SOjeiJ7vK993jEOetEhSsUIQ8VHsMKs6qkTtZr0/DKoV+Z/9w==",
-            "dependencies": {
-                "@internationalized/string": "^3.2.3",
-                "@react-aria/i18n": "^3.11.1",
-                "@react-aria/ssr": "^3.9.4",
-                "@react-aria/utils": "^3.24.1",
-                "@react-aria/visually-hidden": "^3.8.12",
-                "@react-spectrum/actionbar": "^3.4.5",
-                "@react-spectrum/actiongroup": "^3.10.5",
-                "@react-spectrum/avatar": "^3.0.12",
-                "@react-spectrum/badge": "^3.1.13",
-                "@react-spectrum/breadcrumbs": "^3.9.7",
-                "@react-spectrum/button": "^3.16.4",
-                "@react-spectrum/buttongroup": "^3.6.13",
-                "@react-spectrum/calendar": "^3.4.9",
-                "@react-spectrum/checkbox": "^3.9.6",
-                "@react-spectrum/combobox": "^3.12.5",
-                "@react-spectrum/contextualhelp": "^3.6.11",
-                "@react-spectrum/datepicker": "^3.9.6",
-                "@react-spectrum/dialog": "^3.8.11",
-                "@react-spectrum/divider": "^3.5.13",
-                "@react-spectrum/dnd": "^3.3.10",
-                "@react-spectrum/dropzone": "^3.0.1",
-                "@react-spectrum/filetrigger": "^3.0.1",
-                "@react-spectrum/form": "^3.7.6",
-                "@react-spectrum/icon": "^3.7.13",
-                "@react-spectrum/illustratedmessage": "^3.5.1",
-                "@react-spectrum/image": "^3.5.1",
-                "@react-spectrum/inlinealert": "^3.2.5",
-                "@react-spectrum/labeledvalue": "^3.1.14",
-                "@react-spectrum/layout": "^3.6.5",
-                "@react-spectrum/link": "^3.6.7",
-                "@react-spectrum/list": "^3.7.10",
-                "@react-spectrum/listbox": "^3.12.9",
-                "@react-spectrum/menu": "^3.19.1",
-                "@react-spectrum/meter": "^3.5.1",
-                "@react-spectrum/numberfield": "^3.9.3",
-                "@react-spectrum/overlays": "^5.6.1",
-                "@react-spectrum/picker": "^3.14.5",
-                "@react-spectrum/progress": "^3.7.7",
-                "@react-spectrum/provider": "^3.9.7",
-                "@react-spectrum/radio": "^3.7.6",
-                "@react-spectrum/searchfield": "^3.8.6",
-                "@react-spectrum/slider": "^3.6.9",
-                "@react-spectrum/statuslight": "^3.5.13",
-                "@react-spectrum/switch": "^3.5.5",
-                "@react-spectrum/table": "^3.12.10",
-                "@react-spectrum/tabs": "^3.8.10",
-                "@react-spectrum/tag": "^3.2.6",
-                "@react-spectrum/text": "^3.5.5",
-                "@react-spectrum/textfield": "^3.12.1",
-                "@react-spectrum/theme-dark": "^3.5.10",
-                "@react-spectrum/theme-default": "^3.5.10",
-                "@react-spectrum/theme-light": "^3.4.10",
-                "@react-spectrum/tooltip": "^3.6.7",
-                "@react-spectrum/view": "^3.6.10",
-                "@react-spectrum/well": "^3.4.13",
-                "@react-stately/collections": "^3.10.7",
-                "@react-stately/data": "^3.11.4",
-                "@react-types/shared": "^3.23.1",
+    "dependencies": {
+        "@adobe/react-spectrum": {
+            "version": "3.35.0",
+            "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.35.0.tgz",
+            "integrity": "sha512-dhCKWKDS/xF02EpWluicH7Va6KAANbrUx4rb1UeeFT3czhwz44oCQ74ZYviVedNv6Bq7O22/kbMa20rDdyt/4A==",
+            "requires": {
+                "@internationalized/string": "^3.2.2",
+                "@react-aria/i18n": "^3.11.0",
+                "@react-aria/ssr": "^3.9.3",
+                "@react-aria/utils": "^3.24.0",
+                "@react-aria/visually-hidden": "^3.8.11",
+                "@react-spectrum/actionbar": "^3.4.4",
+                "@react-spectrum/actiongroup": "^3.10.4",
+                "@react-spectrum/avatar": "^3.0.11",
+                "@react-spectrum/badge": "^3.1.12",
+                "@react-spectrum/breadcrumbs": "^3.9.6",
+                "@react-spectrum/button": "^3.16.3",
+                "@react-spectrum/buttongroup": "^3.6.12",
+                "@react-spectrum/calendar": "^3.4.8",
+                "@react-spectrum/checkbox": "^3.9.5",
+                "@react-spectrum/combobox": "^3.12.4",
+                "@react-spectrum/contextualhelp": "^3.6.10",
+                "@react-spectrum/datepicker": "^3.9.5",
+                "@react-spectrum/dialog": "^3.8.10",
+                "@react-spectrum/divider": "^3.5.12",
+                "@react-spectrum/dnd": "^3.3.9",
+                "@react-spectrum/dropzone": "^3.0.0",
+                "@react-spectrum/filetrigger": "^3.0.0",
+                "@react-spectrum/form": "^3.7.5",
+                "@react-spectrum/icon": "^3.7.12",
+                "@react-spectrum/illustratedmessage": "^3.5.0",
+                "@react-spectrum/image": "^3.5.0",
+                "@react-spectrum/inlinealert": "^3.2.4",
+                "@react-spectrum/labeledvalue": "^3.1.13",
+                "@react-spectrum/layout": "^3.6.4",
+                "@react-spectrum/link": "^3.6.6",
+                "@react-spectrum/list": "^3.7.9",
+                "@react-spectrum/listbox": "^3.12.8",
+                "@react-spectrum/menu": "^3.19.0",
+                "@react-spectrum/meter": "^3.5.0",
+                "@react-spectrum/numberfield": "^3.9.2",
+                "@react-spectrum/overlays": "^5.6.0",
+                "@react-spectrum/picker": "^3.14.4",
+                "@react-spectrum/progress": "^3.7.6",
+                "@react-spectrum/provider": "^3.9.6",
+                "@react-spectrum/radio": "^3.7.5",
+                "@react-spectrum/searchfield": "^3.8.5",
+                "@react-spectrum/slider": "^3.6.8",
+                "@react-spectrum/statuslight": "^3.5.12",
+                "@react-spectrum/switch": "^3.5.4",
+                "@react-spectrum/table": "^3.12.9",
+                "@react-spectrum/tabs": "^3.8.9",
+                "@react-spectrum/tag": "^3.2.5",
+                "@react-spectrum/text": "^3.5.4",
+                "@react-spectrum/textfield": "^3.12.0",
+                "@react-spectrum/theme-dark": "^3.5.9",
+                "@react-spectrum/theme-default": "^3.5.9",
+                "@react-spectrum/theme-light": "^3.4.9",
+                "@react-spectrum/tooltip": "^3.6.6",
+                "@react-spectrum/view": "^3.6.9",
+                "@react-spectrum/well": "^3.4.12",
+                "@react-stately/collections": "^3.10.6",
+                "@react-stately/data": "^3.11.3",
+                "@react-types/shared": "^3.23.0",
                 "client-only": "^0.0.1"
             }
         },
@@ -1045,6 +733,13 @@
                 "@babel/helper-replace-supers": "^7.24.1",
                 "@babel/helper-split-export-declaration": "^7.24.5",
                 "globals": "^11.1.0"
+            },
+            "dependencies": {
+                "globals": {
+                    "version": "11.12.0",
+                    "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+                    "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
+                }
             }
         },
         "@babel/plugin-transform-computed-properties": {
@@ -1598,6 +1293,13 @@
                 "@babel/types": "^7.24.5",
                 "debug": "^4.3.1",
                 "globals": "^11.1.0"
+            },
+            "dependencies": {
+                "globals": {
+                    "version": "11.12.0",
+                    "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+                    "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
+                }
             }
         },
         "@babel/types": {
@@ -1610,6 +1312,11 @@
                 "to-fast-properties": "^2.0.0"
             }
         },
+        "@braintree/sanitize-url": {
+            "version": "6.0.4",
+            "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz",
+            "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A=="
+        },
         "@bundled-es-modules/pdfjs-dist": {
             "version": "3.6.172-alpha.1",
             "resolved": "https://registry.npmjs.org/@bundled-es-modules/pdfjs-dist/-/pdfjs-dist-3.6.172-alpha.1.tgz",
@@ -1857,9 +1564,9 @@
             }
         },
         "@eslint/js": {
-            "version": "8.57.0",
-            "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
-            "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
+            "version": "9.4.0",
+            "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.4.0.tgz",
+            "integrity": "sha512-fdI7VJjP3Rvc70lC4xkFXHB0fiPeojiL1PxVG6t1ZvXQrarj893PweuBTujxDUFk0Fxj4R7PIIAZ/aiiyZPZcg==",
             "dev": true
         },
         "@ffmpeg/core": {
@@ -2233,6 +1940,72 @@
                 }
             }
         },
+        "@jest/schemas": {
+            "version": "29.6.3",
+            "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+            "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+            "requires": {
+                "@sinclair/typebox": "^0.27.8"
+            }
+        },
+        "@jest/types": {
+            "version": "29.6.3",
+            "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
+            "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
+            "requires": {
+                "@jest/schemas": "^29.6.3",
+                "@types/istanbul-lib-coverage": "^2.0.0",
+                "@types/istanbul-reports": "^3.0.0",
+                "@types/node": "*",
+                "@types/yargs": "^17.0.8",
+                "chalk": "^4.0.0"
+            },
+            "dependencies": {
+                "ansi-styles": {
+                    "version": "4.3.0",
+                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+                    "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+                    "requires": {
+                        "color-convert": "^2.0.1"
+                    }
+                },
+                "chalk": {
+                    "version": "4.1.2",
+                    "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+                    "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+                    "requires": {
+                        "ansi-styles": "^4.1.0",
+                        "supports-color": "^7.1.0"
+                    }
+                },
+                "color-convert": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+                    "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+                    "requires": {
+                        "color-name": "~1.1.4"
+                    }
+                },
+                "color-name": {
+                    "version": "1.1.4",
+                    "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+                    "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+                },
+                "has-flag": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+                    "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
+                },
+                "supports-color": {
+                    "version": "7.2.0",
+                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+                    "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+                    "requires": {
+                        "has-flag": "^4.0.0"
+                    }
+                }
+            }
+        },
         "@jimp/bmp": {
             "version": "0.22.12",
             "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.12.tgz",
@@ -2889,7 +2662,6 @@
             "version": "2.1.5",
             "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
             "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
-            "dev": true,
             "requires": {
                 "@nodelib/fs.stat": "2.0.5",
                 "run-parallel": "^1.1.9"
@@ -2898,14 +2670,12 @@
         "@nodelib/fs.stat": {
             "version": "2.0.5",
             "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
-            "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
-            "dev": true
+            "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="
         },
         "@nodelib/fs.walk": {
             "version": "1.2.8",
             "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
             "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
-            "dev": true,
             "requires": {
                 "@nodelib/fs.scandir": "2.1.5",
                 "fastq": "^1.6.0"
@@ -4592,6 +4362,49 @@
                 "@swc/helpers": "^0.5.0"
             }
         },
+        "@react-spring/animated": {
+            "version": "9.7.3",
+            "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.3.tgz",
+            "integrity": "sha512-5CWeNJt9pNgyvuSzQH+uy2pvTg8Y4/OisoscZIR8/ZNLIOI+CatFBhGZpDGTF/OzdNFsAoGk3wiUYTwoJ0YIvw==",
+            "requires": {
+                "@react-spring/shared": "~9.7.3",
+                "@react-spring/types": "~9.7.3"
+            }
+        },
+        "@react-spring/core": {
+            "version": "9.7.3",
+            "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.3.tgz",
+            "integrity": "sha512-IqFdPVf3ZOC1Cx7+M0cXf4odNLxDC+n7IN3MDcVCTIOSBfqEcBebSv+vlY5AhM0zw05PDbjKrNmBpzv/AqpjnQ==",
+            "requires": {
+                "@react-spring/animated": "~9.7.3",
+                "@react-spring/shared": "~9.7.3",
+                "@react-spring/types": "~9.7.3"
+            }
+        },
+        "@react-spring/shared": {
+            "version": "9.7.3",
+            "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.3.tgz",
+            "integrity": "sha512-NEopD+9S5xYyQ0pGtioacLhL2luflh6HACSSDUZOwLHoxA5eku1UPuqcJqjwSD6luKjjLfiLOspxo43FUHKKSA==",
+            "requires": {
+                "@react-spring/types": "~9.7.3"
+            }
+        },
+        "@react-spring/types": {
+            "version": "9.7.3",
+            "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.3.tgz",
+            "integrity": "sha512-Kpx/fQ/ZFX31OtlqVEFfgaD1ACzul4NksrvIgYfIFq9JpDHFwQkMVZ10tbo0FU/grje4rcL4EIrjekl3kYwgWw=="
+        },
+        "@react-spring/web": {
+            "version": "9.7.3",
+            "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.3.tgz",
+            "integrity": "sha512-BXt6BpS9aJL/QdVqEIX9YoUy8CE6TJrU0mNCqSoxdXlIeNcEBWOfIyE6B14ENNsyQKS3wOWkiJfco0tCr/9tUg==",
+            "requires": {
+                "@react-spring/animated": "~9.7.3",
+                "@react-spring/core": "~9.7.3",
+                "@react-spring/shared": "~9.7.3",
+                "@react-spring/types": "~9.7.3"
+            }
+        },
         "@react-stately/calendar": {
             "version": "3.5.0",
             "resolved": "https://registry.npmjs.org/@react-stately/calendar/-/calendar-3.5.0.tgz",
@@ -5305,6 +5118,11 @@
                 "selderee": "^0.11.0"
             }
         },
+        "@sinclair/typebox": {
+            "version": "0.27.8",
+            "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
+            "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="
+        },
         "@sindresorhus/is": {
             "version": "6.3.0",
             "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-6.3.0.tgz",
@@ -7004,8 +6822,7 @@
         "@types/d3-scale-chromatic": {
             "version": "3.0.3",
             "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz",
-            "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==",
-            "dev": true
+            "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw=="
         },
         "@types/d3-selection": {
             "version": "3.0.10",
@@ -7169,9 +6986,9 @@
             }
         },
         "@types/geojson": {
-            "version": "7946.0.8",
-            "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz",
-            "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA=="
+            "version": "7946.0.14",
+            "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz",
+            "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg=="
         },
         "@types/glob": {
             "version": "7.2.0",
@@ -7241,6 +7058,27 @@
                 "@types/node": "*"
             }
         },
+        "@types/istanbul-lib-coverage": {
+            "version": "2.0.6",
+            "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
+            "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="
+        },
+        "@types/istanbul-lib-report": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
+            "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
+            "requires": {
+                "@types/istanbul-lib-coverage": "*"
+            }
+        },
+        "@types/istanbul-reports": {
+            "version": "3.0.4",
+            "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
+            "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
+            "requires": {
+                "@types/istanbul-lib-report": "*"
+            }
+        },
         "@types/jquery": {
             "version": "3.5.30",
             "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.30.tgz",
@@ -7271,6 +7109,11 @@
             "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
             "dev": true
         },
+        "@types/katex": {
+            "version": "0.16.7",
+            "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz",
+            "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ=="
+        },
         "@types/keygrip": {
             "version": "1.0.6",
             "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz",
@@ -7730,6 +7573,11 @@
             "integrity": "sha512-tgsWVG80dM5PVEBSbXUttPJTBCOo0IKbBh4R4z/SHsC5C81A3aaUH4fsbj+JYk7fopApU/Mao1c0EWTE592TSg==",
             "dev": true
         },
+        "@types/web": {
+            "version": "0.0.148",
+            "resolved": "https://registry.npmjs.org/@types/web/-/web-0.0.148.tgz",
+            "integrity": "sha512-HX2eARbn26tZuCOxZ25Ew6UUNhw8fgdGrOGcxX0/J6yTtlJm+nHlL9/h+2zgSzse13vlVe+c+W3LWqhnlAd5rg=="
+        },
         "@types/webidl-conversions": {
             "version": "7.0.3",
             "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
@@ -7773,12 +7621,123 @@
                 "@types/node": "*"
             }
         },
+        "@types/yargs": {
+            "version": "17.0.32",
+            "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz",
+            "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==",
+            "requires": {
+                "@types/yargs-parser": "*"
+            }
+        },
+        "@types/yargs-parser": {
+            "version": "21.0.3",
+            "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
+            "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="
+        },
         "@types/youtube": {
             "version": "0.0.50",
             "resolved": "https://registry.npmjs.org/@types/youtube/-/youtube-0.0.50.tgz",
             "integrity": "sha512-d4GpH4uPYp9W07kc487tiq6V/EUHl18vZWFMbQoe4Sk9LXEWzFi/BMf9x7TI4m7/j7gU3KeX8H6M8aPBgykeLw==",
             "dev": true
         },
+        "@typescript-eslint/eslint-plugin": {
+            "version": "7.13.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.13.0.tgz",
+            "integrity": "sha512-FX1X6AF0w8MdVFLSdqwqN/me2hyhuQg4ykN6ZpVhh1ij/80pTvDKclX1sZB9iqex8SjQfVhwMKs3JtnnMLzG9w==",
+            "dev": true,
+            "requires": {
+                "@eslint-community/regexpp": "^4.10.0",
+                "@typescript-eslint/scope-manager": "7.13.0",
+                "@typescript-eslint/type-utils": "7.13.0",
+                "@typescript-eslint/utils": "7.13.0",
+                "@typescript-eslint/visitor-keys": "7.13.0",
+                "graphemer": "^1.4.0",
+                "ignore": "^5.3.1",
+                "natural-compare": "^1.4.0",
+                "ts-api-utils": "^1.3.0"
+            }
+        },
+        "@typescript-eslint/parser": {
+            "version": "7.13.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.13.0.tgz",
+            "integrity": "sha512-EjMfl69KOS9awXXe83iRN7oIEXy9yYdqWfqdrFAYAAr6syP8eLEFI7ZE4939antx2mNgPRW/o1ybm2SFYkbTVA==",
+            "requires": {
+                "@typescript-eslint/scope-manager": "7.13.0",
+                "@typescript-eslint/types": "7.13.0",
+                "@typescript-eslint/typescript-estree": "7.13.0",
+                "@typescript-eslint/visitor-keys": "7.13.0",
+                "debug": "^4.3.4"
+            }
+        },
+        "@typescript-eslint/scope-manager": {
+            "version": "7.13.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.13.0.tgz",
+            "integrity": "sha512-ZrMCe1R6a01T94ilV13egvcnvVJ1pxShkE0+NDjDzH4nvG1wXpwsVI5bZCvE7AEDH1mXEx5tJSVR68bLgG7Dng==",
+            "requires": {
+                "@typescript-eslint/types": "7.13.0",
+                "@typescript-eslint/visitor-keys": "7.13.0"
+            }
+        },
+        "@typescript-eslint/type-utils": {
+            "version": "7.13.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.13.0.tgz",
+            "integrity": "sha512-xMEtMzxq9eRkZy48XuxlBFzpVMDurUAfDu5Rz16GouAtXm0TaAoTFzqWUFPPuQYXI/CDaH/Bgx/fk/84t/Bc9A==",
+            "dev": true,
+            "requires": {
+                "@typescript-eslint/typescript-estree": "7.13.0",
+                "@typescript-eslint/utils": "7.13.0",
+                "debug": "^4.3.4",
+                "ts-api-utils": "^1.3.0"
+            }
+        },
+        "@typescript-eslint/types": {
+            "version": "7.13.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.13.0.tgz",
+            "integrity": "sha512-QWuwm9wcGMAuTsxP+qz6LBBd3Uq8I5Nv8xb0mk54jmNoCyDspnMvVsOxI6IsMmway5d1S9Su2+sCKv1st2l6eA=="
+        },
+        "@typescript-eslint/typescript-estree": {
+            "version": "7.13.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.13.0.tgz",
+            "integrity": "sha512-cAvBvUoobaoIcoqox1YatXOnSl3gx92rCZoMRPzMNisDiM12siGilSM4+dJAekuuHTibI2hVC2fYK79iSFvWjw==",
+            "requires": {
+                "@typescript-eslint/types": "7.13.0",
+                "@typescript-eslint/visitor-keys": "7.13.0",
+                "debug": "^4.3.4",
+                "globby": "^11.1.0",
+                "is-glob": "^4.0.3",
+                "minimatch": "^9.0.4",
+                "semver": "^7.6.0",
+                "ts-api-utils": "^1.3.0"
+            },
+            "dependencies": {
+                "semver": {
+                    "version": "7.6.2",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
+                    "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w=="
+                }
+            }
+        },
+        "@typescript-eslint/utils": {
+            "version": "7.13.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.13.0.tgz",
+            "integrity": "sha512-jceD8RgdKORVnB4Y6BqasfIkFhl4pajB1wVxrF4akxD2QPM8GNYjgGwEzYS+437ewlqqrg7Dw+6dhdpjMpeBFQ==",
+            "dev": true,
+            "requires": {
+                "@eslint-community/eslint-utils": "^4.4.0",
+                "@typescript-eslint/scope-manager": "7.13.0",
+                "@typescript-eslint/types": "7.13.0",
+                "@typescript-eslint/typescript-estree": "7.13.0"
+            }
+        },
+        "@typescript-eslint/visitor-keys": {
+            "version": "7.13.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.13.0.tgz",
+            "integrity": "sha512-nxn+dozQx+MK61nn/JP+M4eCkHDSxSLDpgE3WcQo0+fkjEolnaB5jswvIKC4K56By8MMgIho7f1PVxERHEo8rw==",
+            "requires": {
+                "@typescript-eslint/types": "7.13.0",
+                "eslint-visitor-keys": "^3.4.3"
+            }
+        },
         "@ungap/structured-clone": {
             "version": "1.2.0",
             "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
@@ -8681,6 +8640,14 @@
             "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz",
             "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A=="
         },
+        "better-react-mathjax": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/better-react-mathjax/-/better-react-mathjax-2.0.3.tgz",
+            "integrity": "sha512-wfifT8GFOKb1TWm2+E50I6DJpLZ5kLbch283Lu043EJtwSv0XvZDjr4YfR4d2MjAhqP6SH4VjjrKgbX8R00oCQ==",
+            "requires": {
+                "mathjax-full": "^3.2.2"
+            }
+        },
         "bezier-curve": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/bezier-curve/-/bezier-curve-1.0.0.tgz",
@@ -8957,6 +8924,11 @@
                                     "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
                                     "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA=="
                                 },
+                                "ansi-styles": {
+                                    "version": "6.2.1",
+                                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+                                    "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="
+                                },
                                 "emoji-regex": {
                                     "version": "9.2.2",
                                     "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@@ -8979,6 +8951,16 @@
                                     "requires": {
                                         "ansi-regex": "^6.0.1"
                                     }
+                                },
+                                "wrap-ansi": {
+                                    "version": "8.1.0",
+                                    "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+                                    "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+                                    "requires": {
+                                        "ansi-styles": "^6.1.0",
+                                        "string-width": "^5.0.1",
+                                        "strip-ansi": "^7.0.1"
+                                    }
                                 }
                             }
                         },
@@ -10829,7 +10811,7 @@
                             }
                         },
                         "string-width": {
-                            "version": "npm:string-width@4.2.3",
+                            "version": "4.2.3",
                             "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
                             "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
                             "requires": {
@@ -10847,7 +10829,7 @@
                             }
                         },
                         "strip-ansi": {
-                            "version": "npm:strip-ansi@6.0.1",
+                            "version": "6.0.1",
                             "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
                             "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
                             "requires": {
@@ -11367,6 +11349,11 @@
             "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
             "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg=="
         },
+        "ci-info": {
+            "version": "3.9.0",
+            "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+            "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="
+        },
         "class-transformer": {
             "version": "0.5.1",
             "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
@@ -11706,6 +11693,35 @@
                 }
             }
         },
+        "compute-cosine-similarity": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/compute-cosine-similarity/-/compute-cosine-similarity-1.1.0.tgz",
+            "integrity": "sha512-FXhNx0ILLjGi9Z9+lglLzM12+0uoTnYkHm7GiadXDAr0HGVLm25OivUS1B/LPkbzzvlcXz/1EvWg9ZYyJSdhTw==",
+            "requires": {
+                "compute-dot": "^1.1.0",
+                "compute-l2norm": "^1.1.0",
+                "validate.io-array": "^1.0.5",
+                "validate.io-function": "^1.0.2"
+            }
+        },
+        "compute-dot": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/compute-dot/-/compute-dot-1.1.0.tgz",
+            "integrity": "sha512-L5Ocet4DdMrXboss13K59OK23GXjiSia7+7Ukc7q4Bl+RVpIXK2W9IHMbWDZkh+JUEvJAwOKRaJDiFUa1LTnJg==",
+            "requires": {
+                "validate.io-array": "^1.0.3",
+                "validate.io-function": "^1.0.2"
+            }
+        },
+        "compute-l2norm": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/compute-l2norm/-/compute-l2norm-1.1.0.tgz",
+            "integrity": "sha512-6EHh1Elj90eU28SXi+h2PLnTQvZmkkHWySpoFz+WOlVNLz3DQoC4ISUHSV9n5jMxPHtKGJ01F4uu2PsXBB8sSg==",
+            "requires": {
+                "validate.io-array": "^1.0.3",
+                "validate.io-function": "^1.0.2"
+            }
+        },
         "concat-map": {
             "version": "0.0.1",
             "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -11874,6 +11890,14 @@
                 "vary": "^1"
             }
         },
+        "cose-base": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz",
+            "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==",
+            "requires": {
+                "layout-base": "^1.0.0"
+            }
+        },
         "cosmiconfig": {
             "version": "7.1.0",
             "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
@@ -12059,11 +12083,44 @@
             "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.4.6.tgz",
             "integrity": "sha512-h2V2XZ3uOTLilF5dPIptgUfN/o2ia/80Ie0Lly18LAnw5s8Eb7kt8rfxSUy24AztJZas9f6DPZpVlzDUtFt/ag=="
         },
+        "csvtojson": {
+            "version": "2.0.10",
+            "resolved": "https://registry.npmjs.org/csvtojson/-/csvtojson-2.0.10.tgz",
+            "integrity": "sha512-lUWFxGKyhraKCW8Qghz6Z0f2l/PqB1W3AO0HKJzGIQ5JRSlR651ekJDiGJbBT4sRNNv5ddnSGVEnsxP9XRCVpQ==",
+            "requires": {
+                "bluebird": "^3.5.1",
+                "lodash": "^4.17.3",
+                "strip-bom": "^2.0.0"
+            },
+            "dependencies": {
+                "strip-bom": {
+                    "version": "2.0.0",
+                    "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+                    "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==",
+                    "requires": {
+                        "is-utf8": "^0.2.0"
+                    }
+                }
+            }
+        },
         "custom-event": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz",
             "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg=="
         },
+        "cytoscape": {
+            "version": "3.29.2",
+            "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.29.2.tgz",
+            "integrity": "sha512-2G1ycU28Nh7OHT9rkXRLpCDP30MKH1dXJORZuBhtEhEW7pKwgPi77ImqlCWinouyE1PNepIOGZBOrE84DG7LyQ=="
+        },
+        "cytoscape-cose-bilkent": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz",
+            "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==",
+            "requires": {
+                "cose-base": "^1.0.0"
+            }
+        },
         "d3": {
             "version": "7.9.0",
             "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz",
@@ -12288,6 +12345,30 @@
             "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz",
             "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ=="
         },
+        "d3-sankey": {
+            "version": "0.12.3",
+            "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz",
+            "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==",
+            "requires": {
+                "d3-array": "1 - 2",
+                "d3-shape": "^1.2.0"
+            },
+            "dependencies": {
+                "d3-path": {
+                    "version": "1.0.9",
+                    "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
+                    "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
+                },
+                "d3-shape": {
+                    "version": "1.3.7",
+                    "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
+                    "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
+                    "requires": {
+                        "d3-path": "1"
+                    }
+                }
+            }
+        },
         "d3-scale": {
             "version": "4.0.2",
             "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
@@ -12392,6 +12473,15 @@
                 "d3-transition": "2 - 3"
             }
         },
+        "dagre-d3-es": {
+            "version": "7.0.10",
+            "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.10.tgz",
+            "integrity": "sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==",
+            "requires": {
+                "d3": "^7.8.2",
+                "lodash-es": "^4.17.21"
+            }
+        },
         "damerau-levenshtein": {
             "version": "1.0.8",
             "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@@ -12486,6 +12576,11 @@
             "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
             "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="
         },
+        "dayjs": {
+            "version": "1.11.11",
+            "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz",
+            "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg=="
+        },
         "debug": {
             "version": "4.3.4",
             "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -12764,6 +12859,14 @@
             "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
             "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A=="
         },
+        "dir-glob": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+            "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+            "requires": {
+                "path-type": "^4.0.0"
+            }
+        },
         "dns-packet": {
             "version": "5.6.1",
             "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz",
@@ -12832,6 +12935,11 @@
                 "domelementtype": "^2.3.0"
             }
         },
+        "dompurify": {
+            "version": "3.1.5",
+            "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.5.tgz",
+            "integrity": "sha512-lwG+n5h8QNpxtyrJW/gJWckL+1/DQiYMX8f7t8Z2AZTPw1esVrqjI63i7Zc2Gz0aKzLVMYC1V1PL/ky+aY/NgA=="
+        },
         "domutils": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
@@ -12907,6 +13015,11 @@
             "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.761.tgz",
             "integrity": "sha512-PIbxpiJGx6Bb8dQaonNc6CGTRlVntdLg/2nMa1YhnrwYOORY9a3ZgGN0UQYE6lAcj/lkyduJN7BPt/JiY+jAQQ=="
         },
+        "elkjs": {
+            "version": "0.9.3",
+            "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz",
+            "integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ=="
+        },
         "emoji-regex": {
             "version": "9.2.2",
             "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@@ -13183,6 +13296,12 @@
                 "text-table": "^0.2.0"
             },
             "dependencies": {
+                "@eslint/js": {
+                    "version": "8.57.0",
+                    "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
+                    "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
+                    "dev": true
+                },
                 "ajv": {
                     "version": "6.12.6",
                     "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -14045,6 +14164,21 @@
                 }
             }
         },
+        "eslint-import-resolver-typescript": {
+            "version": "3.6.1",
+            "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz",
+            "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==",
+            "dev": true,
+            "requires": {
+                "debug": "^4.3.4",
+                "enhanced-resolve": "^5.12.0",
+                "eslint-module-utils": "^2.7.4",
+                "fast-glob": "^3.3.1",
+                "get-tsconfig": "^4.5.0",
+                "is-core-module": "^2.11.0",
+                "is-glob": "^4.0.3"
+            }
+        },
         "eslint-module-utils": {
             "version": "2.8.1",
             "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz",
@@ -14390,8 +14524,50 @@
         "eslint-visitor-keys": {
             "version": "3.4.3",
             "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
-            "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
-            "dev": true
+            "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="
+        },
+        "eslint-webpack-plugin": {
+            "version": "4.2.0",
+            "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-4.2.0.tgz",
+            "integrity": "sha512-rsfpFQ01AWQbqtjgPRr2usVRxhWDuG0YDYcG8DJOteD3EFnpeuYuOwk0PQiN7PRBTqS6ElNdtPZPggj8If9WnA==",
+            "requires": {
+                "@types/eslint": "^8.56.10",
+                "jest-worker": "^29.7.0",
+                "micromatch": "^4.0.5",
+                "normalize-path": "^3.0.0",
+                "schema-utils": "^4.2.0"
+            },
+            "dependencies": {
+                "has-flag": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+                    "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
+                },
+                "jest-worker": {
+                    "version": "29.7.0",
+                    "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
+                    "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
+                    "requires": {
+                        "@types/node": "*",
+                        "jest-util": "^29.7.0",
+                        "merge-stream": "^2.0.0",
+                        "supports-color": "^8.0.0"
+                    }
+                },
+                "supports-color": {
+                    "version": "8.1.1",
+                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+                    "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+                    "requires": {
+                        "has-flag": "^4.0.0"
+                    }
+                }
+            }
+        },
+        "esm": {
+            "version": "3.2.25",
+            "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
+            "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA=="
         },
         "espree": {
             "version": "9.6.1",
@@ -14702,6 +14878,11 @@
                 "tmp": "^0.0.33"
             }
         },
+        "extract-colors": {
+            "version": "4.0.6",
+            "resolved": "https://registry.npmjs.org/extract-colors/-/extract-colors-4.0.6.tgz",
+            "integrity": "sha512-U+pYyQKXCSHOmtZPIEJBGLJjLDiqS+oOub2ILA3a7UGt9+IvZvwAN3hOPFjUgT+gX/apSBwP5vBgnKMlV0fy8Q=="
+        },
         "extsprintf": {
             "version": "1.3.0",
             "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
@@ -14728,6 +14909,18 @@
             "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
             "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="
         },
+        "fast-glob": {
+            "version": "3.3.2",
+            "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+            "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+            "requires": {
+                "@nodelib/fs.stat": "^2.0.2",
+                "@nodelib/fs.walk": "^1.2.3",
+                "glob-parent": "^5.1.2",
+                "merge2": "^1.3.0",
+                "micromatch": "^4.0.4"
+            }
+        },
         "fast-json-stable-stringify": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -14748,7 +14941,6 @@
             "version": "1.17.1",
             "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
             "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
-            "dev": true,
             "requires": {
                 "reusify": "^1.0.4"
             }
@@ -15491,6 +15683,13 @@
                 "@turf/meta": "6.x",
                 "@types/geojson": "7946.0.8",
                 "rbush": "^3.0.1"
+            },
+            "dependencies": {
+                "@types/geojson": {
+                    "version": "7946.0.8",
+                    "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz",
+                    "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA=="
+                }
             }
         },
         "geojson-vt": {
@@ -15552,6 +15751,15 @@
                 "get-intrinsic": "^1.2.4"
             }
         },
+        "get-tsconfig": {
+            "version": "4.7.5",
+            "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz",
+            "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==",
+            "dev": true,
+            "requires": {
+                "resolve-pkg-maps": "^1.0.0"
+            }
+        },
         "get-value": {
             "version": "2.0.6",
             "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
@@ -15646,9 +15854,10 @@
             }
         },
         "globals": {
-            "version": "11.12.0",
-            "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
-            "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
+            "version": "15.4.0",
+            "resolved": "https://registry.npmjs.org/globals/-/globals-15.4.0.tgz",
+            "integrity": "sha512-unnwvMZpv0eDUyjNyh9DH/yxUaRYrEjW/qK4QcdrHg3oO11igUQrCSgODHEqxlKg8v2CD2Sd7UkqqEBoz5U7TQ==",
+            "dev": true
         },
         "globalthis": {
             "version": "1.0.4",
@@ -15660,6 +15869,19 @@
                 "gopd": "^1.0.1"
             }
         },
+        "globby": {
+            "version": "11.1.0",
+            "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+            "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+            "requires": {
+                "array-union": "^2.1.0",
+                "dir-glob": "^3.0.1",
+                "fast-glob": "^3.2.9",
+                "ignore": "^5.2.0",
+                "merge2": "^1.4.1",
+                "slash": "^3.0.0"
+            }
+        },
         "golden-layout": {
             "version": "2.6.0",
             "resolved": "https://registry.npmjs.org/golden-layout/-/golden-layout-2.6.0.tgz",
@@ -15865,6 +16087,40 @@
                 "function-bind": "^1.1.2"
             }
         },
+        "hast-util-from-dom": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.0.tgz",
+            "integrity": "sha512-d6235voAp/XR3Hh5uy7aGLbM3S4KamdW0WEgOaU1YoewnuYw4HXb5eRtv9g65m/RFGEfUY1Mw4UqCc5Y8L4Stg==",
+            "requires": {
+                "@types/hast": "^3.0.0",
+                "hastscript": "^8.0.0",
+                "web-namespaces": "^2.0.0"
+            }
+        },
+        "hast-util-from-html": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.1.tgz",
+            "integrity": "sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==",
+            "requires": {
+                "@types/hast": "^3.0.0",
+                "devlop": "^1.1.0",
+                "hast-util-from-parse5": "^8.0.0",
+                "parse5": "^7.0.0",
+                "vfile": "^6.0.0",
+                "vfile-message": "^4.0.0"
+            }
+        },
+        "hast-util-from-html-isomorphic": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz",
+            "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==",
+            "requires": {
+                "@types/hast": "^3.0.0",
+                "hast-util-from-dom": "^5.0.0",
+                "hast-util-from-html": "^2.0.0",
+                "unist-util-remove-position": "^5.0.0"
+            }
+        },
         "hast-util-from-parse5": {
             "version": "8.0.1",
             "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz",
@@ -15880,6 +16136,14 @@
                 "web-namespaces": "^2.0.0"
             }
         },
+        "hast-util-is-element": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz",
+            "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==",
+            "requires": {
+                "@types/hast": "^3.0.0"
+            }
+        },
         "hast-util-parse-selector": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz",
@@ -15944,6 +16208,17 @@
                 "zwitch": "^2.0.0"
             }
         },
+        "hast-util-to-text": {
+            "version": "4.0.2",
+            "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz",
+            "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==",
+            "requires": {
+                "@types/hast": "^3.0.0",
+                "@types/unist": "^3.0.0",
+                "hast-util-is-element": "^3.0.0",
+                "unist-util-find-after": "^5.0.0"
+            }
+        },
         "hast-util-whitespace": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
@@ -17167,6 +17442,11 @@
             "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
             "dev": true
         },
+        "is-utf8": {
+            "version": "0.2.1",
+            "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
+            "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q=="
+        },
         "is-weakmap": {
             "version": "2.0.2",
             "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
@@ -17266,6 +17546,64 @@
                 "@pkgjs/parseargs": "^0.11.0"
             }
         },
+        "jest-util": {
+            "version": "29.7.0",
+            "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
+            "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
+            "requires": {
+                "@jest/types": "^29.6.3",
+                "@types/node": "*",
+                "chalk": "^4.0.0",
+                "ci-info": "^3.2.0",
+                "graceful-fs": "^4.2.9",
+                "picomatch": "^2.2.3"
+            },
+            "dependencies": {
+                "ansi-styles": {
+                    "version": "4.3.0",
+                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+                    "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+                    "requires": {
+                        "color-convert": "^2.0.1"
+                    }
+                },
+                "chalk": {
+                    "version": "4.1.2",
+                    "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+                    "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+                    "requires": {
+                        "ansi-styles": "^4.1.0",
+                        "supports-color": "^7.1.0"
+                    }
+                },
+                "color-convert": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+                    "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+                    "requires": {
+                        "color-name": "~1.1.4"
+                    }
+                },
+                "color-name": {
+                    "version": "1.1.4",
+                    "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+                    "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+                },
+                "has-flag": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+                    "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
+                },
+                "supports-color": {
+                    "version": "7.2.0",
+                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+                    "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+                    "requires": {
+                        "has-flag": "^4.0.0"
+                    }
+                }
+            }
+        },
         "jest-worker": {
             "version": "27.5.1",
             "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
@@ -17623,6 +17961,21 @@
             "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
             "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q=="
         },
+        "katex": {
+            "version": "0.16.10",
+            "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.10.tgz",
+            "integrity": "sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==",
+            "requires": {
+                "commander": "^8.3.0"
+            },
+            "dependencies": {
+                "commander": {
+                    "version": "8.3.0",
+                    "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
+                    "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="
+                }
+            }
+        },
         "kdbush": {
             "version": "4.0.2",
             "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz",
@@ -17644,6 +17997,11 @@
                 "json-buffer": "3.0.1"
             }
         },
+        "khroma": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz",
+            "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="
+        },
         "kind-of": {
             "version": "6.0.3",
             "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@@ -17657,6 +18015,11 @@
                 "graceful-fs": "^4.1.9"
             }
         },
+        "kleur": {
+            "version": "4.1.5",
+            "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+            "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="
+        },
         "kruptein": {
             "version": "3.0.6",
             "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.6.tgz",
@@ -17715,6 +18078,11 @@
                 "shell-quote": "^1.8.1"
             }
         },
+        "layout-base": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz",
+            "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="
+        },
         "lazystream": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz",
@@ -17739,7 +18107,12 @@
                 }
             }
         },
-        "node_modules/leac": {
+        "ldrs": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/ldrs/-/ldrs-1.0.2.tgz",
+            "integrity": "sha512-sYJmivdkIiHrUEqTrEWccBoLdaENpzbzkABI5rk8rRxTXrg9i2xVuDvUUuhOhJY3RmQyaoxs046pM1DCRdcIpg=="
+        },
+        "leac": {
             "version": "0.6.0",
             "resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz",
             "integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg=="
@@ -17767,6 +18140,14 @@
             "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
             "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
         },
+        "linkify-it": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
+            "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
+            "requires": {
+                "uc.micro": "^2.0.0"
+            }
+        },
         "load-bmfont": {
             "version": "1.4.1",
             "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz",
@@ -18058,6 +18439,26 @@
                 "vt-pbf": "^3.1.3"
             }
         },
+        "markdown-it": {
+            "version": "14.1.0",
+            "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
+            "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
+            "requires": {
+                "argparse": "^2.0.1",
+                "entities": "^4.4.0",
+                "linkify-it": "^5.0.0",
+                "mdurl": "^2.0.0",
+                "punycode.js": "^2.3.1",
+                "uc.micro": "^2.1.0"
+            },
+            "dependencies": {
+                "argparse": {
+                    "version": "2.0.1",
+                    "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+                    "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+                }
+            }
+        },
         "markdown-table": {
             "version": "3.0.3",
             "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz",
@@ -18077,6 +18478,17 @@
                 "mr-parser": "^0.2.1"
             }
         },
+        "mathjax-full": {
+            "version": "3.2.2",
+            "resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.2.tgz",
+            "integrity": "sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==",
+            "requires": {
+                "esm": "^3.2.25",
+                "mhchemparser": "^4.1.0",
+                "mj-context-menu": "^0.6.1",
+                "speech-rule-engine": "^4.0.6"
+            }
+        },
         "mathquill": {
             "version": "0.10.1-a",
             "resolved": "https://registry.npmjs.org/mathquill/-/mathquill-0.10.1-a.tgz",
@@ -18205,6 +18617,20 @@
                 "mdast-util-to-markdown": "^2.0.0"
             }
         },
+        "mdast-util-math": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz",
+            "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==",
+            "requires": {
+                "@types/hast": "^3.0.0",
+                "@types/mdast": "^4.0.0",
+                "devlop": "^1.0.0",
+                "longest-streak": "^3.0.0",
+                "mdast-util-from-markdown": "^2.0.0",
+                "mdast-util-to-markdown": "^2.1.0",
+                "unist-util-remove-position": "^5.0.0"
+            }
+        },
         "mdast-util-mdx-expression": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.0.tgz",
@@ -18299,6 +18725,11 @@
                 "@types/mdast": "^4.0.0"
             }
         },
+        "mdurl": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
+            "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="
+        },
         "media-typer": {
             "version": "0.3.0",
             "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -18337,11 +18768,309 @@
             "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
             "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
         },
+        "merge2": {
+            "version": "1.4.1",
+            "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+            "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
+        },
+        "mermaid": {
+            "version": "10.9.1",
+            "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.9.1.tgz",
+            "integrity": "sha512-Mx45Obds5W1UkW1nv/7dHRsbfMM1aOKA2+Pxs/IGHNonygDHwmng8xTHyS9z4KWVi0rbko8gjiBmuwwXQ7tiNA==",
+            "requires": {
+                "@braintree/sanitize-url": "^6.0.1",
+                "@types/d3-scale": "^4.0.3",
+                "@types/d3-scale-chromatic": "^3.0.0",
+                "cytoscape": "^3.28.1",
+                "cytoscape-cose-bilkent": "^4.1.0",
+                "d3": "^7.4.0",
+                "d3-sankey": "^0.12.3",
+                "dagre-d3-es": "7.0.10",
+                "dayjs": "^1.11.7",
+                "dompurify": "^3.0.5",
+                "elkjs": "^0.9.0",
+                "katex": "^0.16.9",
+                "khroma": "^2.0.0",
+                "lodash-es": "^4.17.21",
+                "mdast-util-from-markdown": "^1.3.0",
+                "non-layered-tidy-tree-layout": "^2.0.2",
+                "stylis": "^4.1.3",
+                "ts-dedent": "^2.2.0",
+                "uuid": "^9.0.0",
+                "web-worker": "^1.2.0"
+            },
+            "dependencies": {
+                "@types/mdast": {
+                    "version": "3.0.15",
+                    "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz",
+                    "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==",
+                    "requires": {
+                        "@types/unist": "^2"
+                    }
+                },
+                "@types/unist": {
+                    "version": "2.0.10",
+                    "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz",
+                    "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA=="
+                },
+                "mdast-util-from-markdown": {
+                    "version": "1.3.1",
+                    "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz",
+                    "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==",
+                    "requires": {
+                        "@types/mdast": "^3.0.0",
+                        "@types/unist": "^2.0.0",
+                        "decode-named-character-reference": "^1.0.0",
+                        "mdast-util-to-string": "^3.1.0",
+                        "micromark": "^3.0.0",
+                        "micromark-util-decode-numeric-character-reference": "^1.0.0",
+                        "micromark-util-decode-string": "^1.0.0",
+                        "micromark-util-normalize-identifier": "^1.0.0",
+                        "micromark-util-symbol": "^1.0.0",
+                        "micromark-util-types": "^1.0.0",
+                        "unist-util-stringify-position": "^3.0.0",
+                        "uvu": "^0.5.0"
+                    }
+                },
+                "mdast-util-to-string": {
+                    "version": "3.2.0",
+                    "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz",
+                    "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==",
+                    "requires": {
+                        "@types/mdast": "^3.0.0"
+                    }
+                },
+                "micromark": {
+                    "version": "3.2.0",
+                    "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz",
+                    "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==",
+                    "requires": {
+                        "@types/debug": "^4.0.0",
+                        "debug": "^4.0.0",
+                        "decode-named-character-reference": "^1.0.0",
+                        "micromark-core-commonmark": "^1.0.1",
+                        "micromark-factory-space": "^1.0.0",
+                        "micromark-util-character": "^1.0.0",
+                        "micromark-util-chunked": "^1.0.0",
+                        "micromark-util-combine-extensions": "^1.0.0",
+                        "micromark-util-decode-numeric-character-reference": "^1.0.0",
+                        "micromark-util-encode": "^1.0.0",
+                        "micromark-util-normalize-identifier": "^1.0.0",
+                        "micromark-util-resolve-all": "^1.0.0",
+                        "micromark-util-sanitize-uri": "^1.0.0",
+                        "micromark-util-subtokenize": "^1.0.0",
+                        "micromark-util-symbol": "^1.0.0",
+                        "micromark-util-types": "^1.0.1",
+                        "uvu": "^0.5.0"
+                    }
+                },
+                "micromark-core-commonmark": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz",
+                    "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==",
+                    "requires": {
+                        "decode-named-character-reference": "^1.0.0",
+                        "micromark-factory-destination": "^1.0.0",
+                        "micromark-factory-label": "^1.0.0",
+                        "micromark-factory-space": "^1.0.0",
+                        "micromark-factory-title": "^1.0.0",
+                        "micromark-factory-whitespace": "^1.0.0",
+                        "micromark-util-character": "^1.0.0",
+                        "micromark-util-chunked": "^1.0.0",
+                        "micromark-util-classify-character": "^1.0.0",
+                        "micromark-util-html-tag-name": "^1.0.0",
+                        "micromark-util-normalize-identifier": "^1.0.0",
+                        "micromark-util-resolve-all": "^1.0.0",
+                        "micromark-util-subtokenize": "^1.0.0",
+                        "micromark-util-symbol": "^1.0.0",
+                        "micromark-util-types": "^1.0.1",
+                        "uvu": "^0.5.0"
+                    }
+                },
+                "micromark-factory-destination": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz",
+                    "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==",
+                    "requires": {
+                        "micromark-util-character": "^1.0.0",
+                        "micromark-util-symbol": "^1.0.0",
+                        "micromark-util-types": "^1.0.0"
+                    }
+                },
+                "micromark-factory-label": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz",
+                    "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==",
+                    "requires": {
+                        "micromark-util-character": "^1.0.0",
+                        "micromark-util-symbol": "^1.0.0",
+                        "micromark-util-types": "^1.0.0",
+                        "uvu": "^0.5.0"
+                    }
+                },
+                "micromark-factory-space": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz",
+                    "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==",
+                    "requires": {
+                        "micromark-util-character": "^1.0.0",
+                        "micromark-util-types": "^1.0.0"
+                    }
+                },
+                "micromark-factory-title": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz",
+                    "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==",
+                    "requires": {
+                        "micromark-factory-space": "^1.0.0",
+                        "micromark-util-character": "^1.0.0",
+                        "micromark-util-symbol": "^1.0.0",
+                        "micromark-util-types": "^1.0.0"
+                    }
+                },
+                "micromark-factory-whitespace": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz",
+                    "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==",
+                    "requires": {
+                        "micromark-factory-space": "^1.0.0",
+                        "micromark-util-character": "^1.0.0",
+                        "micromark-util-symbol": "^1.0.0",
+                        "micromark-util-types": "^1.0.0"
+                    }
+                },
+                "micromark-util-character": {
+                    "version": "1.2.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz",
+                    "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==",
+                    "requires": {
+                        "micromark-util-symbol": "^1.0.0",
+                        "micromark-util-types": "^1.0.0"
+                    }
+                },
+                "micromark-util-chunked": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz",
+                    "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==",
+                    "requires": {
+                        "micromark-util-symbol": "^1.0.0"
+                    }
+                },
+                "micromark-util-classify-character": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz",
+                    "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==",
+                    "requires": {
+                        "micromark-util-character": "^1.0.0",
+                        "micromark-util-symbol": "^1.0.0",
+                        "micromark-util-types": "^1.0.0"
+                    }
+                },
+                "micromark-util-combine-extensions": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz",
+                    "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==",
+                    "requires": {
+                        "micromark-util-chunked": "^1.0.0",
+                        "micromark-util-types": "^1.0.0"
+                    }
+                },
+                "micromark-util-decode-numeric-character-reference": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz",
+                    "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==",
+                    "requires": {
+                        "micromark-util-symbol": "^1.0.0"
+                    }
+                },
+                "micromark-util-decode-string": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz",
+                    "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==",
+                    "requires": {
+                        "decode-named-character-reference": "^1.0.0",
+                        "micromark-util-character": "^1.0.0",
+                        "micromark-util-decode-numeric-character-reference": "^1.0.0",
+                        "micromark-util-symbol": "^1.0.0"
+                    }
+                },
+                "micromark-util-encode": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz",
+                    "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw=="
+                },
+                "micromark-util-html-tag-name": {
+                    "version": "1.2.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz",
+                    "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q=="
+                },
+                "micromark-util-normalize-identifier": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz",
+                    "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==",
+                    "requires": {
+                        "micromark-util-symbol": "^1.0.0"
+                    }
+                },
+                "micromark-util-resolve-all": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz",
+                    "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==",
+                    "requires": {
+                        "micromark-util-types": "^1.0.0"
+                    }
+                },
+                "micromark-util-sanitize-uri": {
+                    "version": "1.2.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz",
+                    "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==",
+                    "requires": {
+                        "micromark-util-character": "^1.0.0",
+                        "micromark-util-encode": "^1.0.0",
+                        "micromark-util-symbol": "^1.0.0"
+                    }
+                },
+                "micromark-util-subtokenize": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz",
+                    "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==",
+                    "requires": {
+                        "micromark-util-chunked": "^1.0.0",
+                        "micromark-util-symbol": "^1.0.0",
+                        "micromark-util-types": "^1.0.0",
+                        "uvu": "^0.5.0"
+                    }
+                },
+                "micromark-util-symbol": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz",
+                    "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag=="
+                },
+                "micromark-util-types": {
+                    "version": "1.1.0",
+                    "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz",
+                    "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg=="
+                },
+                "unist-util-stringify-position": {
+                    "version": "3.0.3",
+                    "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz",
+                    "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==",
+                    "requires": {
+                        "@types/unist": "^2.0.0"
+                    }
+                }
+            }
+        },
         "methods": {
             "version": "1.1.2",
             "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
             "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="
         },
+        "mhchemparser": {
+            "version": "4.2.1",
+            "resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.2.1.tgz",
+            "integrity": "sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ=="
+        },
         "micromark": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz",
@@ -18475,6 +19204,20 @@
                 "micromark-util-types": "^2.0.0"
             }
         },
+        "micromark-extension-math": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.0.0.tgz",
+            "integrity": "sha512-iJ2Q28vBoEovLN5o3GO12CpqorQRYDPT+p4zW50tGwTfJB+iv/VnB6Ini+gqa24K97DwptMBBIvVX6Bjk49oyQ==",
+            "requires": {
+                "@types/katex": "^0.16.0",
+                "devlop": "^1.0.0",
+                "katex": "^0.16.0",
+                "micromark-factory-space": "^2.0.0",
+                "micromark-util-character": "^2.0.0",
+                "micromark-util-symbol": "^2.0.0",
+                "micromark-util-types": "^2.0.0"
+            }
+        },
         "micromark-factory-destination": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz",
@@ -18736,6 +19479,11 @@
                 }
             }
         },
+        "mj-context-menu": {
+            "version": "0.6.1",
+            "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz",
+            "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA=="
+        },
         "mkdirp": {
             "version": "1.0.4",
             "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
@@ -19044,6 +19792,11 @@
             "resolved": "https://registry.npmjs.org/mr-parser/-/mr-parser-0.2.1.tgz",
             "integrity": "sha512-hug+mpbSSKnH13rFqy3zm+XiG+QTStiDAgMTHK355TIstQE0qBkBtSJsa5YHP94AuarVX9b/4dcebdTRZ9YiEw=="
         },
+        "mri": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
+            "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="
+        },
         "ms": {
             "version": "2.1.2",
             "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -19102,9 +19855,9 @@
             "dev": true
         },
         "nan": {
-            "version": "2.19.0",
-            "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz",
-            "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==",
+            "version": "2.20.0",
+            "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz",
+            "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==",
             "optional": true
         },
         "nanoid": {
@@ -19256,6 +20009,11 @@
                 }
             }
         },
+        "non-layered-tidy-tree-layout": {
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz",
+            "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw=="
+        },
         "nopt": {
             "version": "5.0.0",
             "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
@@ -19352,7 +20110,7 @@
             "dependencies": {
                 "@isaacs/cliui": {
                     "version": "8.0.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "string-width": "^5.1.2",
                         "string-width-cjs": "npm:string-width@^4.2.0",
@@ -19364,7 +20122,7 @@
                     "dependencies": {
                         "ansi-regex": {
                             "version": "6.0.1",
-                            "bundled": true
+                            "resolved": false
                         },
                         "ansi-styles": {
                             "version": "4.3.0",
@@ -19376,11 +20134,11 @@
                         },
                         "emoji-regex": {
                             "version": "9.2.2",
-                            "bundled": true
+                            "resolved": false
                         },
                         "string-width": {
                             "version": "5.1.2",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "eastasianwidth": "^0.2.0",
                                 "emoji-regex": "^9.2.2",
@@ -19419,7 +20177,7 @@
                         },
                         "strip-ansi": {
                             "version": "7.1.0",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "ansi-regex": "^6.0.1"
                             }
@@ -19483,11 +20241,11 @@
                 },
                 "@isaacs/string-locale-compare": {
                     "version": "1.1.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "@npmcli/agent": {
                     "version": "2.2.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "agent-base": "^7.1.0",
                         "http-proxy-agent": "^7.0.0",
@@ -19498,7 +20256,7 @@
                 },
                 "@npmcli/arborist": {
                     "version": "7.5.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@isaacs/string-locale-compare": "^1.1.0",
                         "@npmcli/fs": "^3.1.1",
@@ -19539,7 +20297,7 @@
                 },
                 "@npmcli/config": {
                     "version": "8.3.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/map-workspaces": "^3.0.2",
                         "ci-info": "^4.0.0",
@@ -19553,14 +20311,14 @@
                 },
                 "@npmcli/fs": {
                     "version": "3.1.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "semver": "^7.3.5"
                     }
                 },
                 "@npmcli/git": {
                     "version": "5.0.7",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/promise-spawn": "^7.0.0",
                         "lru-cache": "^10.0.1",
@@ -19574,7 +20332,7 @@
                 },
                 "@npmcli/installed-package-contents": {
                     "version": "2.1.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "npm-bundled": "^3.0.0",
                         "npm-normalize-package-bin": "^3.0.0"
@@ -19582,7 +20340,7 @@
                 },
                 "@npmcli/map-workspaces": {
                     "version": "3.0.6",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/name-from-folder": "^2.0.0",
                         "glob": "^10.2.2",
@@ -19592,7 +20350,7 @@
                 },
                 "@npmcli/metavuln-calculator": {
                     "version": "7.1.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "cacache": "^18.0.0",
                         "json-parse-even-better-errors": "^3.0.0",
@@ -19603,15 +20361,15 @@
                 },
                 "@npmcli/name-from-folder": {
                     "version": "2.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "@npmcli/node-gyp": {
                     "version": "3.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "@npmcli/package-json": {
                     "version": "5.1.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/git": "^5.0.0",
                         "glob": "^10.2.2",
@@ -19624,25 +20382,25 @@
                 },
                 "@npmcli/promise-spawn": {
                     "version": "7.0.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "which": "^4.0.0"
                     }
                 },
                 "@npmcli/query": {
                     "version": "3.1.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "postcss-selector-parser": "^6.0.10"
                     }
                 },
                 "@npmcli/redact": {
                     "version": "2.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "@npmcli/run-script": {
                     "version": "8.1.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/node-gyp": "^3.0.0",
                         "@npmcli/package-json": "^5.0.0",
@@ -19654,27 +20412,27 @@
                 },
                 "@pkgjs/parseargs": {
                     "version": "0.11.0",
-                    "bundled": true,
+                    "resolved": false,
                     "optional": true
                 },
                 "@sigstore/bundle": {
                     "version": "2.3.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@sigstore/protobuf-specs": "^0.3.2"
                     }
                 },
                 "@sigstore/core": {
                     "version": "1.1.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "@sigstore/protobuf-specs": {
                     "version": "0.3.2",
-                    "bundled": true
+                    "resolved": false
                 },
                 "@sigstore/sign": {
                     "version": "2.3.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@sigstore/bundle": "^2.3.2",
                         "@sigstore/core": "^1.0.0",
@@ -19686,7 +20444,7 @@
                 },
                 "@sigstore/tuf": {
                     "version": "2.3.4",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@sigstore/protobuf-specs": "^0.3.2",
                         "tuf-js": "^2.2.1"
@@ -19694,7 +20452,7 @@
                 },
                 "@sigstore/verify": {
                     "version": "1.2.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@sigstore/bundle": "^2.3.2",
                         "@sigstore/core": "^1.1.0",
@@ -19703,11 +20461,11 @@
                 },
                 "@tufjs/canonical-json": {
                     "version": "2.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "@tufjs/models": {
                     "version": "2.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@tufjs/canonical-json": "2.0.0",
                         "minimatch": "^9.0.4"
@@ -19715,18 +20473,18 @@
                 },
                 "abbrev": {
                     "version": "2.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "agent-base": {
                     "version": "7.1.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "debug": "^4.3.4"
                     }
                 },
                 "aggregate-error": {
                     "version": "3.1.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "clean-stack": "^2.0.0",
                         "indent-string": "^4.0.0"
@@ -19734,27 +20492,27 @@
                 },
                 "ansi-regex": {
                     "version": "5.0.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "ansi-styles": {
                     "version": "6.2.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "aproba": {
                     "version": "2.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "archy": {
                     "version": "1.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "balanced-match": {
                     "version": "1.0.2",
-                    "bundled": true
+                    "resolved": false
                 },
                 "bin-links": {
                     "version": "4.0.4",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "cmd-shim": "^6.0.0",
                         "npm-normalize-package-bin": "^3.0.0",
@@ -19764,18 +20522,18 @@
                 },
                 "binary-extensions": {
                     "version": "2.3.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "brace-expansion": {
                     "version": "2.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "balanced-match": "^1.0.0"
                     }
                 },
                 "cacache": {
                     "version": "18.0.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/fs": "^3.1.0",
                         "fs-minipass": "^3.0.0",
@@ -19793,30 +20551,30 @@
                 },
                 "chalk": {
                     "version": "5.3.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "chownr": {
                     "version": "2.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "ci-info": {
                     "version": "4.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "cidr-regex": {
                     "version": "4.1.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "ip-regex": "^5.0.0"
                     }
                 },
                 "clean-stack": {
                     "version": "2.2.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "cli-columns": {
                     "version": "4.0.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "string-width": "^4.2.3",
                         "strip-ansi": "^6.0.1"
@@ -19824,26 +20582,26 @@
                 },
                 "cmd-shim": {
                     "version": "6.0.3",
-                    "bundled": true
+                    "resolved": false
                 },
                 "color-convert": {
                     "version": "2.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "color-name": "~1.1.4"
                     }
                 },
                 "color-name": {
                     "version": "1.1.4",
-                    "bundled": true
+                    "resolved": false
                 },
                 "common-ancestor-path": {
                     "version": "1.0.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "cross-spawn": {
                     "version": "7.0.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "path-key": "^3.1.0",
                         "shebang-command": "^2.0.0",
@@ -19852,7 +20610,7 @@
                     "dependencies": {
                         "which": {
                             "version": "2.0.2",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "isexe": "^2.0.0"
                             }
@@ -19861,36 +20619,36 @@
                 },
                 "cssesc": {
                     "version": "3.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "debug": {
                     "version": "4.3.4",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "ms": "2.1.2"
                     },
                     "dependencies": {
                         "ms": {
                             "version": "2.1.2",
-                            "bundled": true
+                            "resolved": false
                         }
                     }
                 },
                 "diff": {
                     "version": "5.2.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "eastasianwidth": {
                     "version": "0.2.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "emoji-regex": {
                     "version": "8.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "encoding": {
                     "version": "0.1.13",
-                    "bundled": true,
+                    "resolved": false,
                     "optional": true,
                     "requires": {
                         "iconv-lite": "^0.6.2"
@@ -19898,23 +20656,23 @@
                 },
                 "env-paths": {
                     "version": "2.2.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "err-code": {
                     "version": "2.0.3",
-                    "bundled": true
+                    "resolved": false
                 },
                 "exponential-backoff": {
                     "version": "3.1.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "fastest-levenshtein": {
                     "version": "1.0.16",
-                    "bundled": true
+                    "resolved": false
                 },
                 "foreground-child": {
                     "version": "3.1.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "cross-spawn": "^7.0.0",
                         "signal-exit": "^4.0.1"
@@ -19922,18 +20680,18 @@
                 },
                 "fs-minipass": {
                     "version": "3.0.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "minipass": "^7.0.3"
                     }
                 },
                 "function-bind": {
                     "version": "1.1.2",
-                    "bundled": true
+                    "resolved": false
                 },
                 "glob": {
                     "version": "10.4.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "foreground-child": "^3.1.0",
                         "jackspeak": "^3.1.2",
@@ -19944,29 +20702,29 @@
                 },
                 "graceful-fs": {
                     "version": "4.2.11",
-                    "bundled": true
+                    "resolved": false
                 },
                 "hasown": {
                     "version": "2.0.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "function-bind": "^1.1.2"
                     }
                 },
                 "hosted-git-info": {
                     "version": "7.0.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "lru-cache": "^10.0.1"
                     }
                 },
                 "http-cache-semantics": {
                     "version": "4.1.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "http-proxy-agent": {
                     "version": "7.0.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "agent-base": "^7.1.0",
                         "debug": "^4.3.4"
@@ -19974,7 +20732,7 @@
                 },
                 "https-proxy-agent": {
                     "version": "7.0.4",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "agent-base": "^7.0.2",
                         "debug": "4"
@@ -19982,7 +20740,7 @@
                 },
                 "iconv-lite": {
                     "version": "0.6.3",
-                    "bundled": true,
+                    "resolved": false,
                     "optional": true,
                     "requires": {
                         "safer-buffer": ">= 2.1.2 < 3.0.0"
@@ -19990,26 +20748,26 @@
                 },
                 "ignore-walk": {
                     "version": "6.0.5",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "minimatch": "^9.0.0"
                     }
                 },
                 "imurmurhash": {
                     "version": "0.1.4",
-                    "bundled": true
+                    "resolved": false
                 },
                 "indent-string": {
                     "version": "4.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "ini": {
                     "version": "4.1.3",
-                    "bundled": true
+                    "resolved": false
                 },
                 "init-package-json": {
                     "version": "6.0.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/package-json": "^5.0.0",
                         "npm-package-arg": "^11.0.0",
@@ -20022,7 +20780,7 @@
                 },
                 "ip-address": {
                     "version": "9.0.5",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "jsbn": "1.1.0",
                         "sprintf-js": "^1.1.3"
@@ -20030,37 +20788,37 @@
                 },
                 "ip-regex": {
                     "version": "5.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "is-cidr": {
                     "version": "5.1.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "cidr-regex": "^4.1.1"
                     }
                 },
                 "is-core-module": {
                     "version": "2.13.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "hasown": "^2.0.0"
                     }
                 },
                 "is-fullwidth-code-point": {
                     "version": "3.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "is-lambda": {
                     "version": "1.0.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "isexe": {
                     "version": "2.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "jackspeak": {
                     "version": "3.1.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@isaacs/cliui": "^8.0.2",
                         "@pkgjs/parseargs": "^0.11.0"
@@ -20068,31 +20826,31 @@
                 },
                 "jsbn": {
                     "version": "1.1.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "json-parse-even-better-errors": {
                     "version": "3.0.2",
-                    "bundled": true
+                    "resolved": false
                 },
                 "json-stringify-nice": {
                     "version": "1.1.4",
-                    "bundled": true
+                    "resolved": false
                 },
                 "jsonparse": {
                     "version": "1.3.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "just-diff": {
                     "version": "6.0.2",
-                    "bundled": true
+                    "resolved": false
                 },
                 "just-diff-apply": {
                     "version": "5.5.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "libnpmaccess": {
                     "version": "8.0.6",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "npm-package-arg": "^11.0.2",
                         "npm-registry-fetch": "^17.0.1"
@@ -20100,7 +20858,7 @@
                 },
                 "libnpmdiff": {
                     "version": "6.1.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/arborist": "^7.5.3",
                         "@npmcli/installed-package-contents": "^2.1.0",
@@ -20114,7 +20872,7 @@
                 },
                 "libnpmexec": {
                     "version": "8.1.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/arborist": "^7.5.3",
                         "@npmcli/run-script": "^8.1.0",
@@ -20130,14 +20888,14 @@
                 },
                 "libnpmfund": {
                     "version": "5.0.11",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/arborist": "^7.5.3"
                     }
                 },
                 "libnpmhook": {
                     "version": "10.0.5",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "aproba": "^2.0.0",
                         "npm-registry-fetch": "^17.0.1"
@@ -20145,7 +20903,7 @@
                 },
                 "libnpmorg": {
                     "version": "6.0.6",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "aproba": "^2.0.0",
                         "npm-registry-fetch": "^17.0.1"
@@ -20153,7 +20911,7 @@
                 },
                 "libnpmpack": {
                     "version": "7.0.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/arborist": "^7.5.3",
                         "@npmcli/run-script": "^8.1.0",
@@ -20163,7 +20921,7 @@
                 },
                 "libnpmpublish": {
                     "version": "9.0.9",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "ci-info": "^4.0.0",
                         "normalize-package-data": "^6.0.1",
@@ -20177,14 +20935,14 @@
                 },
                 "libnpmsearch": {
                     "version": "7.0.6",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "npm-registry-fetch": "^17.0.1"
                     }
                 },
                 "libnpmteam": {
                     "version": "6.0.5",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "aproba": "^2.0.0",
                         "npm-registry-fetch": "^17.0.1"
@@ -20192,7 +20950,7 @@
                 },
                 "libnpmversion": {
                     "version": "6.0.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/git": "^5.0.7",
                         "@npmcli/run-script": "^8.1.0",
@@ -20203,11 +20961,11 @@
                 },
                 "lru-cache": {
                     "version": "10.2.2",
-                    "bundled": true
+                    "resolved": false
                 },
                 "make-fetch-happen": {
                     "version": "13.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/agent": "^2.0.0",
                         "cacache": "^18.0.0",
@@ -20225,25 +20983,25 @@
                 },
                 "minimatch": {
                     "version": "9.0.4",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "brace-expansion": "^2.0.1"
                     }
                 },
                 "minipass": {
                     "version": "7.1.2",
-                    "bundled": true
+                    "resolved": false
                 },
                 "minipass-collect": {
                     "version": "2.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "minipass": "^7.0.3"
                     }
                 },
                 "minipass-fetch": {
                     "version": "3.0.5",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "encoding": "^0.1.13",
                         "minipass": "^7.0.3",
@@ -20253,14 +21011,14 @@
                 },
                 "minipass-flush": {
                     "version": "1.0.5",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "minipass": "^3.0.0"
                     },
                     "dependencies": {
                         "minipass": {
                             "version": "3.3.6",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "yallist": "^4.0.0"
                             }
@@ -20269,7 +21027,7 @@
                 },
                 "minipass-json-stream": {
                     "version": "1.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "jsonparse": "^1.3.1",
                         "minipass": "^3.0.0"
@@ -20277,7 +21035,7 @@
                     "dependencies": {
                         "minipass": {
                             "version": "3.3.6",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "yallist": "^4.0.0"
                             }
@@ -20286,14 +21044,14 @@
                 },
                 "minipass-pipeline": {
                     "version": "1.2.4",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "minipass": "^3.0.0"
                     },
                     "dependencies": {
                         "minipass": {
                             "version": "3.3.6",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "yallist": "^4.0.0"
                             }
@@ -20302,14 +21060,14 @@
                 },
                 "minipass-sized": {
                     "version": "1.0.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "minipass": "^3.0.0"
                     },
                     "dependencies": {
                         "minipass": {
                             "version": "3.3.6",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "yallist": "^4.0.0"
                             }
@@ -20318,7 +21076,7 @@
                 },
                 "minizlib": {
                     "version": "2.1.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "minipass": "^3.0.0",
                         "yallist": "^4.0.0"
@@ -20326,7 +21084,7 @@
                     "dependencies": {
                         "minipass": {
                             "version": "3.3.6",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "yallist": "^4.0.0"
                             }
@@ -20335,23 +21093,23 @@
                 },
                 "mkdirp": {
                     "version": "1.0.4",
-                    "bundled": true
+                    "resolved": false
                 },
                 "ms": {
                     "version": "2.1.3",
-                    "bundled": true
+                    "resolved": false
                 },
                 "mute-stream": {
                     "version": "1.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "negotiator": {
                     "version": "0.6.3",
-                    "bundled": true
+                    "resolved": false
                 },
                 "node-gyp": {
                     "version": "10.1.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "env-paths": "^2.2.0",
                         "exponential-backoff": "^3.1.1",
@@ -20367,20 +21125,20 @@
                     "dependencies": {
                         "proc-log": {
                             "version": "3.0.0",
-                            "bundled": true
+                            "resolved": false
                         }
                     }
                 },
                 "nopt": {
                     "version": "7.2.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "abbrev": "^2.0.0"
                     }
                 },
                 "normalize-package-data": {
                     "version": "6.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "hosted-git-info": "^7.0.0",
                         "is-core-module": "^2.8.1",
@@ -20390,29 +21148,29 @@
                 },
                 "npm-audit-report": {
                     "version": "5.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "npm-bundled": {
                     "version": "3.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "npm-normalize-package-bin": "^3.0.0"
                     }
                 },
                 "npm-install-checks": {
                     "version": "6.3.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "semver": "^7.1.1"
                     }
                 },
                 "npm-normalize-package-bin": {
                     "version": "3.0.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "npm-package-arg": {
                     "version": "11.0.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "hosted-git-info": "^7.0.0",
                         "proc-log": "^4.0.0",
@@ -20422,14 +21180,14 @@
                 },
                 "npm-packlist": {
                     "version": "8.0.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "ignore-walk": "^6.0.4"
                     }
                 },
                 "npm-pick-manifest": {
                     "version": "9.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "npm-install-checks": "^6.0.0",
                         "npm-normalize-package-bin": "^3.0.0",
@@ -20439,7 +21197,7 @@
                 },
                 "npm-profile": {
                     "version": "10.0.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "npm-registry-fetch": "^17.0.1",
                         "proc-log": "^4.0.0"
@@ -20447,7 +21205,7 @@
                 },
                 "npm-registry-fetch": {
                     "version": "17.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/redact": "^2.0.0",
                         "make-fetch-happen": "^13.0.0",
@@ -20461,18 +21219,18 @@
                 },
                 "npm-user-validate": {
                     "version": "2.0.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "p-map": {
                     "version": "4.0.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "aggregate-error": "^3.0.0"
                     }
                 },
                 "pacote": {
                     "version": "18.0.6",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@npmcli/git": "^5.0.0",
                         "@npmcli/installed-package-contents": "^2.0.1",
@@ -20495,7 +21253,7 @@
                 },
                 "parse-conflict-json": {
                     "version": "3.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "json-parse-even-better-errors": "^3.0.0",
                         "just-diff": "^6.0.0",
@@ -20504,11 +21262,11 @@
                 },
                 "path-key": {
                     "version": "3.1.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "path-scurry": {
                     "version": "1.11.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "lru-cache": "^10.2.0",
                         "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
@@ -20516,7 +21274,7 @@
                 },
                 "postcss-selector-parser": {
                     "version": "6.1.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "cssesc": "^3.0.0",
                         "util-deprecate": "^1.0.2"
@@ -20524,27 +21282,27 @@
                 },
                 "proc-log": {
                     "version": "4.2.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "proggy": {
                     "version": "2.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "promise-all-reject-late": {
                     "version": "1.0.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "promise-call-limit": {
                     "version": "3.0.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "promise-inflight": {
                     "version": "1.0.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "promise-retry": {
                     "version": "2.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "err-code": "^2.0.2",
                         "retry": "^0.12.0"
@@ -20552,29 +21310,29 @@
                 },
                 "promzard": {
                     "version": "1.0.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "read": "^3.0.1"
                     }
                 },
                 "qrcode-terminal": {
                     "version": "0.12.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "read": {
                     "version": "3.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "mute-stream": "^1.0.0"
                     }
                 },
                 "read-cmd-shim": {
                     "version": "4.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "read-package-json-fast": {
                     "version": "3.0.2",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "json-parse-even-better-errors": "^3.0.0",
                         "npm-normalize-package-bin": "^3.0.0"
@@ -20582,35 +21340,35 @@
                 },
                 "retry": {
                     "version": "0.12.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "safer-buffer": {
                     "version": "2.1.2",
-                    "bundled": true,
+                    "resolved": false,
                     "optional": true
                 },
                 "semver": {
                     "version": "7.6.2",
-                    "bundled": true
+                    "resolved": false
                 },
                 "shebang-command": {
                     "version": "2.0.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "shebang-regex": "^3.0.0"
                     }
                 },
                 "shebang-regex": {
                     "version": "3.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "signal-exit": {
                     "version": "4.1.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "sigstore": {
                     "version": "2.3.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@sigstore/bundle": "^2.3.2",
                         "@sigstore/core": "^1.0.0",
@@ -20622,11 +21380,11 @@
                 },
                 "smart-buffer": {
                     "version": "4.2.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "socks": {
                     "version": "2.8.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "ip-address": "^9.0.5",
                         "smart-buffer": "^4.2.0"
@@ -20634,7 +21392,7 @@
                 },
                 "socks-proxy-agent": {
                     "version": "8.0.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "agent-base": "^7.1.1",
                         "debug": "^4.3.4",
@@ -20643,7 +21401,7 @@
                 },
                 "spdx-correct": {
                     "version": "3.2.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "spdx-expression-parse": "^3.0.0",
                         "spdx-license-ids": "^3.0.0"
@@ -20651,7 +21409,7 @@
                     "dependencies": {
                         "spdx-expression-parse": {
                             "version": "3.0.1",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "spdx-exceptions": "^2.1.0",
                                 "spdx-license-ids": "^3.0.0"
@@ -20661,11 +21419,11 @@
                 },
                 "spdx-exceptions": {
                     "version": "2.5.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "spdx-expression-parse": {
                     "version": "4.0.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "spdx-exceptions": "^2.1.0",
                         "spdx-license-ids": "^3.0.0"
@@ -20673,22 +21431,22 @@
                 },
                 "spdx-license-ids": {
                     "version": "3.0.18",
-                    "bundled": true
+                    "resolved": false
                 },
                 "sprintf-js": {
                     "version": "1.1.3",
-                    "bundled": true
+                    "resolved": false
                 },
                 "ssri": {
                     "version": "10.0.6",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "minipass": "^7.0.3"
                     }
                 },
                 "string-width": {
                     "version": "4.2.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "emoji-regex": "^8.0.0",
                         "is-fullwidth-code-point": "^3.0.0",
@@ -20697,7 +21455,7 @@
                 },
                 "string-width-cjs": {
                     "version": "npm:string-width-cjs@4.2.3",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "emoji-regex": "^8.0.0",
                         "is-fullwidth-code-point": "^3.0.0",
@@ -20706,25 +21464,25 @@
                 },
                 "strip-ansi": {
                     "version": "6.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "ansi-regex": "^5.0.1"
                     }
                 },
                 "strip-ansi-cjs": {
                     "version": "npm:strip-ansi-cjs@6.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "ansi-regex": "^5.0.1"
                     }
                 },
                 "supports-color": {
                     "version": "9.4.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "tar": {
                     "version": "6.2.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "chownr": "^2.0.0",
                         "fs-minipass": "^2.0.0",
@@ -20736,14 +21494,14 @@
                     "dependencies": {
                         "fs-minipass": {
                             "version": "2.1.0",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "minipass": "^3.0.0"
                             },
                             "dependencies": {
                                 "minipass": {
                                     "version": "3.3.6",
-                                    "bundled": true,
+                                    "resolved": false,
                                     "requires": {
                                         "yallist": "^4.0.0"
                                     }
@@ -20752,25 +21510,25 @@
                         },
                         "minipass": {
                             "version": "5.0.0",
-                            "bundled": true
+                            "resolved": false
                         }
                     }
                 },
                 "text-table": {
                     "version": "0.2.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "tiny-relative-date": {
                     "version": "1.3.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "treeverse": {
                     "version": "3.0.0",
-                    "bundled": true
+                    "resolved": false
                 },
                 "tuf-js": {
                     "version": "2.2.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "@tufjs/models": "2.0.1",
                         "debug": "^4.3.4",
@@ -20779,25 +21537,25 @@
                 },
                 "unique-filename": {
                     "version": "3.0.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "unique-slug": "^4.0.0"
                     }
                 },
                 "unique-slug": {
                     "version": "4.0.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "imurmurhash": "^0.1.4"
                     }
                 },
                 "util-deprecate": {
                     "version": "1.0.2",
-                    "bundled": true
+                    "resolved": false
                 },
                 "validate-npm-package-license": {
                     "version": "3.0.4",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "spdx-correct": "^3.0.0",
                         "spdx-expression-parse": "^3.0.0"
@@ -20805,7 +21563,7 @@
                     "dependencies": {
                         "spdx-expression-parse": {
                             "version": "3.0.1",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "spdx-exceptions": "^2.1.0",
                                 "spdx-license-ids": "^3.0.0"
@@ -20815,28 +21573,28 @@
                 },
                 "validate-npm-package-name": {
                     "version": "5.0.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "walk-up-path": {
                     "version": "3.0.1",
-                    "bundled": true
+                    "resolved": false
                 },
                 "which": {
                     "version": "4.0.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "isexe": "^3.1.1"
                     },
                     "dependencies": {
                         "isexe": {
                             "version": "3.1.1",
-                            "bundled": true
+                            "resolved": false
                         }
                     }
                 },
                 "wrap-ansi": {
                     "version": "8.1.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "ansi-styles": "^6.1.0",
                         "string-width": "^5.0.1",
@@ -20845,15 +21603,15 @@
                     "dependencies": {
                         "ansi-regex": {
                             "version": "6.0.1",
-                            "bundled": true
+                            "resolved": false
                         },
                         "emoji-regex": {
                             "version": "9.2.2",
-                            "bundled": true
+                            "resolved": false
                         },
                         "string-width": {
                             "version": "5.1.2",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "eastasianwidth": "^0.2.0",
                                 "emoji-regex": "^9.2.2",
@@ -20862,7 +21620,7 @@
                         },
                         "strip-ansi": {
                             "version": "7.1.0",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "ansi-regex": "^6.0.1"
                             }
@@ -20871,7 +21629,7 @@
                 },
                 "wrap-ansi-cjs": {
                     "version": "npm:wrap-ansi-cjs@7.0.0",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "ansi-styles": "^4.0.0",
                         "string-width": "^4.1.0",
@@ -20880,7 +21638,7 @@
                     "dependencies": {
                         "ansi-styles": {
                             "version": "4.3.0",
-                            "bundled": true,
+                            "resolved": false,
                             "requires": {
                                 "color-convert": "^2.0.1"
                             }
@@ -20889,7 +21647,7 @@
                 },
                 "write-file-atomic": {
                     "version": "5.0.1",
-                    "bundled": true,
+                    "resolved": false,
                     "requires": {
                         "imurmurhash": "^0.1.4",
                         "signal-exit": "^4.0.1"
@@ -20897,7 +21655,7 @@
                 },
                 "yallist": {
                     "version": "4.0.0",
-                    "bundled": true
+                    "resolved": false
                 }
             }
         },
@@ -22024,6 +22782,11 @@
             "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
             "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
         },
+        "punycode.js": {
+            "version": "2.3.1",
+            "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
+            "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="
+        },
         "q": {
             "version": "1.5.1",
             "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
@@ -22069,8 +22832,7 @@
         "queue-microtask": {
             "version": "1.2.3",
             "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
-            "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
-            "dev": true
+            "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
         },
         "queue-tick": {
             "version": "1.0.1",
@@ -22442,6 +23204,14 @@
                 }
             }
         },
+        "react-latex-next": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/react-latex-next/-/react-latex-next-3.0.0.tgz",
+            "integrity": "sha512-x70f1b1G7TronVigsRgKHKYYVUNfZk/3bciFyYX1lYLQH2y3/TXku3+5Vap8MDbJhtopePSYBsYWS6jhzIdz+g==",
+            "requires": {
+                "katex": "^0.16.0"
+            }
+        },
         "react-loading": {
             "version": "2.0.3",
             "resolved": "https://registry.npmjs.org/react-loading/-/react-loading-2.0.3.tgz",
@@ -22561,6 +23331,16 @@
                 "@react-types/shared": "^3.23.0"
             }
         },
+        "react-textarea-autosize": {
+            "version": "8.5.3",
+            "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz",
+            "integrity": "sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==",
+            "requires": {
+                "@babel/runtime": "^7.20.13",
+                "use-composed-ref": "^1.3.0",
+                "use-latest": "^1.2.1"
+            }
+        },
         "react-themeable": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/react-themeable/-/react-themeable-1.1.0.tgz",
@@ -22803,6 +23583,20 @@
                 "jsesc": "~0.5.0"
             }
         },
+        "rehype-katex": {
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.0.tgz",
+            "integrity": "sha512-h8FPkGE00r2XKU+/acgqwWUlyzve1IiOKwsEkg4pDL3k48PiE0Pt+/uLtVHDVkN1yA4iurZN6UES8ivHVEQV6Q==",
+            "requires": {
+                "@types/hast": "^3.0.0",
+                "@types/katex": "^0.16.0",
+                "hast-util-from-html-isomorphic": "^2.0.0",
+                "hast-util-to-text": "^4.0.0",
+                "katex": "^0.16.0",
+                "unist-util-visit-parents": "^6.0.0",
+                "vfile": "^6.0.0"
+            }
+        },
         "rehype-raw": {
             "version": "7.0.0",
             "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz",
@@ -22831,6 +23625,17 @@
                 "unified": "^11.0.0"
             }
         },
+        "remark-math": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz",
+            "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==",
+            "requires": {
+                "@types/mdast": "^4.0.0",
+                "mdast-util-math": "^3.0.0",
+                "micromark-extension-math": "^3.0.0",
+                "unified": "^11.0.0"
+            }
+        },
         "remark-parse": {
             "version": "11.0.0",
             "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
@@ -23092,6 +23897,12 @@
             "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
             "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
         },
+        "resolve-pkg-maps": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+            "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+            "dev": true
+        },
         "resolve-protobuf-schema": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
@@ -23135,8 +23946,7 @@
         "reusify": {
             "version": "1.0.4",
             "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
-            "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
-            "dev": true
+            "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="
         },
         "reveal.js": {
             "version": "5.1.0",
@@ -23183,7 +23993,6 @@
             "version": "1.2.0",
             "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
             "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
-            "dev": true,
             "requires": {
                 "queue-microtask": "^1.2.2"
             }
@@ -23210,6 +24019,14 @@
                 }
             }
         },
+        "sade": {
+            "version": "1.8.1",
+            "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
+            "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
+            "requires": {
+                "mri": "^1.1.0"
+            }
+        },
         "safe-array-concat": {
             "version": "1.1.2",
             "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz",
@@ -23689,6 +24506,11 @@
             "resolved": "https://registry.npmjs.org/skmeans/-/skmeans-0.9.7.tgz",
             "integrity": "sha512-hNj1/oZ7ygsfmPZ7ZfN5MUBRoGg1gtpnImuJBgLO0ljQ67DtJuiQaiYdS4lUA6s0KCwnPhGivtC/WRwIZLkHyg=="
         },
+        "slash": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+            "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="
+        },
         "slice-ansi": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
@@ -23880,6 +24702,23 @@
                 }
             }
         },
+        "speech-rule-engine": {
+            "version": "4.0.7",
+            "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.0.7.tgz",
+            "integrity": "sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g==",
+            "requires": {
+                "commander": "9.2.0",
+                "wicked-good-xpath": "1.3.0",
+                "xmldom-sre": "0.1.31"
+            },
+            "dependencies": {
+                "commander": {
+                    "version": "9.2.0",
+                    "resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz",
+                    "integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w=="
+                }
+            }
+        },
         "splaytree": {
             "version": "3.1.2",
             "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-3.1.2.tgz",
@@ -24620,6 +25459,16 @@
             "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz",
             "integrity": "sha512-6C5h3CE+0qjGp+YKYTs74xR0k/Nw/ePtl/Lp6CCf44hqBQ66qnH1sDFR5mV/Gc48EsrHLB53lCFSffQCkka3kg=="
         },
+        "ts-api-utils": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
+            "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ=="
+        },
+        "ts-dedent": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz",
+            "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="
+        },
         "ts-loader": {
             "version": "9.5.1",
             "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz",
@@ -25114,6 +25963,17 @@
             "resolved": "https://registry.npmjs.org/typescript-collections/-/typescript-collections-1.3.3.tgz",
             "integrity": "sha512-7sI4e/bZijOzyURng88oOFZCISQPTHozfE2sUu5AviFYk5QV7fYGb6YiDl+vKjF/pICA354JImBImL9XJWUvdQ=="
         },
+        "typescript-eslint": {
+            "version": "7.13.0",
+            "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.13.0.tgz",
+            "integrity": "sha512-upO0AXxyBwJ4BbiC6CRgAJKtGYha2zw4m1g7TIVPSonwYEuf7vCicw3syjS1OxdDMTz96sZIXl3Jx3vWJLLKFw==",
+            "dev": true,
+            "requires": {
+                "@typescript-eslint/eslint-plugin": "7.13.0",
+                "@typescript-eslint/parser": "7.13.0",
+                "@typescript-eslint/utils": "7.13.0"
+            }
+        },
         "typescript-language-server": {
             "version": "4.3.3",
             "resolved": "https://registry.npmjs.org/typescript-language-server/-/typescript-language-server-4.3.3.tgz",
@@ -25137,6 +25997,11 @@
             "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
             "integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg=="
         },
+        "uc.micro": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
+            "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="
+        },
         "uid-safe": {
             "version": "2.1.5",
             "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
@@ -25242,6 +26107,15 @@
                 "set-value": "^2.0.1"
             }
         },
+        "unist-util-find-after": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz",
+            "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==",
+            "requires": {
+                "@types/unist": "^3.0.0",
+                "unist-util-is": "^6.0.0"
+            }
+        },
         "unist-util-is": {
             "version": "6.0.0",
             "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
@@ -25417,11 +26291,24 @@
             "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz",
             "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw=="
         },
+        "use-composed-ref": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz",
+            "integrity": "sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ=="
+        },
         "use-isomorphic-layout-effect": {
             "version": "1.1.2",
             "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
             "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA=="
         },
+        "use-latest": {
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.1.tgz",
+            "integrity": "sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==",
+            "requires": {
+                "use-isomorphic-layout-effect": "^1.1.1"
+            }
+        },
         "use-sync-external-store": {
             "version": "1.2.2",
             "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
@@ -25467,6 +26354,24 @@
             "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
             "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="
         },
+        "uvu": {
+            "version": "0.5.6",
+            "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz",
+            "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==",
+            "requires": {
+                "dequal": "^2.0.0",
+                "diff": "^5.0.0",
+                "kleur": "^4.0.3",
+                "sade": "^1.7.3"
+            },
+            "dependencies": {
+                "diff": {
+                    "version": "5.2.0",
+                    "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
+                    "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="
+                }
+            }
+        },
         "v8-compile-cache": {
             "version": "2.4.0",
             "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz",
@@ -25484,6 +26389,16 @@
             "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz",
             "integrity": "sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA=="
         },
+        "validate.io-array": {
+            "version": "1.0.6",
+            "resolved": "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz",
+            "integrity": "sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg=="
+        },
+        "validate.io-function": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/validate.io-function/-/validate.io-function-1.0.2.tgz",
+            "integrity": "sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ=="
+        },
         "validator": {
             "version": "13.12.0",
             "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz",
@@ -25643,6 +26558,11 @@
             "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
             "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="
         },
+        "web-worker": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.3.0.tgz",
+            "integrity": "sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA=="
+        },
         "webidl-conversions": {
             "version": "3.0.1",
             "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@@ -26005,6 +26925,11 @@
                 "has-tostringtag": "^1.0.2"
             }
         },
+        "wicked-good-xpath": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz",
+            "integrity": "sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw=="
+        },
         "wide-align": {
             "version": "1.1.5",
             "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
@@ -26194,6 +27119,11 @@
             "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
             "dev": true
         },
+        "xmldom-sre": {
+            "version": "0.1.31",
+            "resolved": "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz",
+            "integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw=="
+        },
         "xmlhttprequest-ssl": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index f42c15c14..80e13875c 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -676,6 +676,7 @@ pie title Minerals in my tap water
             { title: "Color",     icon:"palette",        toolTip:"Sort by document color",                     btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "AI Sort",     icon:"robot",        toolTip:"Have Chat GPT sort your cards for you !",   btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"chat",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Pile",     icon:"layer-group",        toolTip:"View the cards as a pile in the free form view !",   btnType: ButtonType.ClickButton,  expertMode: false, toolType:"pile",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Chat Popup", icon:"lightbulb",     toolTip:"Toggle the chat popup's visibility!", width: 50,btnType: ButtonType.ToggleButton, expertMode: false, toolType:"toggle-chat",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
 
             { title: "Sort",    toolTip: "Manage sort order / lock status",   icon: "sort" ,        btnType: ButtonType.MultiToggleButton,                 toolType:"alignment", ignoreClick: true,
               subMenu: [
@@ -684,7 +685,7 @@ pie title Minerals in my tap water
             ]},
 
             { title: "Filter", icon:"Filter",     toolTip:"Filter cards by tags", width: 150, subMenu: this.cardGroupTools(), expertMode: false, toolType:CollectionViewType.Card,  funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
-
+            
 
 
             
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7ad5cd88c..6c95bf4db 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -550,7 +550,8 @@ export class MainView extends ObservableReactComponent<{}> {
                 fa.faSatellite,
                 fa.faStar,
                 fa.faCloud,
-                fa.faBolt
+                fa.faBolt,
+                fa.faLightbulb
             ]
         );
     }
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 60befebec..907a5a343 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -613,9 +613,11 @@ export class CollectionCardView extends CollectionSubView() {
         const isEmpty = this.childDocsWithoutLinks.length === 0;
         const isDesc = BoolCast(this.Document.cardSort_isDesc)
 
+        console.log(this.childDocsWithoutLinks.length + "length")
+
         if (isEmpty) {
             return (
-                <span className="no-card-span" style={{ width: `100%` }}>
+                <span className="no-card-span" style={{ width: ` ${this._props.PanelWidth()}px`, height: ` ${this._props.PanelHeight()}px` }}>
                     Sorry ! There are no cards in this group
                 </span>
             );
@@ -676,6 +678,10 @@ export class CollectionCardView extends CollectionSubView() {
     };
 
     render() {
+        const isEmpty = this.childDocsWithoutLinks.length === 0;
+        const transformValue = `scale(${1 / this.fitContentScale})`;
+        const heightValue = `${100 * this.fitContentScale}%`;
+    
         return (
             <div
                 onPointerMove={e => this.onPointerMove(e)}
@@ -688,9 +694,9 @@ export class CollectionCardView extends CollectionSubView() {
                 <div
                     className="card-wrapper"
                     style={{
-                        transform: ` scale(${1 / this.fitContentScale})`,
-                        height: `${100 * this.fitContentScale}%`,
-                        gridAutoRows: `${100/this.numRows}%`
+                        ...(!isEmpty && { transform: transformValue }),
+                        ...(!isEmpty && { height: heightValue }),
+                        gridAutoRows: `${100 / this.numRows}%`
                     }}
                     onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
                     {this.renderCards()}
@@ -698,4 +704,5 @@ export class CollectionCardView extends CollectionSubView() {
             </div>
         );
     }
+    
 }
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 2ecde0eee..6ea1751a9 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -42,6 +42,7 @@ import { CollectionViewType } from '../../documents/DocumentTypes';
 // import { InkTranscription } from '../InkTranscription';
 import { Docs } from '../../documents/Documents';
 import { CollectionSubView } from '../collections/CollectionSubView';
+import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
 
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function IsNoneSelected() {
@@ -145,7 +146,7 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
     }
 
     // prettier-ignore
-    const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'heart' | 'star' | 'cloud' | 'bolt' | 'chat' | '1' | '2' | '3' | '4' | 'up' | 'down' | 'pile', 
+    const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'heart' | 'star' | 'cloud' | 'bolt' | 'chat' | '1' | '2' | '3' | '4' | 'up' | 'down' | 'pile' | 'toggle-chat', 
     { 
         waitForRender?: boolean; 
         checkResult: (doc: Doc) => any; 
@@ -242,6 +243,14 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
                 doc.cardSort_isDesc = true;
             },
         }],
+        ['toggle-chat', {
+            checkResult: (doc: Doc) => GPTPopup.Instance.visible,
+            setDoc: (doc: Doc, dv: DocumentView) => {
+                GPTPopup.Instance.setVisible(!GPTPopup.Instance.visible);
+                GPTPopup.Instance.setMode(GPTPopupMode.SORT);
+
+            },
+        }],
         ['pile', {
             checkResult: (doc: Doc) => doc._type_collection == CollectionViewType.Freeform,
             setDoc: (doc: Doc, dv: DocumentView) => {
@@ -269,7 +278,8 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
 
                 // if (doc.type == DocumentType.COL ) {
                     newCol && dv.ComponentView?.addDocument?.(newCol);
-                    dv.ComponentView?._props.focus(newCol, {willPan: true , willZoomCentered: true, zoomScale: 200} )
+                    // dv.ComponentView?._props.focus(newCol, {willPan: true , willZoomCentered: true, zoomScale: 200} )
+                    DocumentView.showDocument(newCol, { willZoomCentered: true })
 
                 // }
                 // doc as CollectionFreeFormView
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index 042b1dea5..291f272ce 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -7,10 +7,13 @@ $highlightedText: #82e0ff;
 
 .summary-box {
     position: fixed;
-    bottom: 10px;
-    right: 10px;
+    top: 115px;
+    left: 75px;
     width: 250px;
+    height: 200px;
     min-height: 200px;
+    min-width: 180px;
+
     border-radius: 16px;
     padding: 16px;
     padding-bottom: 0;
@@ -21,6 +24,18 @@ $highlightedText: #82e0ff;
     background-color: #ffffff;
     box-shadow: 0 2px 5px #7474748d;
     color: $textgrey;
+    resize: both; /* Allows resizing */
+    overflow: auto;
+
+    .resize-handle {
+        width: 10px;
+        height: 10px;
+        background: #ccc;
+        position: absolute;
+        right: 0;
+        bottom: 0;
+        cursor: se-resize;
+      }
 
     .summary-heading {
         display: flex;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 5a5e80dc4..71d437c3d 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -354,6 +354,84 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         }
     };
 
+    cardMenu = () => (
+        <div className="btns-wrapper-gpt">
+            <Button
+                tooltip="Have ChatGPT sort your cards for you!"
+                text="Sort Cards!"
+                onClick={this.generateSort}
+                color={StrCast(Doc.UserDoc().userVariantColor)}
+                type={Type.TERT}
+                style={{
+                    width: '90%',
+                    textAlign: 'center',
+                    color: '#ffffff',
+                    fontSize: '16px',
+                }}
+            />
+            <Button
+                tooltip="Test your knowledge with ChatGPT!"
+                text="Quiz Cards!"
+                onClick={this.generateSort}
+                color={StrCast(Doc.UserDoc().userVariantColor)}
+                type={Type.TERT}
+                style={{
+                    width: '90%',
+                    textAlign: 'center',
+                    color: '#ffffff',
+                    fontSize: '16px',
+                }}
+            />
+        </div>
+
+    )
+
+    cardActual = (opt: string) => {
+        const isSort = opt === 'sort'
+
+        if (opt === 'sort'){
+            return (
+                <>
+                            <div className="btns-wrapper-gpt">
+                                <input
+                                    className="searchBox-input"
+                                    defaultValue=""
+                                    autoComplete="off"
+                                    onChange={this.sortPromptChanged}
+                                    onKeyDown={e => {
+                                        if (e.key === 'Enter') {
+                                            this.generateSort();
+                                        }
+                                        e.stopPropagation();
+                                    }}
+                                    type="text"
+                                    placeholder= {`How do you want to sort your cards ?` }
+                                    id="search-input"
+                                    style={{ width: '100%' }}
+                                />
+                            </div>
+                            <div className="btns-wrapper-gpt">
+                                <Button
+                                    tooltip="Have ChatGPT sort your cards for you!"
+                                    text="Sort!"
+                                    onClick={this.generateSort}
+                                    color={StrCast(Doc.UserDoc().userVariantColor)}
+                                    type={Type.TERT}
+                                    style={{
+                                        width: '90%',
+                                        textAlign: 'center',
+                                        color: '#ffffff',
+                                        fontSize: '16px',
+                                    }}
+                                />
+                            </div>
+                        </>
+            )
+
+        }
+
+}
+
     sortBox = () => (
     <>
         <div>
@@ -575,12 +653,22 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
 
     render() {
         return (
-
-            <div className="summary-box" style={{ display: this.visible ? 'flex' : 'none' }}>
-                {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.DATA ? this.dataAnalysisBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : this.mode === GPTPopupMode.SORT ? this.sortBox() : null}
+            <div
+                className="summary-box"
+                style={{ display: this.visible ? 'flex' : 'none' }}
+            >
+                {this.mode === GPTPopupMode.SUMMARY
+                    ? this.summaryBox()
+                    : this.mode === GPTPopupMode.DATA
+                    ? this.dataAnalysisBox()
+                    : this.mode === GPTPopupMode.IMAGE
+                    ? this.imageBox()
+                    : this.mode === GPTPopupMode.SORT
+                    ? this.sortBox()
+                    : null}
+                <div className="resize-handle" />
             </div>
-
-
         );
     }
+    
 }
-- 
cgit v1.2.3-70-g09d2


From abacdb311ee658b74f009106f4e7751fc216af4b Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Thu, 18 Jul 2024 11:24:12 -0400
Subject: boutta get silly

---
 src/client/util/CurrentUserUtils.ts                |  27 +-
 src/client/views/DocumentButtonBar.scss            |   4 +
 src/client/views/DocumentButtonBar.tsx             |  42 ++++
 src/client/views/FilterPanel.tsx                   |   2 +-
 src/client/views/MainView.tsx                      |   2 +-
 src/client/views/PropertiesView.tsx                |   2 +-
 .../views/collections/CollectionCardDeckView.tsx   |   2 +-
 src/client/views/global/globalScripts.ts           | 201 ++++++++-------
 src/client/views/pdf/GPTPopup/GPTPopup.scss        |  23 +-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         | 277 ++++++++++++---------
 10 files changed, 356 insertions(+), 226 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 80e13875c..380d16686 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -676,7 +676,7 @@ pie title Minerals in my tap water
             { title: "Color",     icon:"palette",        toolTip:"Sort by document color",                     btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "AI Sort",     icon:"robot",        toolTip:"Have Chat GPT sort your cards for you !",   btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"chat",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Pile",     icon:"layer-group",        toolTip:"View the cards as a pile in the free form view !",   btnType: ButtonType.ClickButton,  expertMode: false, toolType:"pile",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Chat Popup", icon:"lightbulb",     toolTip:"Toggle the chat popup's visibility!", width: 50,btnType: ButtonType.ToggleButton, expertMode: false, toolType:"toggle-chat",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
+            { title: "Chat Popup", icon:"lightbulb",     toolTip:"Toggle the chat popup's visibility!", width: 45 ,btnType: ButtonType.ToggleButton, expertMode: false, toolType:"toggle-chat",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
 
             { title: "Sort",    toolTip: "Manage sort order / lock status",   icon: "sort" ,        btnType: ButtonType.MultiToggleButton,                 toolType:"alignment", ignoreClick: true,
               subMenu: [
@@ -684,7 +684,8 @@ pie title Minerals in my tap water
                 { title: "Descending",   toolTip: "Sort the cards in descending order",btnType: ButtonType.ToggleButton, icon: "sort-down",toolType:"down",ignoreClick: true,  scripts: {onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
             ]},
 
-            { title: "Filter", icon:"Filter",     toolTip:"Filter cards by tags", width: 150, subMenu: this.cardGroupTools(), expertMode: false, toolType:CollectionViewType.Card,  funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
+            // { title: "Filter", icon:"Filter",     toolTip:"Filter cards by tags", width: 150, subMenu: this.cardGroupTools(), expertMode: false, toolType:CollectionViewType.Card,  funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
+            
             
 
 
@@ -711,12 +712,13 @@ pie title Minerals in my tap water
     //         { title: "3rd", icon:"3rd",     toolTip:"Filter ideas", width: 80, subMenu: this.cardGroupTools("cloud"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("idea", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
     //     ]
     // }
-    static cardGroupTools(): Button[] {
+    static tagGroupTools(): Button[] {
         return [
-            { title: "Star", icon: "star", toolTip:"Click to toggle the star group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Heart", icon: "heart", toolTip:"Click to toggle the heart group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Bolt", icon: "bolt", toolTip:"Click to toggle the bolt group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Cloud", icon: "cloud", toolTip:"Click to toggle the cloud group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Star", icon: "star", toolTip:"Click to toggle the star group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "Heart", icon: "heart", toolTip:"Click to toggle the heart group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "Bolt", icon: "bolt", toolTip:"Click to toggle the bolt group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "Cloud", icon: "cloud", toolTip:"Click to toggle the cloud group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "Options", icon: "gear", toolTip:"Click to customize your filter panel",  btnType: ButtonType.ClickButton,  expertMode: false, toolType:"opts", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
 
 
             // { title: "Group 1", icon, toolTip:"Click to toggle group 1's visibility",  btnType: ButtonType.ToggleButton, width: 40,  expertMode: false, toolType:"1", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
@@ -847,7 +849,16 @@ pie title Minerals in my tap water
             // { title: "Visibility", icon:"Visibility",     toolTip:"Filter stars", width: 80, subMenu: this.cardGroupTools("star"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("star", true)`, width: 100, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, 
             // // { title: "Custom",  icon:"cloud", toolTip:"Add Idea labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"idea",                  funcs: {hidden:`showFreeform ("idea", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             // { title: "Visibility", icon:"Visibility",     toolTip:"Filter ideas", width: 80, subMenu: this.cardGroupTools("cloud"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("idea", true)`,width: 100, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
-            
+            { title: "Filter", icon:"=",     toolTip:"Filter cards by tags", btnType: ButtonType.MultiToggleButton, width: 150, ignoreClick: true,toolType:DocumentType.COL, 
+            subMenu: this.tagGroupTools(), funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, 
+            // [
+            //     { title: "Star", icon: "star", toolTip:"Click to toggle the star group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            //     { title: "Heart", icon: "heart", toolTip:"Click to toggle the heart group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            //     { title: "Bolt", icon: "bolt", toolTip:"Click to toggle the bolt group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            //     { title: "Cloud", icon: "cloud", toolTip:"Click to toggle the cloud group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+
+            // ]
+        },
             { title: "Card",    icon: "Card",          toolTip: "Card View Tools",            subMenu: CurrentUserUtils.cardTools(),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
             // { title: "Create",   icon: "Create",         toolTip: "Assign card labels",   subMenu: CurrentUserUtils.labelTools(),  expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss
index 11614d627..374d6ecd3 100644
--- a/src/client/views/DocumentButtonBar.scss
+++ b/src/client/views/DocumentButtonBar.scss
@@ -153,6 +153,10 @@ $linkGap: 3px;
 
     &:hover {
         background-color: $black;
+        
+        .documentButtonBar-pinTypes {
+            display: flex;
+        }
     }
 }
 
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index a75c7098c..3d6436cb5 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -286,6 +286,42 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
     @computed
     get keywordButton() {
         const targetDoc = this.view0?.Document;
+
+        const metaBtn = (name: string, icon: IconProp) => {
+            const tooltip = `Show ${name}`;
+            return (
+                <Tooltip title={<div className="dash-tooltip">{tooltip}</div>}>
+                    <div className="documentButtonBar-pinIcon">
+                        <FontAwesomeIcon
+                            className="documentdecorations-icon"
+                            style={{ width: 20 }}
+                            key={icon.toString()}
+                            size="sm"
+                            icon={icon}
+                            // onPointerEnter={action(() => {
+                            //     this.subPin =
+                            //         (pinLayoutView ? 'Layout' : '') +
+                            //         (pinLayoutView && pinContentView ? ' &' : '') +
+                            //         (pinContentView ? ' Content View' : '') +
+                            //         (pinLayoutView && pinContentView ? '(shift+alt)' : pinLayoutView ? '(shift)' : pinContentView ? '(alt)' : '');
+                            // })}
+                            // onPointerLeave={action(() => {
+                            //     this.subPin = '';
+                            // })}
+                            onClick={e => {
+                                name === 'keywords' ? targetDoc && (targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels) : 'hi'
+
+                               
+                            }}
+                        />
+                    </div>
+                </Tooltip>
+            );
+        };
+
+
+
+
         return !targetDoc ? null : (
             <Tooltip title={<div className="dash-keyword-button">Open keyword menu</div>}>
                 <div
@@ -294,6 +330,12 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                     onClick={() => {
                         targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels;
                     }}>
+
+                    <div className="documentButtonBar-pinTypes">
+                        {/* {pinBtn(true, false, 'window-maximize')}
+                        {pinBtn(false, true, 'address-card')}
+                        {pinBtn(true, true, 'id-card')} */}
+                    </div>
                     <FontAwesomeIcon className="documentdecorations-icon" icon="tag" />
                 </div>
             </Tooltip>
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index c97edd7f0..5db627d23 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -222,7 +222,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
             <div className="filterBox-treeView">
                 <div className="filterBox-select">
                     <div style={{ width: '100%' }}>
-                        <FieldsDropdown Document={this.Document} selectFunc={this.facetClick} showPlaceholder placeholder="add a filter" addedFields={['acl_Guest', LinkedTo]} />
+                        <FieldsDropdown Document={this.Document} selectFunc={this.facetClick} showPlaceholder placeholder="add a filter" addedFields={['acl_Guest', LinkedTo, 'Star', 'Heart', 'Bolt', 'Cloud']} />
                     </div>
                     {/* THE FOLLOWING CODE SHOULD BE DEVELOPER FOR BOOLEAN EXPRESSION (AND / OR) */}
                     {/* <div className="filterBox-select-bool"> 
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 0d42f4099..f552540b8 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -90,7 +90,7 @@ export class MainView extends ObservableReactComponent<{}> {
 
     @observable private _windowWidth: number = 0;
     @observable private _windowHeight: number = 0;
-    @observable private _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row)
+    @observable  _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row)
     @observable private _dashUIHeight: number = 0; // height of entire main dashboard region including top menu buttons
     @observable private _panelContent: string = 'none';
     @observable private _sidebarContent: any = Doc.MyLeftSidebarPanel;
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 024db82a4..58bb67120 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -58,7 +58,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     private _widthUndo?: UndoManager.Batch;
 
     // eslint-disable-next-line no-use-before-define
-    public static Instance: PropertiesView | undefined;
+    public static Instance: PropertiesView; 
     constructor(props: any) {
         super(props);
         makeObservable(this);
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 907a5a343..410ddc355 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -516,7 +516,7 @@ export class CollectionCardView extends CollectionSubView() {
      */
     openChatPopup = async () => {
         GPTPopup.Instance.setVisible(true);
-        GPTPopup.Instance.setMode(GPTPopupMode.SORT);
+        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());
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 616633bc1..a48e3f9d9 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -1,10 +1,9 @@
 import { Colors } from 'browndash-components';
 import { action, runInAction } from 'mobx';
 import { aggregateBounds } from '../../../Utils';
-import { Doc, DocListCast, NumListCast, Opt } from '../../../fields/Doc';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { InkTool } from '../../../fields/InkField';
-import { List } from '../../../fields/List';
 import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
 import { Gestures } from '../../../pen-gestures/GestureTypes';
@@ -42,7 +41,9 @@ import { CollectionViewType } from '../../documents/DocumentTypes';
 import { Docs } from '../../documents/Documents';
 import { CollectionSubView } from '../collections/CollectionSubView';
 import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
-
+import { PropertiesView } from '../PropertiesView';
+import { MainView } from '../MainView';
+import { SnappingManager } from '../../util/SnappingManager';
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function IsNoneSelected() {
     return DocumentView.Selected().length <= 0;
@@ -145,7 +146,7 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
     }
 
     // prettier-ignore
-    const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'heart' | 'star' | 'cloud' | 'bolt' | 'chat' | '1' | '2' | '3' | '4' | 'up' | 'down' | 'pile' | 'toggle-chat', 
+    const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'chat' | 'up' | 'down' | 'pile' | 'toggle-chat', 
     { 
         waitForRender?: boolean; 
         checkResult: (doc: Doc) => any; 
@@ -192,34 +193,34 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
             checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "color",
             setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort === "color" ? doc.cardSort = '' : doc.cardSort = 'color',
         }],
-        ['heart', {
-            checkResult: (doc: Doc) => isAttrFiltered('heart'),
-            setDoc: (doc: Doc, dv: DocumentView) => {
-                isAttrFiltered('heart') ? Doc.setDocFilter(doc, 'heart', true, 'remove') : Doc.setDocFilter(doc, 'heart', true, 'match');
-
-            }
-        }],
-        ['star', {
-            checkResult: (doc: Doc) => isAttrFiltered('star'),
-
-            setDoc: (doc: Doc, dv: DocumentView) => {
-                isAttrFiltered('star') ? Doc.setDocFilter(doc, 'star', true, 'remove') : Doc.setDocFilter(doc, 'star', true, 'match');
-            }
-        }],
-        ['bolt', {
-            checkResult: (doc: Doc) => isAttrFiltered('bolt'),
-            setDoc: (doc: Doc, dv: DocumentView) => {
-                isAttrFiltered('bolt') ? Doc.setDocFilter(doc, 'bolt', true, 'remove') : Doc.setDocFilter(doc, 'bolt', true, 'match');
-
-            }
-        }],
-        ['cloud', {
-            checkResult: (doc: Doc) => isAttrFiltered('cloud'),
-            setDoc: (doc: Doc, dv: DocumentView) => {
-                isAttrFiltered('cloud') ? Doc.setDocFilter(doc, 'cloud', true, 'remove') : Doc.setDocFilter(doc, 'cloud', true, 'match');
-
-            }
-        }],
+        // ['heart', {
+        //     checkResult: (doc: Doc) => isAttrFiltered('heart'),
+        //     setDoc: (doc: Doc, dv: DocumentView) => {
+        //         isAttrFiltered('heart') ? Doc.setDocFilter(doc, 'heart', true, 'remove') : Doc.setDocFilter(doc, 'heart', true, 'match');
+
+        //     }
+        // }],
+        // ['star', {
+        //     checkResult: (doc: Doc) => isAttrFiltered('star'),
+
+        //     setDoc: (doc: Doc, dv: DocumentView) => {
+        //         isAttrFiltered('star') ? Doc.setDocFilter(doc, 'star', true, 'remove') : Doc.setDocFilter(doc, 'star', true, 'match');
+        //     }
+        // }],
+        // ['bolt', {
+        //     checkResult: (doc: Doc) => isAttrFiltered('bolt'),
+        //     setDoc: (doc: Doc, dv: DocumentView) => {
+        //         isAttrFiltered('bolt') ? Doc.setDocFilter(doc, 'bolt', true, 'remove') : Doc.setDocFilter(doc, 'bolt', true, 'match');
+
+        //     }
+        // }],
+        // ['cloud', {
+        //     checkResult: (doc: Doc) => isAttrFiltered('cloud'),
+        //     setDoc: (doc: Doc, dv: DocumentView) => {
+        //         isAttrFiltered('cloud') ? Doc.setDocFilter(doc, 'cloud', true, 'remove') : Doc.setDocFilter(doc, 'cloud', true, 'match');
+
+        //     }
+        // }],
         ['chat', {
             checkResult: (doc: Doc) => {
                 
@@ -275,20 +276,9 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
                     }
                 }
 
-                // if (doc.type == DocumentType.COL ) {
                     newCol && dv.ComponentView?.addDocument?.(newCol);
-                    // dv.ComponentView?._props.focus(newCol, {willPan: true , willZoomCentered: true, zoomScale: 200} )
                     DocumentView.showDocument(newCol, { willZoomCentered: true })
 
-                // }
-                // doc as CollectionFreeFormView
-                // doc as typeof CollectionSubView
-                
-                // doc._props
-
-                // CollectionFreeFormView.addDocumemnt
-        
-                // doc?.addDocument; //this._props.addDocument(newCol)
             },
         }],
     ]);
@@ -306,11 +296,40 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
     return undefined;
 });
 
-ScriptingGlobals.add(function cardHasLabel(label: string) {
+
+ScriptingGlobals.add(function handleTags(value?: any, checkResult?: boolean) {
     const selected = DocumentView.SelectedDocs().lastElement();
-    const labelNum = Number(label) - 1;
-    return labelNum < 4 || (selected && DocListCast(selected[Doc.LayoutFieldKey(selected)]).some(doc => doc[StrCast(selected.cardSort_customField)] == labelNum));
-}, '');
+
+    function isAttrFiltered(attr: string) {
+        return StrListCast(selected._childFilters).some(filter => filter.includes(attr));
+    }
+
+        if (checkResult) {
+            return value=== 'opts' ? PropertiesView.Instance.openFilters : isAttrFiltered(value)
+        }
+
+        if (value != 'opts'){
+            isAttrFiltered(value) ? Doc.setDocFilter(selected, value, true, 'remove') : Doc.setDocFilter(selected, value, true, 'match');
+        }
+        else {
+            SnappingManager.PropertiesWidth < 5 && SnappingManager.SetPropertiesWidth(0);
+            SnappingManager.SetPropertiesWidth(MainView.Instance.propertiesWidth() < 15 ? Math.min(MainView.Instance._dashUIWidth - 50, 250) : 0);
+
+            PropertiesView.Instance.CloseAll()
+            PropertiesView.Instance.openFilters = true
+        }
+
+    
+        return undefined;
+    }, '');
+
+
+
+// ScriptingGlobals.add(function cardHasLabel(label: string) {
+//     const selected = DocumentView.SelectedDocs().lastElement();
+//     const labelNum = Number(label) - 1;
+//     return labelNum < 4 || (selected && DocListCast(selected[Doc.LayoutFieldKey(selected)]).some(doc => doc[StrCast(selected.cardSort_customField)] == labelNum));
+// }, '');
 
 // ScriptingGlobals.add(function setCardSort(attr: "Time" | "Type"| "Color"| "ChatGPT"| "Custom 1"| "Custom 2"| "Custom 3", value?: any, checkResult?: boolean) {
 //     // const editorView = RichTextMenu.Instance?.TextView?.EditorView;
@@ -400,55 +419,53 @@ ScriptingGlobals.add(function cardHasLabel(label: string) {
 //     // return undefined;
 // });
 
-ScriptingGlobals.add(function setCardSort(value?: any, checkResult?: boolean) {
-    // const editorView = RichTextMenu.Instance?.TextView?.EditorView;
-    const selected = DocumentView.SelectedDocs().lastElement();
-
-    if (checkResult) {
-        // console.log(attr + "attricute")
-        // console.log(map.get(attr)?.checkResult(selected) + "check result")
-        console.log(StrCast(selected?.cardSort) + 'check');
-        const hi = StrCast(selected?.cardSort);
-        return StrCast(selected?.cardSortForDropDown) ?? 'Time';
-    }
-
-    function docFields(doc: Doc): void {
-        switch (value) {
-            case 'Custom 1':
-                doc.cardSort_customField = 'like';
-                break;
-            case 'Custom 2':
-                doc.cardSort_customField = 'star';
-                break;
-            case 'Custom 3':
-                doc.cardSort_customField = 'idea';
-                break;
-            case 'Chat GPT':
-                doc.cardSort = 'custom';
-                doc.cardSort_customField = 'chat';
-                break;
-            default:
-                break;
-        }
-
-        doc.cardSort_visibleSortGroups = new List<number>();
-    }
+// ScriptingGlobals.add(function setCardSort(value?: any, checkResult?: boolean) {
+//     // const editorView = RichTextMenu.Instance?.TextView?.EditorView;
+//     const selected = DocumentView.SelectedDocs().lastElement();
+//     if (checkResult) {
+//         // console.log(attr + "attricute")
+//         // console.log(map.get(attr)?.checkResult(selected) + "check result")
+//         console.log(StrCast(selected?.cardSort) + 'check');
+//         const hi = StrCast(selected?.cardSort);
+//         return StrCast(selected?.cardSortForDropDown) ?? 'Time';
+//     }
+//     function docFields(doc: Doc): void {
+//         switch (value) {
+//             case 'Custom 1':
+//                 doc.cardSort_customField = 'like';
+//                 break;
+//             case 'Custom 2':
+//                 doc.cardSort_customField = 'star';
+//                 break;
+//             case 'Custom 3':
+//                 doc.cardSort_customField = 'idea';
+//                 break;
+//             case 'Chat GPT':
+//                 doc.cardSort = 'custom';
+//                 doc.cardSort_customField = 'chat';
+//                 break;
+//             default:
+//                 break;
+//         }
+
+//         doc.cardSort_visibleSortGroups = new List<number>();
+//     }
 
-    // const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
-    DocumentView.Selected().map(dv => {
-        dv.Document.cardSortForDropDown = value;
+//     // const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
+//     DocumentView.Selected().map(dv => {
+//         dv.Document.cardSortForDropDown = value;
 
-        if (value != 'Chat GPT') {
-            dv.Document.cardSort = value.trim().split(/\s+/)[0].toLowerCase();
-        }
-        docFields(dv.Document);
-    });
+//         if (value != 'Chat GPT') {
+//             dv.Document.cardSort = value.trim().split(/\s+/)[0].toLowerCase();
+//         }
+//         docFields(dv.Document);
+//     });
 
-    return undefined;
+//     return undefined;
 
-    // map.get(attr)?.setDoc?.();
-    // return undefined;
-});
+//     // map.get(attr)?.setDoc?.();
+//     // return undefined;
+// });
 
 // ScriptingGlobals.add(function setCardSortAttr(attr: 'time' | 'docType' | 'color', value: any, checkResult?: boolean) {
 //     // const editorView = RichTextMenu.Instance?.TextView?.EditorView;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index 291f272ce..57973ef34 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -71,10 +71,12 @@ $highlightedText: #82e0ff;
     }
 
     .btns-wrapper-gpt {
-        height: 50px;
+        height: 100%;
         display: flex;
         justify-content: center;
         align-items: center;
+        flex-direction: column;
+
         transform: translateY(30px);
 
 
@@ -93,17 +95,20 @@ $highlightedText: #82e0ff;
         }
 
         
-    }
 
-    button {
-        font-size: 9px;
-        padding: 10px;
-        color: #ffffff;
-        width: 100%;
-        background-color: $button;
-        border-radius: 5px;
+        
     }
 
+    // button {
+    //     font-size: 9px;
+    //     padding: 10px;
+    //     color: #ffffff;
+    //     width: 100%;
+    //     background-color: $button;
+    //     border-radius: 5px;
+
+    // }
+
     .text-btn {
         &:hover {
             background-color: $button;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 71d437c3d..bbd5ea630 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -4,7 +4,7 @@ import { Button, IconButton, Type } from 'browndash-components';
 import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { CgClose } from 'react-icons/cg';
+import { CgClose, CgPathBack, CgArrowLeftO, CgCornerUpLeft } from 'react-icons/cg';
 import ReactLoading from 'react-loading';
 import { TypeAnimation } from 'react-type-animation';
 import { ClientUtils } from '../../../../ClientUtils';
@@ -33,6 +33,10 @@ export enum GPTPopupMode {
 }
 
 
+
+
+
+
 interface GPTPopupProps {}
 
 @observer
@@ -166,6 +170,15 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         this.chatSortPrompt = e.target.value;
     });
 
+
+    @observable quizAnswer: string = ""
+
+    quizAnswerChanged = action((e: React.ChangeEvent<HTMLInputElement>) => {
+        this.quizAnswer = e.target.value;
+    });
+
+
+
     @observable private regenerateCallback: (() => Promise<void>) | null = null;
 
     @action public setRegenerateCallback(callback: () => Promise<void>) {
@@ -359,81 +372,133 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
             <Button
                 tooltip="Have ChatGPT sort your cards for you!"
                 text="Sort Cards!"
-                onClick={this.generateSort}
+                onClick={() => this.setMode(GPTPopupMode.SORT)}
                 color={StrCast(Doc.UserDoc().userVariantColor)}
                 type={Type.TERT}
                 style={{
-                    width: '90%',
+                    width: '100%',
+                    height: '40%',
                     textAlign: 'center',
                     color: '#ffffff',
                     fontSize: '16px',
+                    marginBottom: '10px'
                 }}
             />
             <Button
                 tooltip="Test your knowledge with ChatGPT!"
                 text="Quiz Cards!"
-                onClick={this.generateSort}
+                onClick={() => this.setMode(GPTPopupMode.QUIZ)}
                 color={StrCast(Doc.UserDoc().userVariantColor)}
                 type={Type.TERT}
                 style={{
-                    width: '90%',
+                    width: '100%',
                     textAlign: 'center',
                     color: '#ffffff',
                     fontSize: '16px',
+                    height: '40%',
+
                 }}
             />
         </div>
-
     )
+    
+    cardActual = (opt: GPTPopupMode) => {
+        if (opt === GPTPopupMode.SORT) {
+            return (
+                !this.sortDone ? (
+                    <>
+                        <div className="btns-wrapper-gpt">
+                            <input
+                                className="searchBox-input"
+                                defaultValue=""
+                                autoComplete="off"
+                                onChange={this.sortPromptChanged}
+                                onKeyDown={e => {
+                                    if (e.key === 'Enter') {
+                                        this.generateSort();
+                                    }
+                                    e.stopPropagation();
+                                }}
+                                type="text"
+                                placeholder="How do you want to sort your cards?"
+                                id="search-input"
+                                style={{ width: '100%' }}
+                            />
+                        {/* </div>
+                        <div className="btns-wrapper-gpt"> */}
+                            <Button
+                                tooltip="Have ChatGPT sort your cards for you!"
+                                text="Sort!"
+                                onClick={this.generateSort}
+                                color={StrCast(Doc.UserDoc().userVariantColor)}
+                                type={Type.TERT}
+                                style={{
+                                    width: '100%',
+                                    textAlign: 'center',
+                                    color: '#ffffff',
+                                    fontSize: '16px',
+                                    height: '40%',
 
-    cardActual = (opt: string) => {
-        const isSort = opt === 'sort'
-
-        if (opt === 'sort'){
+                                }}
+                            />
+                        </div>
+                    </>
+                ) : (
+                    <div>
+                        <div className="content-wrapper">
+                            <p>{this.text === 'Something went wrong :(' ? 'Something went wrong :(' : `${this.sortRespText}`}</p>
+                            <IconButton
+                                tooltip="Generate Again"
+                                onClick={() => this.setSortDone(false)}
+                                icon={<FontAwesomeIcon icon="redo-alt" size="lg" />}
+                                color={StrCast(Doc.UserDoc().userVariantColor)}
+                            />
+                        </div>
+                    </div>
+                )
+            );
+        } else if (opt === GPTPopupMode.QUIZ) {
             return (
                 <>
-                            <div className="btns-wrapper-gpt">
-                                <input
-                                    className="searchBox-input"
-                                    defaultValue=""
-                                    autoComplete="off"
-                                    onChange={this.sortPromptChanged}
-                                    onKeyDown={e => {
-                                        if (e.key === 'Enter') {
-                                            this.generateSort();
-                                        }
-                                        e.stopPropagation();
-                                    }}
-                                    type="text"
-                                    placeholder= {`How do you want to sort your cards ?` }
-                                    id="search-input"
-                                    style={{ width: '100%' }}
-                                />
-                            </div>
-                            <div className="btns-wrapper-gpt">
-                                <Button
-                                    tooltip="Have ChatGPT sort your cards for you!"
-                                    text="Sort!"
-                                    onClick={this.generateSort}
-                                    color={StrCast(Doc.UserDoc().userVariantColor)}
-                                    type={Type.TERT}
-                                    style={{
-                                        width: '90%',
-                                        textAlign: 'center',
-                                        color: '#ffffff',
-                                        fontSize: '16px',
-                                    }}
-                                />
-                            </div>
-                        </>
-            )
-
+                    <div className="btns-wrapper-gpt">
+                        <input
+                            className="searchBox-input"
+                            defaultValue=""
+                            autoComplete="off"
+                            onChange={this.quizAnswerChanged}
+                            onKeyDown={e => {
+                                if (e.key === 'Enter') {
+                                    this.generateSort();
+                                }
+                                e.stopPropagation();
+                            }}
+                            type="text"
+                            placeholder="What is the selected card?"
+                            id="search-input"
+                            style={{ width: '100%' }}
+                        />
+                    </div>
+                    <div className="btns-wrapper-gpt">
+                        <Button
+                            tooltip="See how close you got to the right answer!"
+                            text="Check!"
+                            onClick={this.generateSort}
+                            color={StrCast(Doc.UserDoc().userVariantColor)}
+                            type={Type.TERT}
+                            style={{
+                                width: '90%',
+                                textAlign: 'center',
+                                color: '#ffffff',
+                                fontSize: '16px',
+                            }}
+                        />
+                    </div>
+                </>
+            );
         }
-
-}
-
+    };
+    
     sortBox = () => (
-    <>
         <div>
             {this.heading('SORTING')}
             <>
@@ -445,62 +510,13 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
                         </div>
                     </div>
                 ) : (
-                    !this.sortDone && (
-                        <>
-                            <div className="btns-wrapper-gpt">
-                                <input
-                                    className="searchBox-input"
-                                    defaultValue=""
-                                    autoComplete="off"
-                                    onChange={this.sortPromptChanged}
-                                    onKeyDown={e => {
-                                        if (e.key === 'Enter') {
-                                            this.generateSort();
-                                        }
-                                        e.stopPropagation();
-                                    }}
-                                    type="text"
-                                    placeholder="How do you want to sort your cards ?"
-                                    id="search-input"
-                                    style={{ width: '100%' }}
-                                />
-                            </div>
-                            <div className="btns-wrapper-gpt">
-                                <Button
-                                    tooltip="Have ChatGPT sort your cards for you!"
-                                    text="Sort!"
-                                    onClick={this.generateSort}
-                                    color={StrCast(Doc.UserDoc().userVariantColor)}
-                                    type={Type.TERT}
-                                    style={{
-                                        width: '90%',
-                                        textAlign: 'center',
-                                        color: '#ffffff',
-                                        fontSize: '16px',
-                                    }}
-                                />
-                            </div>
-                        </>
-                    )
-                )}
-
-                {this.sortDone && (
-                    <div>
-                        <div className="content-wrapper">
-                            <p>{this.text === 'Something went wrong :(' ? 'Something went wrong :(' : `${this.sortRespText}`}</p>
-                            <IconButton
-                                tooltip="Generate Again"
-                                onClick={() => this.setSortDone(false)}
-                                icon={<FontAwesomeIcon icon="redo-alt" size="lg" />}
-                                color={StrCast(Doc.UserDoc().userVariantColor)}
-                            />
-                        </div>
-                    </div>
+                    (this.mode === GPTPopupMode.CARD ? this.cardMenu() : this.cardActual(this.mode)) // Call the functions to render JSX
                 )}
             </>
         </div>
-    </>
-);
+    );
+    
+    
 
     imageBox = () => (
         <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
@@ -647,25 +663,60 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     heading = (headingText: string) => (
         <div className="summary-heading">
             <label className="summary-text">{headingText}</label>
-            {this.loading ? <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} /> : <IconButton color={StrCast(SettingsManager.userVariantColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={() => this.setVisible(false)} />}
+            {this.loading ? (
+                <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} />
+            ) : (
+                <>
+                {(this.mode === GPTPopupMode.SORT || this.mode === GPTPopupMode.QUIZ) && (
+                        <IconButton
+                            color={StrCast(SettingsManager.userVariantColor)}
+                            tooltip="back"
+                            icon={<CgCornerUpLeft size="16px" />}
+                            onClick={() => this.mode = GPTPopupMode.CARD}
+                            style = {{right: '-55px'}}
+                        />
+                    )}
+                    <IconButton
+                        color={StrCast(SettingsManager.userVariantColor)}
+                        tooltip="close"
+                        icon={<CgClose size="16px" />}
+                        onClick={() => this.setVisible(false)}
+                    />
+                    
+                </>
+            )}
         </div>
     );
+        
 
     render() {
+        let content;
+        
+        switch (this.mode) {
+            case GPTPopupMode.SUMMARY:
+                content = this.summaryBox();
+                break;
+            case GPTPopupMode.DATA:
+                content = this.dataAnalysisBox();
+                break;
+            case GPTPopupMode.IMAGE:
+                content = this.imageBox();
+                break;
+            case GPTPopupMode.SORT:
+            case GPTPopupMode.CARD:
+            case GPTPopupMode.QUIZ:
+                content = this.sortBox();
+                break;
+            default:
+                content = null;
+        }
+    
         return (
             <div
                 className="summary-box"
                 style={{ display: this.visible ? 'flex' : 'none' }}
             >
-                {this.mode === GPTPopupMode.SUMMARY
-                    ? this.summaryBox()
-                    : this.mode === GPTPopupMode.DATA
-                    ? this.dataAnalysisBox()
-                    : this.mode === GPTPopupMode.IMAGE
-                    ? this.imageBox()
-                    : this.mode === GPTPopupMode.SORT
-                    ? this.sortBox()
-                    : null}
+                {content}
                 <div className="resize-handle" />
             </div>
         );
-- 
cgit v1.2.3-70-g09d2


From de253f5acca34f20017895a2d8469b5ebd6032bf Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Tue, 30 Jul 2024 14:31:00 -0400
Subject: fight w css

---
 src/client/apis/gpt/GPT.ts                         |   9 ++
 src/client/documents/Documents.ts                  |   2 +
 src/client/util/CurrentUserUtils.ts                |   4 +-
 src/client/views/DocumentButtonBar.tsx             |   6 +-
 src/client/views/DocumentDecorations.tsx           |   2 +-
 src/client/views/FilterPanel.tsx                   |  95 ++++++++++++
 src/client/views/StyleProvider.tsx                 |   7 +
 .../views/collections/CollectionCardDeckView.scss  |  50 +++---
 .../views/collections/CollectionCardDeckView.tsx   |   1 +
 src/client/views/nodes/IconTagBox.scss             |  30 ++++
 src/client/views/nodes/IconTagBox.tsx              | 168 +++++++++++++++++++++
 src/client/views/pdf/GPTPopup/GPTPopup.scss        |   3 +-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         | 164 ++++++++++++++------
 13 files changed, 460 insertions(+), 81 deletions(-)
 create mode 100644 src/client/views/nodes/IconTagBox.scss
 create mode 100644 src/client/views/nodes/IconTagBox.tsx

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

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 40af70d99..d95e564c7 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -12,6 +12,7 @@ enum GPTCallType {
     DESCRIBE = 'describe',
     MERMAID = 'mermaid',
     DATA = 'data',
+    RUBRIC = 'rubric'
 }
 
 type GPTCallOpts = {
@@ -26,6 +27,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
     summary: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: 'Summarize the text given in simpler terms.' },
     edit: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: 'Reword the text.' },
     flashcard: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Make flashcards out of this text with each question and answer labeled. Do not label each flashcard and do not include asterisks: ' },
+
     completion: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: "You are a helpful assistant. Answer the user's prompt." },
     mermaid: {
         model: 'gpt-4-turbo',
@@ -54,6 +56,13 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
         temp: 0,
         prompt: 'List unique differences between the content of the UserAnswer and Rubric. Before each difference, label it and provide any additional information the UserAnswer missed and explain it in second person without separating it into UserAnswer and Rubric content and additional information. If there are no differences, say correct',
     },
+
+    rubric: {
+        model: 'gpt-4-turbo',
+        maxTokens: 1024,
+        temp: 0,
+        prompt: "Provide a definition for the vollowing term. It will be used as a rubric to evaluate the user's understanding of the topic",
+    },
 };
 
 let lastCall = '';
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index e8e9b4fb6..612629f89 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -493,6 +493,8 @@ export class DocumentOptions {
     // cardSort_visibleSortGroups?: List<number>; // which sorting values are being filtered (shown)
     cardSort_isDesc?: BOOLt = new BoolInfo('whether the cards are sorted ascending or descending');
     // test?: STRt = new StrInfo('testing for filtering')
+    keywords?: MAPt = new MapInfo('keywords', true);
+
 }
 
 export const DocOptions = new DocumentOptions();
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 380d16686..67f8dafc4 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -39,7 +39,7 @@ import { ColorScheme } from "./SettingsManager";
 import { SnappingManager } from "./SnappingManager";
 import { UndoManager } from "./UndoManager";
 
-interface Button {
+export interface Button {
     // DocumentOptions fields a button can set
     title?: string;
     toolTip?: string;
@@ -714,7 +714,7 @@ pie title Minerals in my tap water
     // }
     static tagGroupTools(): Button[] {
         return [
-            { title: "Star", icon: "star", toolTip:"Click to toggle the star group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "Star", icon: StrCast(Doc.UserDoc().myFilterHotKeyIcons) ?? "Star", toolTip:"Click to toggle the star group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
             { title: "Heart", icon: "heart", toolTip:"Click to toggle the heart group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
             { title: "Bolt", icon: "bolt", toolTip:"Click to toggle the bolt group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
             { title: "Cloud", icon: "cloud", toolTip:"Click to toggle the cloud group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 3d6436cb5..c8df66535 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -309,7 +309,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                             //     this.subPin = '';
                             // })}
                             onClick={e => {
-                                name === 'keywords' ? targetDoc && (targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels) : 'hi'
+                                name === 'tags' ? targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags) : 'hi'
 
                                
                             }}
@@ -332,7 +332,9 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                     }}>
 
                     <div className="documentButtonBar-pinTypes">
-                        {/* {pinBtn(true, false, 'window-maximize')}
+                        {
+                            metaBtn('tags', "tag")
+                        /* {pinBtn(true, false, 'window-maximize')}
                         {pinBtn(false, true, 'address-card')}
                         {pinBtn(true, true, 'id-card')} */}
                     </div>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index dc40562e8..b5d819b97 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -832,7 +832,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                 <div
                                     className="link-button-container"
                                     style={{
-                                        top: `${doc[DocData].showLabels ? 4 + (doc._keywordHeight as number) : 4}px`,
+                                        top: `${(doc[DocData].showLabels || doc[DocData].showIconTags) ? 4 + (doc._keywordHeight as number) : 4}px`,
                                         transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
                                     }}>
                                     <DocumentButtonBar views={() => DocumentView.Selected()} />
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index 5db627d23..332a89a96 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -19,6 +19,16 @@ import './FilterPanel.scss';
 import { DocumentView } from './nodes/DocumentView';
 import { Handle, Tick, TooltipRail, Track } from './nodes/SliderBox-components';
 import { ObservableReactComponent } from './ObservableReactComponent';
+import { Button } from '../util/CurrentUserUtils';
+import { ButtonType } from './nodes/FontIconBox/FontIconBox';
+import { DocCast } from '../../fields/Types';
+// import { Docs } from '../../documents/Documents';
+import { Docs } from '../documents/Documents';
+import { CurrentUserUtils } from '../util/CurrentUserUtils';
+import { DocumentOptions } from '../documents/Documents';
+import { DocUtils } from '../documents/DocUtils';
+import { dropActionType } from '../util/DropActionTypes';
+
 
 interface filterProps {
     Document: Doc;
@@ -217,6 +227,83 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
         return nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2));
     };
 
+    addHotkey() {
+
+        const buttons = DocCast(Doc.UserDoc().myContextMenuBtns);
+        const filter = buttons['Filter']
+        const filter2 = DocCast(filter)
+        const but2 = Doc.UserDoc().myContextMenuBtns
+
+        // const newCol = Docs.Create.CarouselDocument(DocListCast(doc[Doc.LayoutFieldKey(doc)]), {
+        //     _width: 250,
+        //     _height: 200,
+        //     _layout_fitWidth: false,
+        //     _layout_autoHeight: true,
+        // });
+
+        // CurrentUserUtils.setupContextMenuButtons()
+
+        // const hm = Docs.Create.FontIconDocument()
+
+        // DocumentView.getDocumentView(filter2)?.ComponentView?.addDocument?.(hm)
+
+        // console.log(DocumentView.getDocumentView(filter2) + "hiiiii")
+
+        // DocumentView.getDocumentView(DocCast(buttons['Card']))?.ComponentView?.addDocument?.(hm)
+
+        const hi = CurrentUserUtils.contextMenuTools()
+
+        const newKey: Button = { title: "Cloud", icon: "cloud", toolTip:"Click to toggle the cloud group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}
+
+
+        const heyy = [...hi, newKey]
+
+        Doc.UserDoc().myFilterHotKeyIcons = "palette"
+        Doc.UserDoc().myFilterHotKeyTitles = "hi"
+
+        CurrentUserUtils.setupContextMenuButtons(Doc.UserDoc());
+
+
+
+        Doc.UserDoc().hi = Docs.Create.FontIconDocument(
+           { title: '',
+                icon: 'map-pin',
+            
+                backgroundColor: '#ACCEF7',
+                layout_hideAllLinks: true,
+                _width: 15,
+                _height: 15,
+                _xPadding: 0,
+   }
+        )
+
+
+        // buttons['Filter']
+
+
+
+
+        const reqdCtxtOpts:DocumentOptions = { title: "hi", undoIgnoreFields:new List<string>(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: dropActionType.embed, childDontRegisterViews: true,  linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: false, _height: 35 };
+        const ctxtMenuBtns =  CurrentUserUtils.setupContextMenuBtn(newKey, buttons);
+        // DocUtils.AssignOpts(buttons, reqdCtxtOpts, ctxtMenuBtns);
+
+
+        DocUtils.AssignOpts(DocCast(buttons['Filter']), reqdCtxtOpts, [ctxtMenuBtns]);
+
+
+        // newCol && dv.ComponentView?.addDocument?.(newCol);
+
+        // console.log(but2 + "omgg")
+
+
+
+        // doc.cardSort_activeIcons = new List<string>(list.includes(icon) ? list.filter(d => d !== icon) : [...list, icon]);
+
+
+
+        // Doc.UserDoc().myContextMenuBtns = new List<Doc>([...buttons, newKey as Doc])
+    }
+
     render() {
         return (
             <div className="filterBox-treeView">
@@ -283,6 +370,14 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
                         )
                     )}
                 </div>
+                <div>
+                    <div className="filterBox-select">
+                        <div style={{ width: '100%' }}>
+                            <FieldsDropdown Document={this.Document} selectFunc={this.addHotkey} showPlaceholder placeholder="add a hotkey" addedFields={['acl_Guest', LinkedTo, 'Star', 'Heart', 'Bolt', 'Cloud']} />
+                        </div>
+                    </div>
+
+                </div>
             </div>
         );
     }
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 40d0358d2..55e6217ff 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -30,6 +30,7 @@ import { DocumentView, DocumentViewProps } from './nodes/DocumentView';
 import { FieldViewProps } from './nodes/FieldView';
 import { StyleProp } from './StyleProp';
 import './StyleProvider.scss';
+import { IconTagBox } from './nodes/IconTagBox';
 
 function toggleLockedPosition(doc: Doc) {
     UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground');
@@ -377,6 +378,11 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                     return (<KeywordBox isEditing={false} doc={doc}></KeywordBox>)
                 }
             }
+
+            const iconTags = () => {
+                if (doc && doc![DocData].showIconTags)
+                    {return (<IconTagBox doc= {doc}></IconTagBox>)}
+            }
             return (
                 <>
                     {paint()}
@@ -384,6 +390,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                     {filter()}
                     {audio()}
                     {keywords()}
+                    {iconTags()}
                 </>
             );
         }
diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
index eb1b456f5..5869f89e1 100644
--- a/src/client/views/collections/CollectionCardDeckView.scss
+++ b/src/client/views/collections/CollectionCardDeckView.scss
@@ -22,32 +22,32 @@
     transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955);
 }
 
-.card-button-container {
-    display: flex;
-    padding: 3px;
-    // width: 300px;
-    // height:100px;
-    pointer-events: none; /* This ensures the container does not capture hover events */
-
-    background-color: rgb(218, 218, 218); /* Background color of the container */
-    border-radius: 50px; /* Rounds the corners of the container */
-    transform: translateY(25px);
-    // box-shadow: 0 4px 8px rgba(0,0,0,0.1); /* Optional: Adds shadow for depth */
-    align-items: center; /* Centers buttons vertically */
-    justify-content: start; /* Centers buttons horizontally */
-
-    button {
-        pointer-events: auto; /* Re-enable pointer events for the buttons */
+// .card-button-container {
+//     display: flex;
+//     padding: 3px;
+//     // width: 300px;
+//     // height:100px;
+//     pointer-events: none; /* This ensures the container does not capture hover events */
+
+//     background-color: rgb(218, 218, 218); /* Background color of the container */
+//     border-radius: 50px; /* Rounds the corners of the container */
+//     transform: translateY(25px);
+//     // box-shadow: 0 4px 8px rgba(0,0,0,0.1); /* Optional: Adds shadow for depth */
+//     align-items: center; /* Centers buttons vertically */
+//     justify-content: start; /* Centers buttons horizontally */
+
+//     button {
+//         pointer-events: auto; /* Re-enable pointer events for the buttons */
     
-        width: 70px;
-        height: 70px;
-        border-radius: 50%;
-        background-color: $dark-gray;
-        // border-color: $medium-blue;
-        margin: 5px; // transform: translateY(-50px);
-        background-color: transparent;
-    }
-}
+//         width: 70px;
+//         height: 70px;
+//         border-radius: 50%;
+//         background-color: $dark-gray;
+//         // border-color: $medium-blue;
+//         margin: 5px; // transform: translateY(-50px);
+//         background-color: transparent;
+//     }
+// }
 
 .no-card-span{
     position: relative;
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 410ddc355..736cc2354 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -446,6 +446,7 @@ export class CollectionCardView extends CollectionSubView() {
         };
         const docTextPromises = this.childDocsWithoutLinks.map(async doc => {
             const docText = (await docToText(doc)) ?? '';
+            doc['gptInputText'] = docText
             this._textToDoc.set(docText.replace(/\n/g, ' ').trim(), doc);
             return `======${docText.replace(/\n/g, ' ').trim()}======`;
         });
diff --git a/src/client/views/nodes/IconTagBox.scss b/src/client/views/nodes/IconTagBox.scss
new file mode 100644
index 000000000..8c0f92c90
--- /dev/null
+++ b/src/client/views/nodes/IconTagBox.scss
@@ -0,0 +1,30 @@
+@import '../global/globalCssVariables.module.scss';
+
+.card-button-container {
+    display: flex;
+    padding: 3px;
+    position: absolute;
+    // width: 300px;
+    // height:100px;
+    pointer-events: none; /* This ensures the container does not capture hover events */
+
+    background-color: rgb(218, 218, 218); /* Background color of the container */
+    border-radius: 50px; /* Rounds the corners of the container */
+    transform: translateY(25px);
+    // box-shadow: 0 4px 8px rgba(0,0,0,0.1); /* Optional: Adds shadow for depth */
+    align-items: center; /* Centers buttons vertically */
+    justify-content: start; /* Centers buttons horizontally */
+
+    button {
+        pointer-events: auto; /* Re-enable pointer events for the buttons */
+        transform: translateY(-7.5px);
+
+        width: 30px;
+        height: 30px;
+        border-radius: 50%;
+        background-color: $dark-gray;
+        // border-color: $medium-blue;
+        margin: 5px; // transform: translateY(-50px);
+        background-color: transparent;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx
new file mode 100644
index 000000000..9a2273c3a
--- /dev/null
+++ b/src/client/views/nodes/IconTagBox.tsx
@@ -0,0 +1,168 @@
+import React from "react";
+import { observer } from "mobx-react";
+import { computed } from "mobx";
+
+import { ObservableReactComponent } from "../ObservableReactComponent";
+import { NumCast } from "../../../fields/Types";
+import { makeObservable } from "mobx";
+import { Doc } from "../../../fields/Doc";
+import { Reaction } from "mobx";
+import { reaction } from "mobx";
+import { numberRange } from "../../../Utils";
+import { Tooltip } from "@mui/material";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { undoable } from "../../util/UndoManager";
+import { BoolCast } from "../../../fields/Types";
+import { DocCast } from "../../../fields/Types";
+import './IconTagBox.scss';
+import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../fields/DocSymbols';
+
+
+export interface IconTagProps {
+    doc: Doc;
+
+
+}
+
+@observer
+export class IconTagBox extends ObservableReactComponent<IconTagProps> {
+    private ref: React.RefObject<HTMLDivElement>;
+    private height: number = 0;
+
+
+    @computed
+    get currentScale() {
+        return NumCast((this._props.doc.embedContainer as Doc)?._freeform_scale, 1);
+    }
+
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+        this.ref = React.createRef();
+    }
+
+    componentDidMount(): void {
+        this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
+        this._props.doc._keywordHeight = this.height;
+
+        reaction(
+            () => this.currentScale,
+            () => {
+                if (this.currentScale < 1) {
+                    this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
+                    this._props.doc._keywordHeight = this.height;
+                }
+            }
+        );
+    }
+
+    componentDidUpdate(prevProps: Readonly<IconTagProps>): void {
+        this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
+        this._props.doc._keywordHeight = this.height;
+    }
+
+    /**
+     * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups
+     * @param doc
+     * @param cardSort
+     * @returns
+     */
+    renderButtons = (doc: Doc): JSX.Element | null => {
+        // if (cardSort !== cardSortings.Custom) return null;
+
+        const amButtons = 4
+
+        // const amButtons = Math.max(
+        //     4,
+        //     this.childDocs?.reduce((set, d) => {
+        //         if (this.cardSort_customField) {
+        //             set.add(NumCast(d[this.cardSort_customField]));
+        //         }
+        //         return set;
+        //     }, new Set<number>()).size ?? 0
+        // );
+
+        // const activeButtonIndex = CollectionCardView.getButtonGroup(this.cardSort_customField, doc);
+
+        const totalWidth = amButtons * 35 + amButtons * 2 * 5 + 6;
+
+        const iconMap: { [key: number]: any } = {
+            0: 'star',
+            1: 'heart',
+            2: 'cloud',
+            3: 'bolt'
+        };
+
+        return (
+            <div
+            className="card-button-container"
+            style={{
+                transformOrigin: 'top left',
+                transform: `scale(${1 / this.currentScale}) translateY(${doc[DocData].showLabels ? doc._keywordHeight as number : 0}px)`,
+                width: `${totalWidth}px`,
+                fontSize: '50px'
+            }}
+            >           
+                {numberRange(amButtons).map(i => (
+                    <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from the {iconMap[i]} group</div>}>
+                        <button key = {i} type="button" onClick={() => this.toggleButton(doc, iconMap[i] )}>
+                            {this.getButtonIcon(doc, iconMap[i])}
+                        </button>
+                    </Tooltip>
+                ))}
+            </div>
+        );
+    };
+
+      /**
+     * Toggles the buttons between on and off when creating custom sort groupings/changing those created by gpt
+     * @param childPairIndex
+     * @param buttonID
+     * @param doc
+     */
+      toggleButton = undoable((doc: Doc, icon: string) => {
+
+
+
+        // this.cardSort_customField && (doc[this.cardSort_customField] = buttonID);
+
+        // doc.cardSort_activeIcons = new List<string>()
+
+
+        // const list = StrListCast(doc.cardSort_activeIcons);
+        // doc.cardSort_activeIcons = new List<string>(list.includes(icon) ? list.filter(d => d !== icon) : [...list, icon]);
+
+        BoolCast(doc[icon]) ? doc[icon] = false : doc[icon] = true
+
+
+
+        // StrListCast(doc.cardSort_activeIcons).push(iconMap[buttonID])
+    }, 'toggle card tag');
+
+
+    getButtonIcon = (doc: Doc, icon: any): JSX.Element => {
+
+        // const isActive = StrListCast(doc.cardSort_activeIcons).includes(icon)
+        const isActive = doc[icon]
+
+        // console.log(StrListCast(doc.cardSort_activeIcons))
+        const color = isActive ? '#4476f7' : '#323232';
+
+        return <FontAwesomeIcon icon={icon} style={{ color , height: '30px', width: '30px'}} />;
+    };
+
+    render (){
+        return (
+            <>
+                {this.renderButtons(this._props.doc)}
+
+            </>
+        )
+
+    }
+
+
+
+}
+
+
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index 57973ef34..eaa3eaebf 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -66,8 +66,9 @@ $highlightedText: #82e0ff;
     .content-wrapper {
         padding-top: 10px;
         min-height: 50px;
-        max-height: 150px;
+        // max-height: 150px;
         overflow-y: auto;
+        height: 100%
     }
 
     .btns-wrapper-gpt {
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index bbd5ea630..a41c33a4d 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -19,7 +19,9 @@ import { AnchorMenu } from '../AnchorMenu';
 import './GPTPopup.scss';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { SnappingManager } from '../../../util/SnappingManager';
-
+import { DocumentView } from '../../nodes/DocumentView';
+import { DocCast } from '../../../../fields/Types';
+import { RTFCast } from '../../../../fields/Types';
 
 export enum GPTPopupMode {
     SUMMARY,
@@ -177,6 +179,59 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         this.quizAnswer = e.target.value;
     });
 
+    generateQuiz = async () => {
+        this.setLoading(true);
+        this.setSortDone(false);
+
+
+        const selected = DocumentView.SelectedDocs().lastElement();
+
+        const questionText = 'Question: ' + StrCast(selected['gptInputText']);
+
+        if (StrCast(selected['gptRubric']) === '') {
+            const rubricText = 'Rubric: ' + await this.generateRubric(StrCast(selected['gptInputText']), selected)
+        }
+
+        const rubricText = 'Rubric: ' + (StrCast(selected['gptRubric']))
+        // const rubricText = 'Rubric: ' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text);
+        const queryText = questionText + ' UserAnswer: ' + this.quizAnswer + '. ' + 'Rubric' + rubricText;
+
+        try {
+            const res = await gptAPICall(queryText, GPTCallType.QUIZ);
+            if (!res) {
+                console.error('GPT call failed');
+                return;
+            }
+            console.log(res)
+            this.setQuizResp(res)
+
+            this.setLoading(false);
+            this.setSortDone(true);
+
+            // this._outputValue = res;
+        } catch (err) {
+            console.error('GPT call failed');
+        }
+
+
+    }
+
+    generateRubric = async (inputText: string, doc:Doc) => {
+        try {
+            const res = await gptAPICall(inputText, GPTCallType.RUBRIC);
+            console.log(res + "rubbbb")
+            // if (!res) {
+            //     console.error('GPT call failed');
+            //     return;
+            // }
+            doc['gptRubric']= res;
+            return res
+        } catch (err) {
+            console.error('GPT call failed');
+        }
+
+    }
+
 
 
     @observable private regenerateCallback: (() => Promise<void>) | null = null;
@@ -192,6 +247,14 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     public createFilteredDoc: (axes?: any) => boolean = () => false;
     public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined;
 
+    @observable quizRespText: string = ''
+
+    @action setQuizResp (resp: string) {
+        this.quizRespText = resp
+
+    }
+
+
     /**
      * Sorts cards in the CollectionCardDeckView
      */
@@ -403,7 +466,8 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     )
     
     cardActual = (opt: GPTPopupMode) => {
-        if (opt === GPTPopupMode.SORT) {
+        const isSort = opt === GPTPopupMode.SORT
+        // if (opt === GPTPopupMode.SORT) {
             return (
                 !this.sortDone ? (
                     <>
@@ -412,24 +476,24 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
                                 className="searchBox-input"
                                 defaultValue=""
                                 autoComplete="off"
-                                onChange={this.sortPromptChanged}
+                                onChange={isSort ? this.sortPromptChanged : this.quizAnswerChanged}
                                 onKeyDown={e => {
                                     if (e.key === 'Enter') {
-                                        this.generateSort();
+                                        isSort ? this.generateSort() : this.generateQuiz();
                                     }
                                     e.stopPropagation();
                                 }}
                                 type="text"
-                                placeholder="How do you want to sort your cards?"
+                                placeholder={`${isSort ? 'How do you want to sort your cards?' : 'What is the selected card?'}`}
                                 id="search-input"
                                 style={{ width: '100%' }}
                             />
                         {/* </div>
                         <div className="btns-wrapper-gpt"> */}
                             <Button
-                                tooltip="Have ChatGPT sort your cards for you!"
-                                text="Sort!"
-                                onClick={this.generateSort}
+                                tooltip={`${isSort ? 'HaveChatGPT sort ypur cards for you!' : 'See how close you get to the right answer!'}`}
+                                text={`${isSort ? 'Sort!' : 'Check!'}`}
+                                onClick={isSort ? this.generateSort : this.generateQuiz}
                                 color={StrCast(Doc.UserDoc().userVariantColor)}
                                 type={Type.TERT}
                                 style={{
@@ -446,7 +510,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
                 ) : (
                     <div>
                         <div className="content-wrapper">
-                            <p>{this.text === 'Something went wrong :(' ? 'Something went wrong :(' : `${this.sortRespText}`}</p>
+                            <p>{this.text === 'Something went wrong :(' ? 'Something went wrong :(' : `${isSort ? this.sortRespText : this.quizRespText}`}</p>
                             <IconButton
                                 tooltip="Generate Again"
                                 onClick={() => this.setSortDone(false)}
@@ -457,50 +521,50 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
                     </div>
                 )
             );
-        } else if (opt === GPTPopupMode.QUIZ) {
-            return (
-                <>
-                    <div className="btns-wrapper-gpt">
-                        <input
-                            className="searchBox-input"
-                            defaultValue=""
-                            autoComplete="off"
-                            onChange={this.quizAnswerChanged}
-                            onKeyDown={e => {
-                                if (e.key === 'Enter') {
-                                    this.generateSort();
-                                }
-                                e.stopPropagation();
-                            }}
-                            type="text"
-                            placeholder="What is the selected card?"
-                            id="search-input"
-                            style={{ width: '100%' }}
-                        />
-                    </div>
-                    <div className="btns-wrapper-gpt">
-                        <Button
-                            tooltip="See how close you got to the right answer!"
-                            text="Check!"
-                            onClick={this.generateSort}
-                            color={StrCast(Doc.UserDoc().userVariantColor)}
-                            type={Type.TERT}
-                            style={{
-                                width: '90%',
-                                textAlign: 'center',
-                                color: '#ffffff',
-                                fontSize: '16px',
-                            }}
-                        />
-                    </div>
-                </>
-            );
-        }
+        // } else if (opt === GPTPopupMode.QUIZ) {
+        //     return (
+        //         <>
+        //             <div className="btns-wrapper-gpt">
+        //                 <input
+        //                     className="searchBox-input"
+        //                     defaultValue=""
+        //                     autoComplete="off"
+        //                     onChange={this.quizAnswerChanged}
+        //                     onKeyDown={e => {
+        //                         if (e.key === 'Enter') {
+        //                             this.generateQuiz();
+        //                         }
+        //                         e.stopPropagation();
+        //                     }}
+        //                     type="text"
+        //                     placeholder="What is the selected card?"
+        //                     id="search-input"
+        //                     style={{ width: '100%' }}
+        //                 />
+        //             {/* </div>
+        //             <div className="btns-wrapper-gpt"> */}
+        //                 <Button
+        //                     tooltip="See how close you got to the right answer!"
+        //                     text="Check!"
+        //                     onClick={() => this.generateQuiz()}
+        //                     color={StrCast(Doc.UserDoc().userVariantColor)}
+        //                     type={Type.TERT}
+        //                     style={{
+        //                         width: '90%',
+        //                         textAlign: 'center',
+        //                         color: '#ffffff',
+        //                         fontSize: '16px',
+        //                     }}
+        //                 />
+        //             </div>
+        //         </>
+        //     );
+        // }
     };
     
     sortBox = () => (
         <div>
-            {this.heading('SORTING')}
+            {this.heading(this.mode === GPTPopupMode.SORT ? 'SORTING' : 'QUIZ')}
             <>
                 {!this.cardsDoneLoading || this.loading ? (
                     <div className="content-wrapper">
@@ -673,7 +737,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
                             tooltip="back"
                             icon={<CgCornerUpLeft size="16px" />}
                             onClick={() => this.mode = GPTPopupMode.CARD}
-                            style = {{right: '-55px'}}
+                            style = {{right: '-20%'}}
                         />
                     )}
                     <IconButton
-- 
cgit v1.2.3-70-g09d2


From 3ab5b620e462a35d0dc23f67014fc5aa9ef4803c Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Thu, 22 Aug 2024 03:03:03 -0400
Subject: liberty

---
 src/client/util/CurrentUserUtils.ts                |  14 +-
 src/client/views/FilterPanel.scss                  |  67 +++-
 src/client/views/FilterPanel.tsx                   | 352 ++++++++++-----------
 .../views/collections/CollectionCardDeckView.tsx   |   9 +-
 src/client/views/nodes/IconTagBox.tsx              |  75 ++++-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |   2 +-
 6 files changed, 324 insertions(+), 195 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 0d8955ced..1a7c0f6e1 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -714,6 +714,17 @@ pie title Minerals in my tap water
     //     ]
     // }
     static tagGroupTools(): Button[] {
+        if (!Doc.UserDoc().activeDashboard){
+            Doc.UserDoc().myFilterHotKeyTitles = new List<string>(['Star', 'Heart', 'Bolt', 'Cloud' ])
+
+            Doc.UserDoc()['Star'] = 'star'
+            Doc.UserDoc()['Heart'] = 'heart'
+            Doc.UserDoc()['Bolt'] = 'bolt'
+            Doc.UserDoc()['Cloud'] = 'cloud'
+        
+        }
+
+
         // hack: if there's no dashboard, create default filters.  otherwise, just make sure that the Options button is preserved
         return (Doc.UserDoc().activeDashboard ? [] : [
             { title: "Star", isSystem: false, icon: "star", toolTip:"Click to toggle the star group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
@@ -730,7 +741,7 @@ pie title Minerals in my tap water
             // { title: "", icon, toolTip:"Click to toggle group 6's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"6", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             // { title: "", icon, toolTip:"Click to toggle group 7's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"7", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
         ]).concat([
-            { title: "Options", isSystem: true,icon: "gear", toolTip:"Click to customize your filter panel",  btnType: ButtonType.ClickButton,  expertMode: false, toolType:"opts", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}
+            { title: "Options", isSystem: true,icon: "gear", toolTip:"Click to customize your filter panel", btnType: ButtonType.ClickButton,  expertMode: false, toolType:"opts", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}
         ])
     }
     static viewTools(): Button[] {
@@ -1045,6 +1056,7 @@ pie title Minerals in my tap water
         doc.filterDocCount = 0;
         doc.treeView_FreezeChildren = "remove|add";
         doc.activePage = doc.activeDashboard === undefined ? 'home': doc.activePage;
+
         this.setupLinkDocs(doc, linkDatabaseId);
         this.setupSharedDocs(doc, sharingDocumentId);  // sets up the right sidebar collection for mobile upload documents and sharing
         this.setupDefaultIconTemplates(doc);  // creates a set of icon templates triggered by the document deoration icon
diff --git a/src/client/views/FilterPanel.scss b/src/client/views/FilterPanel.scss
index 1877cc78b..0b9076f3b 100644
--- a/src/client/views/FilterPanel.scss
+++ b/src/client/views/FilterPanel.scss
@@ -235,28 +235,62 @@
     width: 100%; //width: 25px;
     border-radius: 5px;
     // margin-right: 20px;
-    margin-bottom: 8px;
+    margin-top: 8px;
     border-color: #d3d3d3;
     border-style: solid;
     border-width: thin;
     transition: all 0.3s ease-out;
+    display: flex;
+    flex-direction: row;
+    padding: 5px; /* Adjust the padding value as needed */
+
 
     &:hover{
         border-color: #e9e9e9;
-        background-color: #878484
+        background-color: #6d6c6c
     }
 
 
 
-    &.active {
-        background-color: #878484;
+    // &.active {
+    //     background-color: #6d6c6c;
+
+
+
+    //     .icon-panel{
+    //         display: flex
+    //     }
+    // }
 
+    .hotKey-icon, .hotKey-close{
+        background-color: transparent;
+        border-radius: 10%;
+        padding: 5px;
 
 
-        .icon-panel{
-            display: flex
+        &:hover{
+            background-color: #616060;
         }
     }
+
+    .hotKey-close{
+        right: 30px;
+        position: fixed;  
+        padding-top: 10px;
+  
+}
+
+    .hotkey-title{
+        top: 6px;
+        position: relative;
+    }
+
+    .hotkey-title-input{
+        background-color: transparent;
+        border: none;
+        border-color: transparent;
+        outline: none;
+    }
 }
 
 .hotKeyButtons {
@@ -287,7 +321,26 @@
 }
 
 .icon-panel {
-    bottom: -14px;
+    // bottom: -14px;
+    position: absolute;
+    z-index: 10000;
+    // background-color: #616060;
+    // background-color: #323232;
+    border-color: black;
+    border-style: solid;
+    border-width: medium;
+    border-radius: 10%;
+    background-color: #323232;
+
+    .icon-panel-button{
+        background-color: #323232;
+        border-radius: 10%;
+
+
+        &:hover{
+            background-color:#7a7878
+        }
+    }
 
 
 
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index e5f9fdb51..17c5cb173 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -41,6 +41,7 @@ import { Tooltip } from '@mui/material';
 import { useLocalObservable } from 'mobx-react';
 import { useRef } from 'react';
 import { useEffect } from 'react';
+import { useState } from 'react';
 
 
 interface filterProps {
@@ -240,157 +241,74 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
         return nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2));
     };
 
-    addHotkey() {
+    addHotkey(hotKey: string) {
         const buttons = DocCast(Doc.UserDoc().myContextMenuBtns);
         const filter = DocCast(buttons.Filter);
         const filter2 = DocCast(filter);
         const but2 = Doc.UserDoc().myContextMenuBtns;
 
-        console.log(DocCast(filter).data);
-
-        //[idToDoc("0bec0963-95ac-4978-8606-8af4c7b9badd"),idToDoc("f740e910-bf83-4ee3-b13a-1e63c49dd254"),idToDoc("61db82b0-025a-4cb9-937b-97b1290e8f1a"),idToDoc("1046859f-34ac-4421-bc07-ee2f2cf812e2"),idToDoc("badaff47-3c3a-4b78-a7d6-f2cbc8e45ba0")]
-
-        // const newCol = Docs.Create.CarouselDocument(DocListCast(doc[Doc.LayoutFieldKey(doc)]), {
-        //     _width: 250,
-        //     _height: 200,
-        //     _layout_fitWidth: false,
-        //     _layout_autoHeight: true,
-        // });
-
-        // CurrentUserUtils.setupContextMenuButtons()
-
-        // const hm = Docs.Create.FontIconDocument()
-
-        // DocumentView.getDocumentView(filter2)?.ComponentView?.addDocument?.(hm)
-
-        // console.log(DocumentView.getDocumentView(filter2) + "hiiiii")
-
-        // DocumentView.getDocumentView(DocCast(buttons['Card']))?.ComponentView?.addDocument?.(hm)
-
-        //         const hi = CurrentUserUtils.contextMenuTools()
 
         const newKey: Button = {
-            title: 'Bob',
-            icon: 'cloud',
-            toolTip: "Click to toggle the cloud group's visibility",
+            title: hotKey,
+            icon: 'bolt',
+            toolTip: `Click to toggle the ${hotKey}'s group's visibility`,
             btnType: ButtonType.ToggleButton,
             expertMode: false,
-            toolType: 'cloud',
+            toolType: 'bolt',
             funcs: {},
             scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}' },
         };
 
         //         const heyy = [...hi, newKey]
 
-        Doc.UserDoc().myFilterHotKeyTitles = new List<string>(['hi hi', 'supppp'])
+        const currHotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles)
 
-        Doc.UserDoc()['hi hi'] = 'heart'
-        Doc.UserDoc()['supppp'] = 'star'
+        Doc.UserDoc().myFilterHotKeyTitles = new List<string>(currHotKeys.concat(hotKey))
 
-        //         CurrentUserUtils.setupContextMenuButtons(Doc.UserDoc());
+        Doc.UserDoc()[hotKey] = 'bolt'
+        // Doc.UserDoc()['supppp'] = 'star'
 
-        //         Doc.UserDoc().hi = Docs.Create.FontIconDocument(
-        //            { title: '',
-        //                 icon: 'map-pin',
 
-        //                 backgroundColor: '#ACCEF7',
-        //                 layout_hideAllLinks: true,
-        //                 _width: 15,
-        //                 _height: 15,
-        //                 _xPadding: 0,
-        //    }
-        //         )
+        const newBtn = CurrentUserUtils.setupContextMenuBtn(newKey, filter);
+        newBtn.isSystem = newBtn[DocData].isSystem = undefined;
 
-        //         // buttons['Filter']
+        const subDocs = DocListCast(filter.data)
+        const opts = subDocs[subDocs.length-1]
+        Doc.AddDocToList(filter, 'data', newBtn, opts, true);
 
-        // const reqdCtxtOpts: DocumentOptions = {
-        //     title: 'Filter',
-        //     undoIgnoreFields: new List<string>(['width', 'linearView_IsOpen']),
-        //     flexGap: 0,
-        //     childDragAction: dropActionType.embed,
-        //     childDontRegisterViews: true,
-        //     linearView_IsOpen: true,
-        //     ignoreClick: true,
-        //     linearView_Expandable: false,
-        //     _height: 35,
-        // };
-        const ctxtMenuBtn = CurrentUserUtils.setupContextMenuBtn(newKey, filter);
-        ctxtMenuBtn.isSystem = ctxtMenuBtn[DocData].isSystem = undefined;
-        Doc.AddDocToList(filter, 'data', ctxtMenuBtn);
-        // // // DocUtils.AssignOpts(buttons, reqdCtxtOpts, ctxtMenuBtns);
-
-        //  DocUtils.AssignOpts(DocCast(buttons['Filter']), reqdCtxtOpts, [ctxtMenuBtns]);
+        
 
-        // newCol && dv.ComponentView?.addDocument?.(newCol);
+        // console.log(filter[DocData].data + 'ok')
+        // // console.log(filter[DocData][0] + 'help')
+        // console.log(filter[DocData] + 'good grief')
 
-        // console.log(but2 + "omgg")
+        // console.log(DocCast(DocCast(filter.data))[0])
+        // this.removeHotKey()
 
-        // doc.cardSort_activeIcons = new List<string>(list.includes(icon) ? list.filter(d => d !== icon) : [...list, icon]);
+        // console.log(DocCast(filter.data) + 'HI')
+        // console.log(DocListCast(filter.data) + 'WOOOOO')
+        // console.log(DocCast(filter.data)[0] + 'hm :(')
 
-        // Doc.UserDoc().myContextMenuBtns = new List<Doc>([...buttons, newKey as Doc])
     }
 
+    
+
     hotKeyButtons() {
         const selected = DocumentView.SelectedDocs().lastElement();
 
-        function isAttrFiltered(attr: string) {
-            if (selected && selected._childFilters != undefined && selected.type === DocumentType.COL) {
-                return StrListCast(selected._childFilters).some(filter => filter.includes(attr));
-            } else {
-                return false;
-            }
-            // return selected._childFilters ? StrListCast(selected._childFilters).some(filter => filter.includes(attr)) : false;
-        }
+        // console.log(StrListCast(Doc.UserDoc().myFilterHotKeyTitles) + "hiii")
 
         const hotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
 
-        console.log(hotKeys + 'hiihihi');
-
-        const hi = ['star', 'heart', 'bolt'];
-
-        
-
-
-        //selecting a button should make t so that the the icon on the top filter panel becomes said icon
-
+        // hotKeys.forEach(l => console.log(l + "render"))
+    
+        // Selecting a button should make it so that the icon on the top filter panel becomes said icon
         const buttons = hotKeys.map((hotKey, i) => (
-            // <div key={i} className={`filterHotKey-button ${isAttrFiltered(hotKey) ? 'active' : ''} ` }
-            
-            // onClick={undoable((e: React.MouseEvent) => {
-
-            //     // if (e.target === e.currentTarget){
-            //         isAttrFiltered(hotKey) ? Doc.setDocFilter(selected, hotKey, true, 'remove') : Doc.setDocFilter(selected, hotKey, true, 'match');
-            //     // }
-            // }, hotKey)}
-
-            // // style = {{backgroundColor: `${isAttrFiltered(hotKey) ? '#878484' : SettingsManager.userBackgroundColor}`}}
-            
-            
-            // >
-                {/* <Toggle
-                    toggleStatus={isAttrFiltered(hotKey)}
-                    tooltip={'toggle filter'}
-                    text={hotKey}
-                    color={SettingsManager.userColor}
-                    icon={<FontAwesomeIcon icon={Doc.UserDoc()[hotKey] as any}/>}
-                    iconPlacement="left"
-                    align="flex-start"
-                    fillWidth
-                    toggleType={ToggleType.BUTTON}
-                    onClick={undoable(() => {
-                        isAttrFiltered(hotKey) ? Doc.setDocFilter(selected, hotKey, true, 'remove') : Doc.setDocFilter(selected, hotKey, true, 'match');
-                    }, hotKey)}
-                /> */}
-                <>
-
-                    <Tooltip key={i} title={<div className="dash-tooltip">Click to customize this hotkey's icon</div>}>
-                        <HotKeyIconButton hotKey = {hotKey} />
-
-                    </Tooltip>
-
-                   </> 
+            <Tooltip key={hotKey} title={<div className="dash-tooltip">Click to customize this hotkey's icon</div>}>
+                <HotKeyIconButton hotKey={hotKey} selected = {selected}/>
+            </Tooltip>
         ));
-
+    
         return buttons;
     }
 
@@ -465,7 +383,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
                 <div>
                     <div className="filterBox-select">
                         <div style={{ width: '100%' }}>
-                            <FieldsDropdown Document={this.Document} selectFunc={this.addHotkey} showPlaceholder placeholder="add a hotkey" addedFields={['acl_Guest', LinkedTo, 'Star', 'Heart', 'Bolt', 'Cloud']} />
+                            <FieldsDropdown Document={this.Document} selectFunc={this.addHotkey} showPlaceholder placeholder="add a hotkey" addedFields={['acl_Guest', LinkedTo]} />
                         </div>
                     </div>
                 </div>
@@ -590,28 +508,87 @@ interface HotKeyButtonProps {
 const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey, selected}) => {
     const state = useLocalObservable(() => ({
         isActive: false,
+        isEditing: false,
+        myHotKey: hotKey,
+
         toggleActive() {
             this.isActive = !this.isActive;
         },
         deactivate() {
             this.isActive = false;
+        },
+        startEditing() {
+            this.isEditing = true;
+        },
+        stopEditing() {
+            this.isEditing = false;
+        },
+        setHotKey(newHotKey: string) {
+            this.myHotKey = newHotKey;
         }
     }));
 
     const panelRef = useRef<HTMLDivElement>(null);
+    const inputRef = useRef<HTMLInputElement>(null);
 
     const handleClick = () => {
         state.toggleActive();
-
-        console.log(state.isActive + "hmmm")
+        // console.log(state.isActive + "hmmm")
     };
 
+    const hotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles)
+
+    const myHotKeyDoc = () => {
+        const buttons = DocCast(Doc.UserDoc().myContextMenuBtns);
+        const filter = DocCast(buttons.Filter);
+        const hotKeyDocs = DocListCast(filter.data)
+        return hotKeyDocs.filter(k => StrCast(k.title) === hotKey)[0]
+
+    }
+
+    const removeHotKey = () => {
+        const buttons = DocCast(Doc.UserDoc().myContextMenuBtns);
+        const filter = DocCast(buttons.Filter);
+
+        Doc.RemoveDocFromList(filter, 'data', myHotKeyDoc());
+
+
+
+
+
+        // console.log((DocListCast(filter.data)[0].title) + "emmanuel")
+
+
+        // console.log(DocCast(filter.data) + 'HI')
+        // console.log(DocCast(filter.data)[0] + 'hm :(')
+
+    }
     const handleClickOutside = (event: MouseEvent) => {
         if (panelRef.current && !panelRef.current.contains(event.target as Node)) {
             state.deactivate();
+            if (state.isEditing) {
+                state.stopEditing();
+
+                updateFromInput()
+
+
+
+                // Doc.UserDoc().myFilterHotKeyTitles = new List<string>(hotKeys.map(k => k === hotKey ? state.myHotKey : k));
+                // Doc.UserDoc()[state.myHotKey] = StrCast(Doc.UserDoc()[hotKey])
+
+            }
         }
     };
 
+    const updateFromInput = () => {
+        const hi = myHotKeyDoc()
+        Doc.UserDoc().myFilterHotKeyTitles = new List<string>(hotKeys.map(k => k === hotKey ? state.myHotKey : k));
+        Doc.UserDoc()[state.myHotKey] = StrCast(Doc.UserDoc()[hotKey])
+        hi.title = state.myHotKey
+        hi.toolTip = `Click to toggle the ${state.myHotKey}'s group's visibility`     
+    }
+
+
     useEffect(() => {
         document.addEventListener('mousedown', handleClickOutside);
         return () => {
@@ -619,73 +596,96 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey, select
         };
     }, []);
 
-    const iconOpts = ['star', 'heart', 'bolt'];
+    const iconOpts = ['star', 'heart', 'bolt', 'satellite', 'palette', 'robot', 'lightbulb', 'highlighter', 'book', 'chalkboard' ];
 
     const iconPanel = iconOpts.map((icon, i) => (
-        <button key={i} onClick={() => Doc.UserDoc()[hotKey] = icon}>
-            <FontAwesomeIcon icon={icon as any} />
-        </button>
-    ));
+        <button key={i} onClick={(e: React.MouseEvent) => {
+            e.stopPropagation;
+            Doc.UserDoc()[hotKey] = icon;
+            const hi = myHotKeyDoc()
+            hi.icon = icon
 
-    const hotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles)
 
-    return (
-        <>
+            
+            }} className='icon-panel-button'>
+            <FontAwesomeIcon icon={icon as any} color = {SnappingManager.userColor}/>
+        </button>
+    ));
 
-        <div className={`filterHotKey-button ${isAttrFiltered(hotKey) ? 'active' : ''} ` }
-                    
-                    onClick={undoable((e: React.MouseEvent) => {
+    function isAttrFiltered(attr: string) {
+        if (selected && selected._childFilters !== undefined && selected.type === DocumentType.COL) {
+            return StrListCast(selected._childFilters).some(filter => filter.includes(attr));
+        } else {
+            return false;
+        }
+    }
 
-                        // if (e.target === e.currentTarget){
-                            isAttrFiltered(hotKey) ? Doc.setDocFilter(selected, hotKey, true, 'remove') : Doc.setDocFilter(selected, hotKey, true, 'match');
-                        // }
-                    }, hotKey)}
 
-                    // style = {{backgroundColor: `${isAttrFiltered(hotKey) ? '#878484' : SettingsManager.userBackgroundColor}`}}
-                    
-                    
+    return (
+        <div className={`filterHotKey-button ${isAttrFiltered(hotKey) ? 'active' : ''}`} 
+            onClick={(e) => {
+                e.stopPropagation();
+                state.startEditing();
+                setTimeout(() => inputRef.current?.focus(), 0);
+            }}
+        >
+            <div className={`hotKey-icon-button ${state.isActive ? 'active' : ''}`} ref={panelRef}>
+                <Tooltip title={<div className="dash-tooltip">Click to customize this hotkey's icon</div>}>
+                    <button
+                        type="button"
+                        className='hotKey-icon'
+                        onClick={(e: React.MouseEvent) => {
+                            e.stopPropagation(); 
+                            handleClick();
+                        }}
                     >
-        <div className={`hotKey-icon-button ${state.isActive ? 'active' : ''}`} ref={panelRef}>
-            <Tooltip title={<div className="dash-tooltip">Click to customize this hotkey's icon</div>}>
-                <button
-                    type="button"
-                    onClick={(e: React.MouseEvent) => {
-                        console.log('hiii')
-                        e.stopPropagation(); 
-                        handleClick();
-
+                        <FontAwesomeIcon icon={Doc.UserDoc()[hotKey] as any} size="2xl" color={SnappingManager.userColor}/>
+                    </button>
+                </Tooltip>
+                {state.isActive && (
+                    <div className="icon-panel">
+                        {iconPanel}
+                    </div>
+                )}
+            </div>
+            {state.isEditing ? (
+                <input
+                    ref={inputRef}
+                    type="text"
+                    value={state.myHotKey.toUpperCase()}
+                    onChange={(e) => state.setHotKey(e.target.value)}
+                    onBlur={() => {
+                        state.stopEditing();
+                        updateFromInput()
                     }}
+                    onKeyDown={(e) => {
+                        if (e.key === 'Enter') {
+                            state.stopEditing();
+                            updateFromInput()
 
-                >
-                    <FontAwesomeIcon icon={Doc.UserDoc()[hotKey] as any} size="2xl"/>
-                </button>
-            </Tooltip>
-            {state.isActive && (
-                <div className="icon-panel" >
-                    {iconPanel}
-                </div>
+                        }
+                    }}
+                    className='hotkey-title-input'
+                />
+            ) : (
+                <p className='hotkey-title'>{hotKey.toUpperCase()}</p>
             )}
-
-            
-        </div>
-
-        <p>     {hotKey.toUpperCase()}    </p>
-
-
-        <button onClick={(e: React.MouseEvent) => { 
-            e.stopPropagation(); 
-            Doc.UserDoc().myFilterHotKeyTitles = new List<string>(hotKeys.filter(k => k !== hotKey)); 
-        }}>
-            <FontAwesomeIcon icon={'x' as any} />
-        </button>
-
+            <button className='hotKey-close' onClick={(e: React.MouseEvent) => { 
+                e.stopPropagation(); 
+                const hi = StrListCast(Doc.UserDoc().myFilterHotKeyTitles)
+                // hi.forEach((str) => {
+                //     console.log(str + "before");
+                // });
+                Doc.UserDoc().myFilterHotKeyTitles = new List<string>(hotKeys.filter(k => k !== hotKey)); 
+                removeHotKey()
+
+                // hi.forEach((str) => {
+                //     console.log(str + "after");
+                // });
+
+            }}>
+                <FontAwesomeIcon icon={'x' as any} color={SnappingManager.userColor}/>
+            </button>
         </div>
-
-
-        </>
-
-        
     );
-});
-
-
+})
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 736cc2354..52f01e239 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -95,7 +95,14 @@ export class CollectionCardView extends CollectionSubView() {
     };
 
     componentDidMount(): void {
+
         this.Document.childFilters_boolean = 'OR'
+        this.childDocsWithoutLinks.forEach(c => {
+            c[DocData].showIconTags = true
+            
+        });
+
+        
         this._disposers.sort = reaction(
             () => ({ cardSort: this.cardSort }),
             ({ cardSort}) => (cardSort === cardSortings.Chat ? this.openChatPopup() : GPTPopup.Instance.setVisible(false))
@@ -672,7 +679,7 @@ export class CollectionCardView extends CollectionSubView() {
                                 }}
                     onMouseEnter={() => this.setHoveredNodeIndex(index)}>
                     {this.displayDoc(doc, childScreenToLocal)}
-                    {this.renderButtons(doc, this.cardSort)}
+                    {/* {this.renderButtons(doc, this.cardSort)} */}
                 </div>
             );
         });
diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx
index 73360b684..cdf2075cb 100644
--- a/src/client/views/nodes/IconTagBox.tsx
+++ b/src/client/views/nodes/IconTagBox.tsx
@@ -16,6 +16,15 @@ import { BoolCast } from "../../../fields/Types";
 import { DocCast } from "../../../fields/Types";
 import './IconTagBox.scss';
 import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../fields/DocSymbols';
+import { StrListCast } from "../../../fields/Doc";
+import { StrCast } from "../../../fields/Types";
+import { DocListCast } from "../../../fields/Doc";
+import { List } from "../../../fields/List";
+import { action } from "mobx";
+import { DragManager } from "../../util/DragManager";
+import { setupMoveUpEvents } from "../../../ClientUtils";
+import { returnFalse } from "../../../ClientUtils";
+import { emptyFunction } from "../../../Utils";
 
 
 export interface IconTagProps {
@@ -61,6 +70,53 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
         this._props.doc[DocData].tagHeight = 36*this.currentScale;
     }
 
+    // createCollection = () => {
+    //     // Get the documents that contain the keyword.
+    //     const selected = DocListCast(this.getKeywordCollectionDocs()!);
+    //     const newEmbeddings = selected.map(doc => Doc.MakeEmbedding(doc));
+
+    //     // Create a new collection and set up configurations.
+    //     const newCollection = ((doc: Doc) => {
+    //         const docData = doc[DocData];
+    //         docData.data = new List<Doc>(newEmbeddings);
+    //         docData.title = this._props.keyword;
+    //         doc._freeform_panX = doc._freeform_panY = 0;
+    //         return doc;
+    //     })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
+    //     newEmbeddings.forEach(embed => (embed.embedContainer = newCollection));
+    //     newCollection._width = 900;
+    //     newCollection._height = 900;
+    //     newCollection.layout_fitWidth = true;
+
+    //     // Add the collection to the keyword document's list of associated smart collections.
+    //     this._props.keywordDoc.collections = new List<Doc>([...DocListCast(this._props.keywordDoc.collections), newCollection]);
+    //     newCollection[DocData].data_labels = new List<string>([this._props.keyword]);
+    //     newCollection[DocData][`${this._props.keyword}`] = true;
+    //     newCollection[DocData].showLabels = true;
+    //     return newCollection;
+    // };
+
+    // @action
+    // handleDragStart = (e: React.PointerEvent) => {
+    //     if (this._props.isEditing) {
+    //         const clone = this.ref.current?.cloneNode(true) as HTMLElement;
+    //         if (!clone) return;
+
+    //         setupMoveUpEvents(
+    //             this,
+    //             e,
+    //             () => {
+    //                 const dragData = new DragManager.DocumentDragData([this.createCollection()]);
+    //                 DragManager.StartDocumentDrag([this.ref.current!], dragData, e.clientX, e.clientY, {});
+    //                 return true;
+    //             },
+    //             returnFalse,
+    //             emptyFunction
+    //         );
+    //         e.preventDefault();
+    //     }
+    // };
+
     /**
      * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups
      * @param doc
@@ -70,7 +126,10 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
     renderButtons = (doc: Doc): JSX.Element | null => {
         // if (cardSort !== cardSortings.Custom) return null;
 
-        const amButtons = 4
+        const amButtons = StrListCast(Doc.UserDoc().myFilterHotKeyTitles).length
+
+        const keys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles)
+
 
         // const amButtons = Math.max(
         //     4,
@@ -86,11 +145,9 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
 
         const totalWidth = amButtons * 35 + amButtons * 2 * 5 + 6;
 
-        const iconMap: { [key: number]: any } = {
-            0: 'star',
-            1: 'heart',
-            2: 'cloud',
-            3: 'bolt'
+        const iconMap =  (buttonID: number)  => {
+            return StrCast(Doc.UserDoc()[keys[buttonID]])
+
         };
 
 
@@ -108,9 +165,9 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
             }}
             >           
                 {numberRange(amButtons).map(i => (
-                    <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from the {iconMap[i]} group</div>}>
-                        <button key = {i} type="button" onClick={() => this.toggleButton(doc, iconMap[i] )}>
-                            {this.getButtonIcon(doc, iconMap[i])}
+                    <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from the {iconMap(i)} group</div>}>
+                        <button key = {i} type="button" onClick={() => this.toggleButton(doc, iconMap(i) )}>
+                            {this.getButtonIcon(doc, iconMap(i))}
                         </button>
                     </Tooltip>
                 ))}
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index a41c33a4d..d216cc421 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -484,7 +484,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
                                     e.stopPropagation();
                                 }}
                                 type="text"
-                                placeholder={`${isSort ? 'How do you want to sort your cards?' : 'What is the selected card?'}`}
+                                placeholder={`${isSort ? 'How do you want to sort your cards?' : 'Define the selected card?'}`}
                                 id="search-input"
                                 style={{ width: '100%' }}
                             />
-- 
cgit v1.2.3-70-g09d2


From 4e34335e600b5d9d29e8a4af99fda6b2a6d3ba69 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Tue, 10 Sep 2024 10:54:29 -0400
Subject: more chat features

---
 src/client/apis/gpt/GPT.ts                         |  34 ++-
 src/client/util/CurrentUserUtils.ts                |  20 +-
 src/client/views/FilterPanel.scss                  |  18 ++
 src/client/views/FilterPanel.tsx                   |   3 +
 src/client/views/InkingStroke.tsx                  |  14 +-
 .../views/collections/CollectionCardDeckView.tsx   | 221 +++++++-------
 src/client/views/global/globalScripts.ts           |  26 +-
 src/client/views/nodes/IconTagBox.tsx              |  23 +-
 src/client/views/pdf/GPTPopup/GPTPopup.scss        |  56 +++-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         | 319 ++++++++++++++++-----
 10 files changed, 525 insertions(+), 209 deletions(-)

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

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index d95e564c7..f9183d3e2 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -12,7 +12,10 @@ enum GPTCallType {
     DESCRIBE = 'describe',
     MERMAID = 'mermaid',
     DATA = 'data',
-    RUBRIC = 'rubric'
+    RUBRIC = 'rubric',
+    TYPE = 'type',
+    SUBSET = 'subset',
+    INFO = 'info'
 }
 
 type GPTCallOpts = {
@@ -45,7 +48,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
         model: 'gpt-4o',
         maxTokens: 2048,
         temp: 0.25,
-        prompt: "The user is going to give you a list of descriptions. Each one is separated by `======` on either side. Descriptions will vary in length, so make sure to only separate when you see `======`. Sort them by shared content. Make sure each description is only in the list once. Each item should be separated by `======`. Immediately afterward, surrounded by `------` on BOTH SIDES, provide some insight into your reasoning for the way you sorted (and mention nothing about the formatting details given in this description). It is VERY important that you format it exactly as described, ensuring the proper number of `=` and `-` (6 of each) and no commas"
+        prompt: "The user is going to give you a list of descriptions. Each one is separated by `======` on either side. Descriptions will vary in length, so make sure to only separate when you see `======`. Sort them by the user's specifications. Make sure each description is only in the list once. Each item should be separated by `======`. Immediately afterward, surrounded by `------` on BOTH SIDES, provide some insight into your reasoning for the way you sorted (and mention nothing about the formatting details given in this description). It is VERY important that you format it exactly as described, ensuring the proper number of `=` and `-` (6 of each) and NO commas"
         // prompt: "I'm going to give you a list of descriptions. Each one is separated by `======` on either side. Descriptions will vary in length, so make sure to only separate when you see `======`. Sort them into lists by shared content. Make sure each description is in only one list. Each list should be separated by `======` with the elements within it separated by `~~~~~~`. Immediately afterward, surrounded by `------` on BOTH SIDES, provide some insight into your reasoning for the way you sorted. It is VERY important that you format it exactly as described, ensuring the proper number of `=` `~` and `-` (6 of each) and no commas.Try to create around 4 groups, but a little more or less is ok. Also, I may provide some more insight after this colon:"
     },
     describe: { model: 'gpt-4-vision-preview', maxTokens: 2048, temp: 0, prompt: 'Describe these images in 3-5 words' },
@@ -54,15 +57,38 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
         model: 'gpt-4-turbo',
         maxTokens: 1024,
         temp: 0,
-        prompt: 'List unique differences between the content of the UserAnswer and Rubric. Before each difference, label it and provide any additional information the UserAnswer missed and explain it in second person without separating it into UserAnswer and Rubric content and additional information. If there are no differences, say correct',
+        prompt: "BRIEFLY (<50 words) describe any differences between the rubric and the user's answer answer in second person. If there are no differences, say correct",
     },
 
     rubric: {
         model: 'gpt-4-turbo',
         maxTokens: 1024,
         temp: 0,
-        prompt: "Provide a definition for the vollowing term. It will be used as a rubric to evaluate the user's understanding of the topic",
+        prompt: "BRIEFLY (<75 words) provide a definition for the following term. It will be used as a rubric to evaluate the user's understanding of the topic",
     },
+
+    type: {
+        model: 'gpt-4-turbo',
+        maxTokens: 1024,
+        temp: 0,
+        prompt: "I'm going to provide you with a question. Based on the question, is the user asking you to 1. Assigns docs with tags(like star / heart etc)/labels, 2. Filter docs, 3. Provide information about a specific doc 4. Provide a specific doc based on a question/information 5. Provide general information 6. Put cards in a specific order. Answer with only the number for 2-6. For number one, provide the number (1) and the appropriate tag",
+    },
+
+    subset: {
+        model: 'gpt-4-turbo',
+        maxTokens: 1024,
+        temp: 0,
+        prompt: "I'm going to give you a list of descriptions. Each one is separated by `======` on either side. Descriptions will vary in length, so make sure to only separate when you see `======`. Based on the question the user asks, provide a subset of the given descriptions that best matches the user's specifications. Make sure each description is only in the list once. Each item should be separated by `======`. Immediately afterward, surrounded by `------` on BOTH SIDES, provide some insight into your reasoning in the 2nd person (and mention nothing about the formatting details given in this description). It is VERY important that you format it exactly as described, ensuring the proper number of `=` and `-` (6 of each) and no commas"
+    },
+
+    info: {
+        model: 'gpt-4-turbo',
+        maxTokens: 1024,
+        temp: 0,
+        prompt: "Answer the user's question with a short (<100 word) response. If a particular document is selected I will provide that information (which may help with your response)"
+    },
+
+
 };
 
 let lastCall = '';
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 1a7c0f6e1..6a81f9125 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -675,7 +675,8 @@ pie title Minerals in my tap water
             { title: "Time",      icon:"hourglass-half", toolTip:"Sort by most recent document creation",      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"time",   funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Type",      icon:"eye",            toolTip:"Sort by document type",                      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"docType",funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Color",     icon:"palette",        toolTip:"Sort by document color",                     btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "AI Sort",     icon:"robot",        toolTip:"Have Chat GPT sort your cards for you !",   btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"chat",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Tags",     icon:"bolt",        toolTip:"Sort by document's tags",                     btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"tag",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            // { title: "AI Sort",     icon:"robot",        toolTip:"Have Chat GPT sort your cards for you !",   btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"chat",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Pile",     icon:"layer-group",        toolTip:"View the cards as a pile in the free form view !",   btnType: ButtonType.ClickButton,  expertMode: false, toolType:"pile",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Chat Popup", icon:"lightbulb",     toolTip:"Toggle the chat popup's visibility!", width: 45 ,btnType: ButtonType.ToggleButton, expertMode: false, toolType:"toggle-chat",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
 
@@ -845,6 +846,9 @@ pie title Minerals in my tap water
             { title: "Back",    icon: "chevron-left",  toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30,  scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
             { title: "Num",     icon:"", toolTip: "Frame # (click to toggle edit mode)",btnType: ButtonType.TextButton,          expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
             { title: "Fwd",     icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30,  scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
+            
+            
+            
             { title: "Text",    icon: "Text",          toolTip: "Text functions",       subMenu: CurrentUserUtils.textTools(),   expertMode: false, toolType:DocumentType.RTF,            funcs: {                                                                             linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Ink",     icon: "Ink",           toolTip: "Ink functions",        subMenu: CurrentUserUtils.inkTools(),    expertMode: false, toolType:DocumentType.INK,            funcs: {hidden: `IsExploreMode()`,                                                   linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
             { title: "Doc",     icon: "Doc",           toolTip: "Freeform Doc tools",   subMenu: CurrentUserUtils.freeTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode, true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
@@ -863,6 +867,13 @@ pie title Minerals in my tap water
             // { title: "Visibility", icon:"Visibility",     toolTip:"Filter stars", width: 80, subMenu: this.cardGroupTools("star"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("star", true)`, width: 100, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, 
             // // { title: "Custom",  icon:"cloud", toolTip:"Add Idea labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"idea",                  funcs: {hidden:`showFreeform ("idea", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             // { title: "Visibility", icon:"Visibility",     toolTip:"Filter ideas", width: 80, subMenu: this.cardGroupTools("cloud"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("idea", true)`,width: 100, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
+            
+            { title: "Card",    icon: "Card",          toolTip: "Card View Tools",            subMenu: CurrentUserUtils.cardTools(),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+            // { title: "Create",   icon: "Create",         toolTip: "Assign card labels",   subMenu: CurrentUserUtils.labelTools(),  expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+            { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
+            { title: "Video",   icon: "Video",         toolTip: "Video functions",      subMenu: CurrentUserUtils.videoTools(),  expertMode: false, toolType:DocumentType.VID,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected
+            { title: "Image",   icon: "Image",         toolTip: "Image functions",      subMenu: CurrentUserUtils.imageTools(),  expertMode: false, toolType:DocumentType.IMG,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected
+            { title: "Schema",  icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Schema is selected
             { title: "Filter", icon:"=",     toolTip:"Filter cards by tags", btnType: ButtonType.MultiToggleButton, width: 150, ignoreClick: true,toolType:DocumentType.COL, 
             subMenu: this.tagGroupTools(), funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, 
             // [
@@ -873,13 +884,8 @@ pie title Minerals in my tap water
 
             // ]
         },
-            { title: "Card",    icon: "Card",          toolTip: "Card View Tools",            subMenu: CurrentUserUtils.cardTools(),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
-            // { title: "Create",   icon: "Create",         toolTip: "Assign card labels",   subMenu: CurrentUserUtils.labelTools(),  expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
-            { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
-            { title: "Video",   icon: "Video",         toolTip: "Video functions",      subMenu: CurrentUserUtils.videoTools(),  expertMode: false, toolType:DocumentType.VID,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected
-            { title: "Image",   icon: "Image",         toolTip: "Image functions",      subMenu: CurrentUserUtils.imageTools(),  expertMode: false, toolType:DocumentType.IMG,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected
-            { title: "Schema",  icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Schema is selected
            ];
+           
     }
 
     /// initializes a context menu button for the top bar context menu
diff --git a/src/client/views/FilterPanel.scss b/src/client/views/FilterPanel.scss
index 0b9076f3b..34c3d2fc1 100644
--- a/src/client/views/FilterPanel.scss
+++ b/src/client/views/FilterPanel.scss
@@ -283,6 +283,8 @@
     .hotkey-title{
         top: 6px;
         position: relative;
+        cursor: text;
+
     }
 
     .hotkey-title-input{
@@ -290,6 +292,8 @@
         border: none;
         border-color: transparent;
         outline: none;
+        cursor: text;
+
     }
 }
 
@@ -344,6 +348,20 @@
 
 
 
+}
+
+.drawing-box{
+    position: absolute;
+    z-index: 10000;
+    border-color: black;
+    border-style: solid;
+    border-width: medium;
+    border-radius: 10%;
+    background-color: #323232;
+
+
+
+
 }
 // .sliderBox-outerDiv {
 //     width: 30%;// width: calc(100% - 14px);   // 14px accounts for handles that are at the max value of the slider that would extend outside the box
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index 17c5cb173..ca4e4df84 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -51,10 +51,13 @@ interface filterProps {
 @observer
 export class FilterPanel extends ObservableReactComponent<filterProps> {
     @observable _selectedFacetHeaders = new Set<string>();
+    public static Instance: FilterPanel; 
+
 
     constructor(props: any) {
         super(props);
         makeObservable(this);
+        FilterPanel.Instance = this;
     }
 
     /**
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 62b79dd5a..a117ff3c3 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -447,13 +447,13 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
                     }),
                     icon: 'paint-brush',
                 });
-                cm?.addItem({
-                    description: 'Create a hotkey',
-                    event: action(() => {
-                        InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton;
-                    }),
-                    icon: 'satellite',
-                });
+                // cm?.addItem({
+                //     description: 'Create a hotkey',
+                //     event: action(() => {
+                //         InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton;
+                //     }),
+                //     icon: 'satellite',
+                // });
             },
         };
         return (
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 52f01e239..5dafb1088 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -28,6 +28,8 @@ 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';
 
 enum cardSortings {
     Time = 'time',
@@ -35,6 +37,7 @@ enum cardSortings {
     Color = 'color',
     Custom = 'custom',
     Chat = 'chat',
+    Tag = 'tag',
     None = '',
 }
 @observer
@@ -91,7 +94,9 @@ export class CollectionCardView extends CollectionSubView() {
     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) => this.processGptOutput(sortResult);
+        GPTPopup.Instance.onSortComplete = (sortResult: string, questionType: string, tag?: string) => this.processGptOutput(sortResult, questionType, tag);
+        GPTPopup.Instance.onQuizRandom = () => this.quizMode()
+
     };
 
     componentDidMount(): void {
@@ -103,9 +108,17 @@ export class CollectionCardView extends CollectionSubView() {
         });
 
         
+        // Reaction to cardSort changes
         this._disposers.sort = reaction(
-            () => ({ cardSort: this.cardSort }),
-            ({ cardSort}) => (cardSort === cardSortings.Chat ? this.openChatPopup() : GPTPopup.Instance.setVisible(false))
+            () => this.cardSort,
+            (cardSort) => {
+                if (cardSort === cardSortings.Chat) {
+                    this.openChatPopup();
+                } else {
+                    GPTPopup.Instance.setVisible(false);
+                }
+                // Force re-render to see if this resolves the sorting issue
+            }
         );
 
        
@@ -121,6 +134,7 @@ 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;
     }
     
@@ -151,6 +165,16 @@ export class CollectionCardView extends CollectionSubView() {
         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)
+
+    }
+
    
 
     /**
@@ -290,13 +314,48 @@ export class CollectionCardView extends CollectionSubView() {
     };
 
 
-     @computed get sortedDocs() {
+    get sortedDocs() {
         // console.log("hi hi hi")
         // console.log(this.layoutDoc.cardSort_isDesc + "layoutdoc desc")
-        console.log(this.cardSort + "card sort")
+        // console.log(this.cardSort + "card sort")
   
-        return this.sort(this.childDocsWithoutLinks, this.cardSort, BoolCast(this.layoutDoc.cardSort_isDesc), this._docDraggedIndex);
+        return this.sort(this.childDocsWithoutLinks, this.cardSort, BoolCast(this.Document.cardSort_isDesc), this._docDraggedIndex);
     }
+
+    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 === '') {
+                    base = i.toString(); // First active tag becomes the base
+                } else {
+                    fraction += i.toString(); // Subsequent active tags become part of the fraction
+                }
+            }
+        }
+    
+        // 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
      * front, latter cards to the back
@@ -305,7 +364,8 @@ export class CollectionCardView extends CollectionSubView() {
      * @param isDesc
      * @returns
      */
-    sort = (docs: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => {
+    @action sort = (docs: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => {
+        // console.log('hiiii')
         // if (sortType === cardSortings.None) return docs;
         
         // if(sortType !== cardSortings.None){
@@ -319,12 +379,13 @@ export class CollectionCardView extends CollectionSubView() {
                     case cardSortings.Color: 
                         return [ClientUtils.hexToHsv(StrCast(docA.backgroundColor)),
                                 ClientUtils.hexToHsv(StrCast(docB.backgroundColor))];
-                    case cardSortings.Custom:
-                        return [CollectionCardView.getButtonGroup(this.cardSort_customField, docA) ?? 9999,
-                                CollectionCardView.getButtonGroup(this.cardSort_customField, docB) ?? 9999];
+                    case cardSortings.Tag:
+                        return [this.tagValue(docA) ?? 9999,
+                                this.tagValue(docB) ?? 9999];
                     case cardSortings.Chat:
-                        return [NumCast(docA.chat) ?? 9999,
-                                NumCast(docB.chat) ?? 9999]
+                        return [NumCast(docA.chatIndex) ?? 9999,
+                                NumCast(docB.chatIndex) ?? 9999]
+                    
 
                     default:
                         return [StrCast(docA.type), StrCast(docB.type)]
@@ -487,12 +548,19 @@ 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 ~~~~~~
      * @param gptOutput
      */
-    @action processGptOutput = (gptOutput: string) => {
+    @action processGptOutput = (gptOutput: string, questionType: string, tag?: string) => {
 
         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'){
+            this.childDocs.forEach(d => {
+                d['chatFilter'] = false
+            })
+        }
         // console.log(listItems + " LISTT");
     
         // Debug: print the map contents
@@ -509,10 +577,47 @@ export class CollectionCardView extends CollectionSubView() {
             // console.log("ITEM:", normalizedItem);
     
             if (doc) {
-                doc.chat = index;
+                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
+                                }
+                            }
+
+                            if (myTag != ''){
+                                doc[myTag] = true
+                            }         
+                        }
+                        break
+                    case '2':
+                    case '4':
+                        doc['chatFilter'] = true
+                        Doc.setDocFilter(DocCast(this.Document.embedContainer), 'chatFilter', true, 'match');
+                        break
+
+
+                
+                }
             } else {
                 console.warn(`No matching document found for item: ${normalizedItem}`);
             }
+
         });
 
     }
@@ -528,100 +633,20 @@ export class CollectionCardView extends CollectionSubView() {
         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) => this.processGptOutput(sortResult);
-    };
-
-    /**
-     * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups
-     * @param doc
-     * @param cardSort
-     * @returns
-     */
-    renderButtons = (doc: Doc, cardSort: cardSortings): JSX.Element | null => {
-        // if (cardSort !== cardSortings.Custom) return null;
-
-        const amButtons = 4
-
-        // const amButtons = Math.max(
-        //     4,
-        //     this.childDocs?.reduce((set, d) => {
-        //         if (this.cardSort_customField) {
-        //             set.add(NumCast(d[this.cardSort_customField]));
-        //         }
-        //         return set;
-        //     }, new Set<number>()).size ?? 0
-        // );
-
-        // const activeButtonIndex = CollectionCardView.getButtonGroup(this.cardSort_customField, doc);
-
-        const totalWidth = amButtons * 72 + amButtons * 2 * 5 + 6;
-
-        const iconMap: { [key: number]: any } = {
-            0: 'star',
-            1: 'heart',
-            2: 'cloud',
-            3: 'bolt'
-        };
-
-        return (
-            <div className="card-button-container" style={{ width: `${totalWidth}px`, fontSize: '50px' }}>
-                {numberRange(amButtons).map(i => (
-                    <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from the {iconMap[i]} group</div>}>
-                        <button type="button" onClick={() => this.toggleButton(doc, iconMap[i] )}>
-                            {this.getButtonIcon(doc, iconMap[i])}
-                        </button>
-                    </Tooltip>
-                ))}
-            </div>
-        );
-    };
-
-      /**
-     * Toggles the buttons between on and off when creating custom sort groupings/changing those created by gpt
-     * @param childPairIndex
-     * @param buttonID
-     * @param doc
-     */
-      toggleButton = undoable((doc: Doc, icon: string) => {
-
-
-
-        // this.cardSort_customField && (doc[this.cardSort_customField] = buttonID);
-
-        // doc.cardSort_activeIcons = new List<string>()
-
-
-        // const list = StrListCast(doc.cardSort_activeIcons);
-        // doc.cardSort_activeIcons = new List<string>(list.includes(icon) ? list.filter(d => d !== icon) : [...list, icon]);
-
-        BoolCast(doc[icon]) ? doc[icon] = false : doc[icon] = true
-
-
-
-        // StrListCast(doc.cardSort_activeIcons).push(iconMap[buttonID])
-    }, 'toggle card tag');
-
-
-    getButtonIcon = (doc: Doc, icon: any): JSX.Element => {
-
-        // const isActive = StrListCast(doc.cardSort_activeIcons).includes(icon)
-        const isActive = doc[icon]
-
-        // console.log(StrListCast(doc.cardSort_activeIcons))
-        const color = isActive ? '#4476f7' : '#323232';
-
-        return <FontAwesomeIcon icon={icon} size="lg" style={{ color }} />;
+        GPTPopup.Instance.onSortComplete = (sortResult: string, questionType: string, tag?: string) => this.processGptOutput(sortResult, questionType, tag);
+        GPTPopup.Instance.onQuizRandom = () => this.quizMode()
     };
 
     /**
      * 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")
+        // console.log(this.childDocsWithoutLinks.length + "length")
 
         if (isEmpty) {
             return (
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index a48e3f9d9..1efcd84e4 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -146,7 +146,7 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
     }
 
     // prettier-ignore
-    const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'chat' | 'up' | 'down' | 'pile' | 'toggle-chat', 
+    const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'chat' | 'up' | 'down' | 'pile' | 'toggle-chat' | 'tag', 
     { 
         waitForRender?: boolean; 
         checkResult: (doc: Doc) => any; 
@@ -193,6 +193,10 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
             checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "color",
             setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort === "color" ? doc.cardSort = '' : doc.cardSort = 'color',
         }],
+        ['tag', {
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "tag",
+            setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort === "tag" ? doc.cardSort = '' : doc.cardSort = 'tag',
+        }],
         // ['heart', {
         //     checkResult: (doc: Doc) => isAttrFiltered('heart'),
         //     setDoc: (doc: Doc, dv: DocumentView) => {
@@ -221,16 +225,16 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
 
         //     }
         // }],
-        ['chat', {
-            checkResult: (doc: Doc) => {
+        // ['chat', {
+        //     checkResult: (doc: Doc) => {
                 
-                if (StrCast(doc?.cardSort) === "chat"){
-                    return true
-                }} ,
-            setDoc: (doc: Doc, dv: DocumentView) => {
-                doc.cardSort === "chat" ? doc.cardSort = '' : doc.cardSort = 'chat';
-            },
-        }],
+        //         if (StrCast(doc?.cardSort) === "chat"){
+        //             return true
+        //         }} ,
+        //     setDoc: (doc: Doc, dv: DocumentView) => {
+        //         doc.cardSort === "chat" ? doc.cardSort = '' : doc.cardSort = 'chat';
+        //     },
+        // }],
         ['up', {
             checkResult: (doc: Doc) => BoolCast(!doc?.cardSort_isDesc),
             setDoc: (doc: Doc, dv: DocumentView) => {
@@ -248,6 +252,8 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
             setDoc: (doc: Doc, dv: DocumentView) => {
                 GPTPopup.Instance.setVisible(!GPTPopup.Instance.visible);
                 GPTPopup.Instance.setMode(GPTPopupMode.SORT);
+                doc.cardSort === "chat" ? doc.cardSort = '' : doc.cardSort = 'chat';
+
 
             },
         }],
diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx
index c72d48ef5..8aa6bff2b 100644
--- a/src/client/views/nodes/IconTagBox.tsx
+++ b/src/client/views/nodes/IconTagBox.tsx
@@ -26,6 +26,9 @@ import { setupMoveUpEvents } from "../../../ClientUtils";
 import { returnFalse } from "../../../ClientUtils";
 import { emptyFunction } from "../../../Utils";
 import { CollectionViewType } from "../../documents/DocumentTypes";
+import { SnappingManager } from "../../util/SnappingManager";
+import { MainView } from "../MainView";
+import { PropertiesView } from "../PropertiesView";
 
 
 export interface IconTagProps {
@@ -129,7 +132,7 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
     renderButtons = (doc: Doc): JSX.Element | null => {
         // if (cardSort !== cardSortings.Custom) return null;
 
-        const amButtons = StrListCast(Doc.UserDoc().myFilterHotKeyTitles).length
+        const amButtons = (StrListCast(Doc.UserDoc().myFilterHotKeyTitles).length) + 1
 
         const keys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles)
 
@@ -146,7 +149,7 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
 
         // const activeButtonIndex = CollectionCardView.getButtonGroup(this.cardSort_customField, doc);
 
-        const totalWidth = amButtons * 35 + amButtons * 2 * 5 + 6;
+        const totalWidth = (amButtons -1) * 35 + (amButtons -1) * 2 * 5 + 6;
 
         const iconMap =  (buttonID: number)  => {
             return StrCast(Doc.UserDoc()[keys[buttonID]])
@@ -169,17 +172,31 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
                 fontSize: '50px'
             }}
             >           
-                {numberRange(amButtons).map(i => (
+                {numberRange(amButtons-1).map(i => (
                     <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from the {iconMap(i)} group</div>}>
                         <button key = {i} type="button" onClick={() => this.toggleButton(doc, iconMap(i) )}>
                             {this.getButtonIcon(doc, iconMap(i))}
                         </button>
                     </Tooltip>
                 ))}
+
+                {/* <Tooltip title={<div className="dash-tooltip">Click to customize these icons</div>}>
+                        <button type="button" onClick={() => this.openHotKeyMenu() }>
+                            <FontAwesomeIcon icon='gear' style={{ color: '#17438a', height: '30px', width: '30px'}} />
+                        </button>
+                </Tooltip> */}
             </div>
         );
     };
 
+    openHotKeyMenu = () => {
+        SnappingManager.PropertiesWidth < 5 && SnappingManager.SetPropertiesWidth(0);
+        SnappingManager.SetPropertiesWidth(MainView.Instance.propertiesWidth() < 15 ? Math.min(MainView.Instance._dashUIWidth - 50, 250) : 0);
+
+        PropertiesView.Instance.CloseAll()
+        PropertiesView.Instance.openFilters = true
+    }
+
       /**
      * Toggles the buttons between on and off when creating custom sort groupings/changing those created by gpt
      * @param childPairIndex
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index eaa3eaebf..1defd1a7f 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -78,15 +78,63 @@ $highlightedText: #82e0ff;
         align-items: center;
         flex-direction: column;
 
-        transform: translateY(30px);
+        .inputWrapper{
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            height: 60px;
+            position: absolute;
+            bottom: 0;
+            width: 100%;
+            background-color: white;
+
 
+        }
 
         .searchBox-input{
-            transform: translateY(-15px);
-            height: 50px;
+            height: 40px;
             border-radius: 10px;
+            position: absolute;
+            bottom: 10px;
             border-color: #5b97ff;
+            width: 90%
+        }
+
+        .chat-wrapper {
+            display: flex;
+            flex-direction: column;
+            width: 100%;
+            max-height: calc(100vh - 80px); /* Height minus the input box and some padding */
+            overflow-y: auto;
+            padding-bottom: 60px; /* Space for the input */
+        }
+    
+        .chat-bubbles {
+            margin-top: 20px;
+            display: flex;
+            flex-direction: column;
+            flex-grow: 1;
+        }
+    
+        .chat-bubble {
+            padding: 10px;
+            margin-bottom: 10px;
+            border-radius: 10px;
+            max-width: 60%;
         }
+    
+        .user-message {
+            background-color: #283d53;
+            align-self: flex-end;
+            color: whitesmoke;
+        }
+    
+        .chat-message {
+            background-color: #367ae7;
+            align-self: flex-start;
+            color:whitesmoke;
+        }
+
 
         
 
@@ -98,6 +146,8 @@ $highlightedText: #82e0ff;
         
 
         
+
+        
     }
 
     // button {
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index d216cc421..1b44508d7 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -34,6 +34,13 @@ export enum GPTPopupMode {
     QUIZ
 }
 
+export enum GPTQuizType {
+    CURRENT = 0,
+    CHOOSE = 1,
+    MULTIPLE = 2
+
+}
+
 
 
 
@@ -151,7 +158,8 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         this.sortDesc = t;
     };
 
-    @observable onSortComplete?: (sortResult: string) => void;
+    @observable onSortComplete?: (sortResult: string, questionType: string, tag?: string) => void;
+    @observable onQuizRandom?: () => void;
     @observable cardsDoneLoading = false;
 
     @action setCardsDoneLoading(done: boolean) {
@@ -165,6 +173,9 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         this.sortRespText = resp
     }
 
+    //mode where gpt says to select a specific card
+    //
+
 
     @observable chatSortPrompt: string = ""
 
@@ -179,39 +190,65 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         this.quizAnswer = e.target.value;
     });
 
+   
+    @observable correctAnswer: string = ''
+    @observable setCorrectAnswer = (s: string) => {
+        this.correctAnswer = s
+    }
+
     generateQuiz = async () => {
         this.setLoading(true);
         this.setSortDone(false);
 
+        const quizType = this.quizMode;
 
         const selected = DocumentView.SelectedDocs().lastElement();
 
-        const questionText = 'Question: ' + StrCast(selected['gptInputText']);
 
-        if (StrCast(selected['gptRubric']) === '') {
-            const rubricText = 'Rubric: ' + await this.generateRubric(StrCast(selected['gptInputText']), selected)
-        }
+                const questionText = 'Question: ' + StrCast(selected['gptInputText']);
 
-        const rubricText = 'Rubric: ' + (StrCast(selected['gptRubric']))
-        // const rubricText = 'Rubric: ' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text);
-        const queryText = questionText + ' UserAnswer: ' + this.quizAnswer + '. ' + 'Rubric' + rubricText;
+                if (StrCast(selected['gptRubric']) === '') {
+                    const rubricText = 'Rubric: ' + await this.generateRubric(StrCast(selected['gptInputText']), selected)
+                }
 
-        try {
-            const res = await gptAPICall(queryText, GPTCallType.QUIZ);
-            if (!res) {
-                console.error('GPT call failed');
-                return;
-            }
-            console.log(res)
-            this.setQuizResp(res)
+                const rubricText = 'Rubric: ' + (StrCast(selected['gptRubric']))
+                // const rubricText = 'Rubric: ' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text);
+                const queryText = questionText + ' UserAnswer: ' + this.quizAnswer + '. ' + 'Rubric' + rubricText;
 
-            this.setLoading(false);
-            this.setSortDone(true);
+                try {
+                    const res = await gptAPICall(queryText, GPTCallType.QUIZ);
+                    if (!res) {
+                        console.error('GPT call failed');
+                        return;
+                    }
+                    console.log(res)
+                    this.setQuizResp(res)
+                    this.conversationArray.push(res)
+
+                    this.setLoading(false);
+                    this.setSortDone(true);
+
+                    // this._outputValue = res;
+                } catch (err) {
+                    console.error('GPT call failed');
+                }
+
+
+                if (this.onQuizRandom){
+                    this.onQuizRandom()
+                }
 
-            // this._outputValue = res;
-        } catch (err) {
-            console.error('GPT call failed');
-        }
+
+            
+
+
+        // switch(quizType){
+        //     default:
+                
+        // }
+
+
+        
 
 
     }
@@ -254,11 +291,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
 
     }
 
-
-    /**
-     * Sorts cards in the CollectionCardDeckView
-     */
-    generateSort = async () => {
+    generateCard = async () => {
         console.log(this.chatSortPrompt + "USER PROMPT")
         this.setLoading(true);
         this.setSortDone(false);
@@ -268,17 +301,50 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         }
     
         try {
-            const res = await gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt);
+            // const res = await gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt);
+            const questionType = await gptAPICall(this.chatSortPrompt, GPTCallType.TYPE);
+            const questionNumber = questionType.split(' ')[0]
+            console.log(questionType)
+            let res = ''
+
+            switch (questionNumber) {
+                case '1':
+                case '2':
+                case '4':
+                    res = await gptAPICall(this.sortDesc, GPTCallType.SUBSET, this.chatSortPrompt);
+                    break
+                case '6':
+                    res = await gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt);
+                    break
+                default:
+
+                    const selected = DocumentView.SelectedDocs().lastElement();
+                    const questionText = StrCast(selected!['gptInputText']);
+
+
+                    res = await gptAPICall(questionText, GPTCallType.INFO, this.chatSortPrompt);
+                    break
+            }
+
+
+            // const res = await gptAPICall(this.sortDesc, GPTCallType.SUBSET, this.chatSortPrompt);
             // Trigger the callback with the result
             if (this.onSortComplete) {
-                this.onSortComplete(res || 'Something went wrong :(');
+                this.onSortComplete(res || 'Something went wrong :(', questionNumber, questionType.split(' ').slice(1).join(' '));
+
+                let explanation = res
+
+                if (questionType != '5' && questionType != '3'){
     
                 // Extract explanation surrounded by ------ at the top or both at the top and bottom
                 const explanationMatch = res.match(/------\s*([\s\S]*?)\s*(?:------|$)/) || [];
-                const explanation = explanationMatch[1] ? explanationMatch[1].trim() : 'No explanation found';
+                explanation = explanationMatch[1] ? explanationMatch[1].trim() : 'No explanation found';
+            } 
     
                 // Set the extracted explanation to sortRespText
                 this.setSortRespText(explanation);
+                this.conversationArray.push(this.sortRespText)
+                this.scrollToBottom()
     
                 console.log(res);
             }
@@ -289,6 +355,43 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         this.setLoading(false);
         this.setSortDone(true);
     };
+
+
+    // /**
+    //  * Sorts cards in the CollectionCardDeckView
+    //  */
+    // generateSort = async () => {
+    //     console.log(this.chatSortPrompt + "USER PROMPT")
+    //     this.setLoading(true);
+    //     this.setSortDone(false);
+
+    //     if (this.regenerateCallback) {
+    //         await this.regenerateCallback();
+    //     }
+    
+    //     try {
+    //         const res = await gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt);
+
+    //         // Trigger the callback with the result
+    //         if (this.onSortComplete) {
+    //             this.onSortComplete(res || 'Something went wrong :(');
+    
+    //             // Extract explanation surrounded by ------ at the top or both at the top and bottom
+    //             const explanationMatch = res.match(/------\s*([\s\S]*?)\s*(?:------|$)/) || [];
+    //             const explanation = explanationMatch[1] ? explanationMatch[1].trim() : 'No explanation found';
+    
+    //             // Set the extracted explanation to sortRespText
+    //             this.setSortRespText(explanation);
+    
+    //             console.log(res);
+    //         }
+    //     } catch (err) {
+    //         console.error(err);
+    //     }
+    
+    //     this.setLoading(false);
+    //     this.setSortDone(true);
+    // };
     
     
     /**
@@ -422,19 +525,40 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         super(props);
         makeObservable(this);
         GPTPopup.Instance = this;
+        this.messagesEndRef = React.createRef();
+
     }
 
+    private messagesEndRef: React.RefObject<HTMLDivElement>;
+
+
+    scrollToBottom = () => {
+            setTimeout(() => {
+                // Code to execute after 1 second (1000 ms)
+                if (this.messagesEndRef.current) {
+
+                this.messagesEndRef.current.scrollIntoView({ behavior: 'smooth', block: 'end' });
+                }
+
+              }, 50);
+      }
+
     componentDidUpdate = () => {
         if (this.loading) {
             this.setDone(false);
         }
     };
 
+    @observable quizMode : GPTQuizType = GPTQuizType.CURRENT
+    @action setQuizMode (g: GPTQuizType) {
+        this.quizMode = g
+    }
+
     cardMenu = () => (
         <div className="btns-wrapper-gpt">
             <Button
-                tooltip="Have ChatGPT sort your cards for you!"
-                text="Sort Cards!"
+                tooltip="Have ChatGPT sort, tag, define, or filter your cards for you!"
+                text="Modify/Sort Cards!"
                 onClick={() => this.setMode(GPTPopupMode.SORT)}
                 color={StrCast(Doc.UserDoc().userVariantColor)}
                 type={Type.TERT}
@@ -450,7 +574,16 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
             <Button
                 tooltip="Test your knowledge with ChatGPT!"
                 text="Quiz Cards!"
-                onClick={() => this.setMode(GPTPopupMode.QUIZ)}
+                onClick={() => {
+                    this.conversationArray = ['Define the selected card!']
+                    this.setMode(GPTPopupMode.QUIZ)
+                    if (this.onQuizRandom){
+                        this.onQuizRandom()
+                    }
+                    
+
+
+                }}
                 color={StrCast(Doc.UserDoc().userVariantColor)}
                 type={Type.TERT}
                 style={{
@@ -464,62 +597,94 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
             />
         </div>
     )
+
+    @observable conversationArray: string[] = ["Hi! In this pop up, you can ask ChatGPT questions about your documents and filter / sort them. If you're not sure if something is possible-- just ask!"]
+
+
+    handleKeyPress = async (e: React.KeyboardEvent, isSort: boolean) => {
+        if (e.key === 'Enter') {
+            e.stopPropagation();
+
+            if (isSort) {
+                this.conversationArray.push(this.chatSortPrompt);
+                await this.generateCard();
+                this.chatSortPrompt = ''
+
+            } else {
+                this.conversationArray.push(this.quizAnswer);
+                await this.generateQuiz();
+                this.quizAnswer = ''
+
+            }
+
+            this.scrollToBottom();
+        }
+    }
     
     cardActual = (opt: GPTPopupMode) => {
         const isSort = opt === GPTPopupMode.SORT
         // if (opt === GPTPopupMode.SORT) {
             return (
-                !this.sortDone ? (
-                    <>
-                        <div className="btns-wrapper-gpt">
+                // !this.sortDone ? (
+                    // <>
+                    <div className="btns-wrapper-gpt">
+                        {/* Chat bubble container with scroll */}
+                        <div className="chat-wrapper">
+                            <div className="chat-bubbles">
+                                {this.conversationArray.map((message, index) => (
+                                    <div
+                                        key={index}
+                                        className={`chat-bubble ${index % 2 === 1 ? 'user-message' : 'chat-message'}`}
+                                    >
+                                        {message}
+                                    </div>
+                                ))}
+
+                                {/* Conditional Loading message */}
+                                {(!this.cardsDoneLoading || this.loading) && (
+                                    <div className={`chat-bubble chat-message`}>
+                                       ...
+                                    </div>
+                                )}
+                            </div>
+
+                            <div ref={this.messagesEndRef} style= {{height: '100px'}} />
+
+                        </div>
+
+                        <div className="inputWrapper">
                             <input
                                 className="searchBox-input"
                                 defaultValue=""
                                 autoComplete="off"
                                 onChange={isSort ? this.sortPromptChanged : this.quizAnswerChanged}
-                                onKeyDown={e => {
-                                    if (e.key === 'Enter') {
-                                        isSort ? this.generateSort() : this.generateQuiz();
+                                onKeyDown={(e) => {
+                                    this.handleKeyPress(e, isSort)
+
+                                            
                                     }
-                                    e.stopPropagation();
-                                }}
+                                }
                                 type="text"
-                                placeholder={`${isSort ? 'How do you want to sort your cards?' : 'Define the selected card?'}`}
-                                id="search-input"
-                                style={{ width: '100%' }}
-                            />
-                        {/* </div>
-                        <div className="btns-wrapper-gpt"> */}
-                            <Button
-                                tooltip={`${isSort ? 'HaveChatGPT sort ypur cards for you!' : 'See how close you get to the right answer!'}`}
-                                text={`${isSort ? 'Sort!' : 'Check!'}`}
-                                onClick={isSort ? this.generateSort : this.generateQuiz}
-                                color={StrCast(Doc.UserDoc().userVariantColor)}
-                                type={Type.TERT}
-                                style={{
-                                    width: '100%',
-                                    textAlign: 'center',
-                                    color: '#ffffff',
-                                    fontSize: '16px',
-                                    height: '40%',
-
-                                }}
-                            />
-                        </div>
-                    </>
-                ) : (
-                    <div>
-                        <div className="content-wrapper">
-                            <p>{this.text === 'Something went wrong :(' ? 'Something went wrong :(' : `${isSort ? this.sortRespText : this.quizRespText}`}</p>
-                            <IconButton
-                                tooltip="Generate Again"
-                                onClick={() => this.setSortDone(false)}
-                                icon={<FontAwesomeIcon icon="redo-alt" size="lg" />}
-                                color={StrCast(Doc.UserDoc().userVariantColor)}
+                                placeholder={`${isSort ? 'Have ChatGPT sort, tag, define, or filter your cards for you!' : 'Define the selected card!'}`}
                             />
                         </div>
                     </div>
-                )
+
+
+                    // </>
+                // ) : (
+                //     <div>
+                //         <div className="content-wrapper">
+                //             <p>{this.text === 'Something went wrong :(' ? 'Something went wrong :(' : `${isSort ? this.sortRespText : this.quizRespText}`}</p>
+                //             <IconButton
+                //                 tooltip="Generate Again"
+                //                 onClick={() => this.setSortDone(false)}
+                //                 icon={<FontAwesomeIcon icon="redo-alt" size="lg" />}
+                //                 color={StrCast(Doc.UserDoc().userVariantColor)}
+                //             />
+                //         </div>
+                //     </div>
+                // )
             );
         // } else if (opt === GPTPopupMode.QUIZ) {
         //     return (
@@ -563,10 +728,10 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     };
     
     sortBox = () => (
-        <div>
+        <div style = {{height: '80%'}}>
             {this.heading(this.mode === GPTPopupMode.SORT ? 'SORTING' : 'QUIZ')}
             <>
-                {!this.cardsDoneLoading || this.loading ? (
+                {!this.cardsDoneLoading? (
                     <div className="content-wrapper">
                         <div className="loading-spinner">
                             <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} />
@@ -737,7 +902,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
                             tooltip="back"
                             icon={<CgCornerUpLeft size="16px" />}
                             onClick={() => this.mode = GPTPopupMode.CARD}
-                            style = {{right: '-20%'}}
+                            style = {{right: '50px', position: 'absolute'}}
                         />
                     )}
                     <IconButton
-- 
cgit v1.2.3-70-g09d2


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 +++++++++------------
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |  95 ++----
 2 files changed, 168 insertions(+), 270 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>
         );
     }
-    
 }
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 2d8547dcd..13196f06d 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -52,6 +52,8 @@ interface GPTPopupProps {}
 export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     // eslint-disable-next-line no-use-before-define
     static Instance: GPTPopup;
+    private messagesEndRef: React.RefObject<HTMLDivElement>;
+
     @observable private chatMode: boolean = false;
     private correlatedColumns: string[] = [];
 
@@ -173,29 +175,25 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         this.sortRespText = resp
     }
 
-    //mode where gpt says to select a specific card
-    //
-
-
     @observable chatSortPrompt: string = ""
 
     sortPromptChanged = action((e: React.ChangeEvent<HTMLInputElement>) => {
         this.chatSortPrompt = e.target.value;
     });
 
-
     @observable quizAnswer: string = ""
 
     quizAnswerChanged = action((e: React.ChangeEvent<HTMLInputElement>) => {
         this.quizAnswer = e.target.value;
     });
 
-   
-    @observable correctAnswer: string = ''
-    @observable setCorrectAnswer = (s: string) => {
-        this.correctAnswer = s
-    }
+    @observable conversationArray: string[] = ["Hi! In this pop up, you can ask ChatGPT questions about your documents and filter / sort them. "]
+
 
+    /**
+     * When the cards are in quiz mode in the card view, allows gpt to determine whether the user's answer was correct
+     * @returns 
+     */
     generateQuiz = async () => {
         this.setLoading(true);
         this.setSortDone(false);
@@ -238,6 +236,12 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
 
     }
 
+    /**
+     * Generates a rubric by which to compare the user's answer to
+     * @param inputText user's answer
+     * @param doc the doc the user is providing info about
+     * @returns gpt's response
+     */
     generateRubric = async (inputText: string, doc:Doc) => {
         try {
             const res = await gptAPICall(inputText, GPTCallType.RUBRIC);            
@@ -253,6 +257,10 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
 
     @observable private regenerateCallback: (() => Promise<void>) | null = null;
 
+    /**
+     * Callback function that causes the card view to update the childpair string list
+     * @param callback 
+     */
     @action public setRegenerateCallback(callback: () => Promise<void>) {
         this.regenerateCallback = callback;
     }
@@ -271,6 +279,9 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
 
     }
 
+    /**
+     * Generates a response to the user's questoin depending on the type of their question
+     */
     generateCard = async () => {
         console.log(this.chatSortPrompt + "USER PROMPT")
         this.setLoading(true);
@@ -469,7 +480,6 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
 
     }
 
-    private messagesEndRef: React.RefObject<HTMLDivElement>;
 
 
     scrollToBottom = () => {
@@ -538,7 +548,6 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         </div>
     )
 
-    @observable conversationArray: string[] = ["Hi! In this pop up, you can ask ChatGPT questions about your documents and filter / sort them. If you're not sure if something is possible-- just ask!"]
 
 
     handleKeyPress = async (e: React.KeyboardEvent, isSort: boolean) => {
@@ -563,12 +572,9 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     
     cardActual = (opt: GPTPopupMode) => {
         const isSort = opt === GPTPopupMode.SORT
-        // if (opt === GPTPopupMode.SORT) {
             return (
-                // !this.sortDone ? (
-                    // <>
+
                     <div className="btns-wrapper-gpt">
-                        {/* Chat bubble container with scroll */}
                         <div className="chat-wrapper">
                             <div className="chat-bubbles">
                                 {this.conversationArray.map((message, index) => (
@@ -579,8 +585,6 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
                                         {message}
                                     </div>
                                 ))}
-
-                                {/* Conditional Loading message */}
                                 {(!this.cardsDoneLoading || this.loading) && (
                                     <div className={`chat-bubble chat-message`}>
                                        ...
@@ -610,62 +614,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
                             />
                         </div>
                     </div>
-
-
-                    // </>
-                // ) : (
-                //     <div>
-                //         <div className="content-wrapper">
-                //             <p>{this.text === 'Something went wrong :(' ? 'Something went wrong :(' : `${isSort ? this.sortRespText : this.quizRespText}`}</p>
-                //             <IconButton
-                //                 tooltip="Generate Again"
-                //                 onClick={() => this.setSortDone(false)}
-                //                 icon={<FontAwesomeIcon icon="redo-alt" size="lg" />}
-                //                 color={StrCast(Doc.UserDoc().userVariantColor)}
-                //             />
-                //         </div>
-                //     </div>
-                // )
             );
-        // } else if (opt === GPTPopupMode.QUIZ) {
-        //     return (
-        //         <>
-        //             <div className="btns-wrapper-gpt">
-        //                 <input
-        //                     className="searchBox-input"
-        //                     defaultValue=""
-        //                     autoComplete="off"
-        //                     onChange={this.quizAnswerChanged}
-        //                     onKeyDown={e => {
-        //                         if (e.key === 'Enter') {
-        //                             this.generateQuiz();
-        //                         }
-        //                         e.stopPropagation();
-        //                     }}
-        //                     type="text"
-        //                     placeholder="What is the selected card?"
-        //                     id="search-input"
-        //                     style={{ width: '100%' }}
-        //                 />
-        //             {/* </div>
-        //             <div className="btns-wrapper-gpt"> */}
-        //                 <Button
-        //                     tooltip="See how close you got to the right answer!"
-        //                     text="Check!"
-        //                     onClick={() => this.generateQuiz()}
-        //                     color={StrCast(Doc.UserDoc().userVariantColor)}
-        //                     type={Type.TERT}
-        //                     style={{
-        //                         width: '90%',
-        //                         textAlign: 'center',
-        //                         color: '#ffffff',
-        //                         fontSize: '16px',
-        //                     }}
-        //                 />
-        //             </div>
-        //         </>
-        //     );
-        // }
     };
     
     sortBox = () => (
-- 
cgit v1.2.3-70-g09d2


From 313b3d3e67689b175cdc85426ff6af809d476622 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Tue, 17 Sep 2024 12:55:38 -0400
Subject: done with comments-- pull request

---
 eslint.config.mjs                                  |  66 +-----
 package-lock.json                                  | 176 +++++++++------
 package.json                                       |  12 +-
 src/client/apis/gpt/GPT.ts                         |   8 +-
 src/client/documents/Documents.ts                  |   2 -
 src/client/util/CurrentUserUtils.ts                |  79 +------
 src/client/util/Scripting.ts                       |   4 +-
 src/client/views/FilterPanel.scss                  |  12 --
 src/client/views/FilterPanel.tsx                   | 145 +++++--------
 src/client/views/PropertiesView.tsx                |   2 +-
 src/client/views/StyleProvider.tsx                 |   6 +
 .../views/collections/CollectionCardDeckView.tsx   |  41 ++--
 src/client/views/global/globalScripts.ts           | 240 ++-------------------
 src/client/views/nodes/IconTagBox.tsx              | 141 ++----------
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |   5 +-
 src/typings/index.d.ts                             |   2 +-
 16 files changed, 255 insertions(+), 686 deletions(-)

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

diff --git a/eslint.config.mjs b/eslint.config.mjs
index 619966f20..c69209327 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -1,57 +1,13 @@
-import pluginJs from '@eslint/js';
-import pluginReactConfig from 'eslint-plugin-react/configs/recommended.js';
-import globals from 'globals';
-import tseslint from 'typescript-eslint';
+import globals from "globals";
+import pluginJs from "@eslint/js";
+import tseslint from "typescript-eslint";
+import pluginReact from "eslint-plugin-react";
 
-export default [
-    {
-        languageOptions: { globals: { ...globals.browser, ...globals.node } },
-    },
-    pluginJs.configs.recommended,
-    ...tseslint.configs.recommended,
-    {
-        rules: {
-            'node/no-missing-import': 0,
-            'no-console': 'off',
-            'func-names': 'off',
-            'no-process-exit': 'off',
-            'object-shorthand': 'off',
-            'class-methods-use-this': 'off',
-            'single-quote': 'off',
-            'max-classes-per-file': 0,
-
-            'react/jsx-filename-extension': [
-                2,
-                {
-                    extensions: ['.js', '.jsx', '.ts', '.tsx'],
-                },
-            ],
 
-            'import/prefer-default-export': 'off',
-            'no-unused-expressions': 'off',
-            '@typescript-eslint/no-unused-expressions': 'off',
-            'prefer-template': 'off',
-            'no-inner-declarations': 'off',
-            'no-plusplus': 'off',
-            'no-multi-assign': 'off',
-            'no-underscore-dangle': 'off',
-            'no-nested-ternary': 'off',
-            'lines-between-class-members': 'off',
-            'no-shadow': 'off',
-            '@typescript-eslint/no-shadow': 'warn',
-            'no-unused-vars': 'off',
-            '@typescript-eslint/no-unused-vars': 'error',
-            '@typescript-eslint/no-namespace': 'off',
-            'react/destructuring-assignment': 0,
-            'prefer-arrow-callback': 'error',
-            'no-return-assign': 'error',
-            'no-await-in-loop': 'error',
-            'no-loop-func': 'error',
-            'no-cond-assign': 'error',
-            'no-use-before-define': 'error',
-            'no-explicit-any': 'error',
-            'no-restricted-globals': ['error', 'event'],
-        },
-    },
-    pluginReactConfig,
-];
+export default [
+  {files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"]},
+  {languageOptions: { globals: globals.browser }},
+  pluginJs.configs.recommended,
+  ...tseslint.configs.recommended,
+  pluginReact.configs.flat.recommended,
+];
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 1e738fcb1..3c9c5c81b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -92,7 +92,7 @@
                 "D": "^1.0.0",
                 "d3": "^7.8.5",
                 "depcheck": "^1.4.7",
-                "dotenv": "^16.3.1",
+                "dotenv": "^16.4.5",
                 "eslint-webpack-plugin": "^4.1.0",
                 "exif": "^0.6.0",
                 "exifr": "^7.1.3",
@@ -130,6 +130,7 @@
                 "image-data-uri": "^2.0.1",
                 "image-size": "^1.0.2",
                 "image-size-stream": "^1.1.0",
+                "install": "^0.13.0",
                 "is-plain-obj": "^4.1.0",
                 "jimp": "^0.22.10",
                 "jpeg-autorotate": "^9.0.0",
@@ -154,7 +155,7 @@
                 "node-stream-zip": "^1.15.0",
                 "nodemailer": "^6.9.7",
                 "nodemon": "^3.0.2",
-                "npm": "^10.2.5",
+                "npm": "^10.8.1",
                 "openai": "^4.26.0",
                 "p-limit": "^6.1.0",
                 "passport": "^0.7.0",
@@ -188,6 +189,7 @@
                 "react-compound-slider": "^3.4.0",
                 "react-datepicker": "^7.3.0",
                 "react-dom": "^18.2.0",
+                "react-draggable": "^4.4.6",
                 "react-grid-layout": "^1.4.4",
                 "react-icons": "^5.0.1",
                 "react-jsx-parser": "^2.0.0",
@@ -246,7 +248,7 @@
                 "xregexp": "^5.1.1"
             },
             "devDependencies": {
-                "@eslint/js": "^9.1.1",
+                "@eslint/js": "^9.10.0",
                 "@types/adm-zip": "^0.5.5",
                 "@types/animejs": "^3.1.12",
                 "@types/archiver": "^6.0.2",
@@ -295,9 +297,9 @@
                 "@types/youtube": "^0.1.0",
                 "chai": "^5.0.0",
                 "cross-env": "^7.0.3",
-                "eslint": "^9.9.0",
-                "eslint-plugin-react": "^7.34.1",
-                "globals": "^15.1.0",
+                "eslint": "^9.10.0",
+                "eslint-plugin-react": "^7.36.1",
+                "globals": "^15.9.0",
                 "jsdom": "^25.0.0",
                 "mocha": "^10.2.0",
                 "prettier": "^3.1.0",
@@ -306,7 +308,7 @@
                 "ts-loader": "^9.5.1",
                 "ts-node": "^10.9.1",
                 "ts-node-dev": "^2.0.0",
-                "typescript-eslint": "^8.2.0",
+                "typescript-eslint": "^8.6.0",
                 "webpack-dev-server": "^5.0.4"
             },
             "engines": {
@@ -2675,9 +2677,10 @@
             }
         },
         "node_modules/@eslint/js": {
-            "version": "9.9.1",
-            "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.9.1.tgz",
-            "integrity": "sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==",
+            "version": "9.10.0",
+            "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz",
+            "integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==",
+            "license": "MIT",
             "engines": {
                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
             }
@@ -2690,6 +2693,17 @@
                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
             }
         },
+        "node_modules/@eslint/plugin-kit": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz",
+            "integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==",
+            "dependencies": {
+                "levn": "^0.4.1"
+            },
+            "engines": {
+                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+            }
+        },
         "node_modules/@ffmpeg/core": {
             "version": "0.12.6",
             "resolved": "https://registry.npmjs.org/@ffmpeg/core/-/core-0.12.6.tgz",
@@ -9993,16 +10007,17 @@
             "dev": true
         },
         "node_modules/@typescript-eslint/eslint-plugin": {
-            "version": "8.3.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.3.0.tgz",
-            "integrity": "sha512-FLAIn63G5KH+adZosDYiutqkOkYEx0nvcwNNfJAf+c7Ae/H35qWwTYvPZUKFj5AS+WfHG/WJJfWnDnyNUlp8UA==",
+            "version": "8.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz",
+            "integrity": "sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==",
             "dev": true,
+            "license": "MIT",
             "dependencies": {
                 "@eslint-community/regexpp": "^4.10.0",
-                "@typescript-eslint/scope-manager": "8.3.0",
-                "@typescript-eslint/type-utils": "8.3.0",
-                "@typescript-eslint/utils": "8.3.0",
-                "@typescript-eslint/visitor-keys": "8.3.0",
+                "@typescript-eslint/scope-manager": "8.6.0",
+                "@typescript-eslint/type-utils": "8.6.0",
+                "@typescript-eslint/utils": "8.6.0",
+                "@typescript-eslint/visitor-keys": "8.6.0",
                 "graphemer": "^1.4.0",
                 "ignore": "^5.3.1",
                 "natural-compare": "^1.4.0",
@@ -10026,15 +10041,16 @@
             }
         },
         "node_modules/@typescript-eslint/parser": {
-            "version": "8.3.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.3.0.tgz",
-            "integrity": "sha512-h53RhVyLu6AtpUzVCYLPhZGL5jzTD9fZL+SYf/+hYOx2bDkyQXztXSc4tbvKYHzfMXExMLiL9CWqJmVz6+78IQ==",
+            "version": "8.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.6.0.tgz",
+            "integrity": "sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow==",
             "dev": true,
+            "license": "BSD-2-Clause",
             "dependencies": {
-                "@typescript-eslint/scope-manager": "8.3.0",
-                "@typescript-eslint/types": "8.3.0",
-                "@typescript-eslint/typescript-estree": "8.3.0",
-                "@typescript-eslint/visitor-keys": "8.3.0",
+                "@typescript-eslint/scope-manager": "8.6.0",
+                "@typescript-eslint/types": "8.6.0",
+                "@typescript-eslint/typescript-estree": "8.6.0",
+                "@typescript-eslint/visitor-keys": "8.6.0",
                 "debug": "^4.3.4"
             },
             "engines": {
@@ -10054,13 +10070,14 @@
             }
         },
         "node_modules/@typescript-eslint/scope-manager": {
-            "version": "8.3.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.3.0.tgz",
-            "integrity": "sha512-mz2X8WcN2nVu5Hodku+IR8GgCOl4C0G/Z1ruaWN4dgec64kDBabuXyPAr+/RgJtumv8EEkqIzf3X2U5DUKB2eg==",
+            "version": "8.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.6.0.tgz",
+            "integrity": "sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==",
             "dev": true,
+            "license": "MIT",
             "dependencies": {
-                "@typescript-eslint/types": "8.3.0",
-                "@typescript-eslint/visitor-keys": "8.3.0"
+                "@typescript-eslint/types": "8.6.0",
+                "@typescript-eslint/visitor-keys": "8.6.0"
             },
             "engines": {
                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -10071,13 +10088,14 @@
             }
         },
         "node_modules/@typescript-eslint/type-utils": {
-            "version": "8.3.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.3.0.tgz",
-            "integrity": "sha512-wrV6qh//nLbfXZQoj32EXKmwHf4b7L+xXLrP3FZ0GOUU72gSvLjeWUl5J5Ue5IwRxIV1TfF73j/eaBapxx99Lg==",
+            "version": "8.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.6.0.tgz",
+            "integrity": "sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==",
             "dev": true,
+            "license": "MIT",
             "dependencies": {
-                "@typescript-eslint/typescript-estree": "8.3.0",
-                "@typescript-eslint/utils": "8.3.0",
+                "@typescript-eslint/typescript-estree": "8.6.0",
+                "@typescript-eslint/utils": "8.6.0",
                 "debug": "^4.3.4",
                 "ts-api-utils": "^1.3.0"
             },
@@ -10095,10 +10113,11 @@
             }
         },
         "node_modules/@typescript-eslint/types": {
-            "version": "8.3.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.3.0.tgz",
-            "integrity": "sha512-y6sSEeK+facMaAyixM36dQ5NVXTnKWunfD1Ft4xraYqxP0lC0POJmIaL/mw72CUMqjY9qfyVfXafMeaUj0noWw==",
+            "version": "8.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz",
+            "integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==",
             "dev": true,
+            "license": "MIT",
             "engines": {
                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
             },
@@ -10108,13 +10127,14 @@
             }
         },
         "node_modules/@typescript-eslint/typescript-estree": {
-            "version": "8.3.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.3.0.tgz",
-            "integrity": "sha512-Mq7FTHl0R36EmWlCJWojIC1qn/ZWo2YiWYc1XVtasJ7FIgjo0MVv9rZWXEE7IK2CGrtwe1dVOxWwqXUdNgfRCA==",
+            "version": "8.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.6.0.tgz",
+            "integrity": "sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==",
             "dev": true,
+            "license": "BSD-2-Clause",
             "dependencies": {
-                "@typescript-eslint/types": "8.3.0",
-                "@typescript-eslint/visitor-keys": "8.3.0",
+                "@typescript-eslint/types": "8.6.0",
+                "@typescript-eslint/visitor-keys": "8.6.0",
                 "debug": "^4.3.4",
                 "fast-glob": "^3.3.2",
                 "is-glob": "^4.0.3",
@@ -10140,6 +10160,7 @@
             "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
             "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
             "dev": true,
+            "license": "ISC",
             "dependencies": {
                 "brace-expansion": "^2.0.1"
             },
@@ -10155,6 +10176,7 @@
             "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
             "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
             "dev": true,
+            "license": "ISC",
             "bin": {
                 "semver": "bin/semver.js"
             },
@@ -10163,15 +10185,16 @@
             }
         },
         "node_modules/@typescript-eslint/utils": {
-            "version": "8.3.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.3.0.tgz",
-            "integrity": "sha512-F77WwqxIi/qGkIGOGXNBLV7nykwfjLsdauRB/DOFPdv6LTF3BHHkBpq81/b5iMPSF055oO2BiivDJV4ChvNtXA==",
+            "version": "8.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.6.0.tgz",
+            "integrity": "sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==",
             "dev": true,
+            "license": "MIT",
             "dependencies": {
                 "@eslint-community/eslint-utils": "^4.4.0",
-                "@typescript-eslint/scope-manager": "8.3.0",
-                "@typescript-eslint/types": "8.3.0",
-                "@typescript-eslint/typescript-estree": "8.3.0"
+                "@typescript-eslint/scope-manager": "8.6.0",
+                "@typescript-eslint/types": "8.6.0",
+                "@typescript-eslint/typescript-estree": "8.6.0"
             },
             "engines": {
                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -10185,12 +10208,13 @@
             }
         },
         "node_modules/@typescript-eslint/visitor-keys": {
-            "version": "8.3.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.3.0.tgz",
-            "integrity": "sha512-RmZwrTbQ9QveF15m/Cl28n0LXD6ea2CjkhH5rQ55ewz3H24w+AMCJHPVYaZ8/0HoG8Z3cLLFFycRXxeO2tz9FA==",
+            "version": "8.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.6.0.tgz",
+            "integrity": "sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==",
             "dev": true,
+            "license": "MIT",
             "dependencies": {
-                "@typescript-eslint/types": "8.3.0",
+                "@typescript-eslint/types": "8.6.0",
                 "eslint-visitor-keys": "^3.4.3"
             },
             "engines": {
@@ -10206,6 +10230,7 @@
             "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
             "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
             "dev": true,
+            "license": "Apache-2.0",
             "engines": {
                 "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
             },
@@ -17448,15 +17473,17 @@
             }
         },
         "node_modules/eslint": {
-            "version": "9.9.1",
-            "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.9.1.tgz",
-            "integrity": "sha512-dHvhrbfr4xFQ9/dq+jcVneZMyRYLjggWjk6RVsIiHsP8Rz6yZ8LvZ//iU4TrZF+SXWG+JkNF2OyiZRvzgRDqMg==",
+            "version": "9.10.0",
+            "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz",
+            "integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==",
+            "license": "MIT",
             "dependencies": {
                 "@eslint-community/eslint-utils": "^4.2.0",
                 "@eslint-community/regexpp": "^4.11.0",
                 "@eslint/config-array": "^0.18.0",
                 "@eslint/eslintrc": "^3.1.0",
-                "@eslint/js": "9.9.1",
+                "@eslint/js": "9.10.0",
+                "@eslint/plugin-kit": "^0.1.0",
                 "@humanwhocodes/module-importer": "^1.0.1",
                 "@humanwhocodes/retry": "^0.3.0",
                 "@nodelib/fs.walk": "^1.2.8",
@@ -17479,7 +17506,6 @@
                 "is-glob": "^4.0.0",
                 "is-path-inside": "^3.0.3",
                 "json-stable-stringify-without-jsonify": "^1.0.1",
-                "levn": "^0.4.1",
                 "lodash.merge": "^4.6.2",
                 "minimatch": "^3.1.2",
                 "natural-compare": "^1.4.0",
@@ -17506,10 +17532,11 @@
             }
         },
         "node_modules/eslint-plugin-react": {
-            "version": "7.35.0",
-            "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz",
-            "integrity": "sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==",
+            "version": "7.36.1",
+            "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.36.1.tgz",
+            "integrity": "sha512-/qwbqNXZoq+VP30s1d4Nc1C5GTxjJQjk4Jzs4Wq2qzxFM7dSmuG2UkIjg2USMLh3A/aVcUNrK7v0J5U1XEGGwA==",
             "dev": true,
+            "license": "MIT",
             "dependencies": {
                 "array-includes": "^3.1.8",
                 "array.prototype.findlast": "^1.2.5",
@@ -18101,6 +18128,7 @@
             "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
             "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
             "dev": true,
+            "license": "MIT",
             "dependencies": {
                 "@nodelib/fs.stat": "^2.0.2",
                 "@nodelib/fs.walk": "^1.2.3",
@@ -18117,6 +18145,7 @@
             "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
             "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
             "dev": true,
+            "license": "ISC",
             "dependencies": {
                 "is-glob": "^4.0.1"
             },
@@ -19305,6 +19334,7 @@
             "resolved": "https://registry.npmjs.org/globals/-/globals-15.9.0.tgz",
             "integrity": "sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==",
             "dev": true,
+            "license": "MIT",
             "engines": {
                 "node": ">=18"
             },
@@ -19458,7 +19488,8 @@
             "version": "1.4.0",
             "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
             "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
-            "dev": true
+            "dev": true,
+            "license": "MIT"
         },
         "node_modules/grid-index": {
             "version": "1.1.0",
@@ -20727,6 +20758,14 @@
                 "inspect-function": "^0.3.1"
             }
         },
+        "node_modules/install": {
+            "version": "0.13.0",
+            "resolved": "https://registry.npmjs.org/install/-/install-0.13.0.tgz",
+            "integrity": "sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==",
+            "engines": {
+                "node": ">= 0.10"
+            }
+        },
         "node_modules/internal-slot": {
             "version": "1.0.7",
             "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",
@@ -27554,6 +27593,7 @@
             "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
             "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
             "dev": true,
+            "license": "MIT",
             "engines": {
                 "node": ">= 8"
             }
@@ -37377,6 +37417,7 @@
             "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
             "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
             "dev": true,
+            "license": "MIT",
             "engines": {
                 "node": ">=16"
             },
@@ -38075,14 +38116,15 @@
             "integrity": "sha512-7sI4e/bZijOzyURng88oOFZCISQPTHozfE2sUu5AviFYk5QV7fYGb6YiDl+vKjF/pICA354JImBImL9XJWUvdQ=="
         },
         "node_modules/typescript-eslint": {
-            "version": "8.3.0",
-            "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.3.0.tgz",
-            "integrity": "sha512-EvWjwWLwwKDIJuBjk2I6UkV8KEQcwZ0VM10nR1rIunRDIP67QJTZAHBXTX0HW/oI1H10YESF8yWie8fRQxjvFA==",
+            "version": "8.6.0",
+            "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.6.0.tgz",
+            "integrity": "sha512-eEhhlxCEpCd4helh3AO1hk0UP2MvbRi9CtIAJTVPQjuSXOOO2jsEacNi4UdcJzZJbeuVg1gMhtZ8UYb+NFYPrA==",
             "dev": true,
+            "license": "MIT",
             "dependencies": {
-                "@typescript-eslint/eslint-plugin": "8.3.0",
-                "@typescript-eslint/parser": "8.3.0",
-                "@typescript-eslint/utils": "8.3.0"
+                "@typescript-eslint/eslint-plugin": "8.6.0",
+                "@typescript-eslint/parser": "8.6.0",
+                "@typescript-eslint/utils": "8.6.0"
             },
             "engines": {
                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
diff --git a/package.json b/package.json
index cb4633c6d..f92a533a9 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,7 @@
         "tsc": "tsc -t es5"
     },
     "devDependencies": {
-        "@eslint/js": "^9.1.1",
+        "@eslint/js": "^9.10.0",
         "@types/adm-zip": "^0.5.5",
         "@types/animejs": "^3.1.12",
         "@types/archiver": "^6.0.2",
@@ -72,9 +72,9 @@
         "@types/youtube": "^0.1.0",
         "chai": "^5.0.0",
         "cross-env": "^7.0.3",
-        "eslint": "^9.9.0",
-        "eslint-plugin-react": "^7.34.1",
-        "globals": "^15.1.0",
+        "eslint": "^9.10.0",
+        "eslint-plugin-react": "^7.36.1",
+        "globals": "^15.9.0",
         "jsdom": "^25.0.0",
         "mocha": "^10.2.0",
         "prettier": "^3.1.0",
@@ -83,7 +83,7 @@
         "ts-loader": "^9.5.1",
         "ts-node": "^10.9.1",
         "ts-node-dev": "^2.0.0",
-        "typescript-eslint": "^8.2.0",
+        "typescript-eslint": "^8.6.0",
         "webpack-dev-server": "^5.0.4"
     },
     "dependencies": {
@@ -132,7 +132,6 @@
         "@types/web": "^0.0.157",
         "@types/webpack-hot-middleware": "^2.25.9",
         "@webscopeio/react-textarea-autocomplete": "^4.9.2",
-        "D": "^1.0.0",
         "adm-zip": "^0.5.10",
         "archiver": "^7.0.1",
         "async": "^3.2.5",
@@ -169,6 +168,7 @@
         "csv-parser": "^3.0.0",
         "csv-stringify": "^6.4.4",
         "csvtojson": "^2.0.10",
+        "D": "^1.0.0",
         "d3": "^7.8.5",
         "depcheck": "^1.4.7",
         "dotenv": "^16.4.5",
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 0b1a5160c..2657bf12e 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -49,7 +49,6 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
         maxTokens: 2048,
         temp: 0.25,
         prompt: "The user is going to give you a list of descriptions. Each one is separated by `======` on either side. Descriptions will vary in length, so make sure to only separate when you see `======`. Sort them by the user's specifications. Make sure each description is only in the list once. Each item should be separated by `======`. Immediately afterward, surrounded by `------` on BOTH SIDES, provide some insight into your reasoning for the way you sorted (and mention nothing about the formatting details given in this description). It is VERY important that you format it exactly as described, ensuring the proper number of `=` and `-` (6 of each) and NO commas"
-        // prompt: "I'm going to give you a list of descriptions. Each one is separated by `======` on either side. Descriptions will vary in length, so make sure to only separate when you see `======`. Sort them into lists by shared content. Make sure each description is in only one list. Each list should be separated by `======` with the elements within it separated by `~~~~~~`. Immediately afterward, surrounded by `------` on BOTH SIDES, provide some insight into your reasoning for the way you sorted. It is VERY important that you format it exactly as described, ensuring the proper number of `=` `~` and `-` (6 of each) and no commas.Try to create around 4 groups, but a little more or less is ok. Also, I may provide some more insight after this colon:"
     },
     describe: { model: 'gpt-4-vision-preview', maxTokens: 2048, temp: 0, prompt: 'Describe these images in 3-5 words' },
     chatcard: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Answer the following question as a short flashcard response. Do not include a label.' },
@@ -64,7 +63,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
         model: 'gpt-4-turbo',
         maxTokens: 1024,
         temp: 0,
-        prompt: "BRIEFLY (<75 words) provide a definition for the following term. It will be used as a rubric to evaluate the user's understanding of the topic",
+        prompt: "BRIEFLY (<25 words) provide a definition for the following term. It will be used as a rubric to evaluate the user's understanding of the topic",
     },
 
     type: {
@@ -106,11 +105,6 @@ const gptAPICall = async (inputTextIn: string, callType: GPTCallType, prompt?: a
     try {
         lastCall = inputText;
 
-        // const configuration: ClientOptions = {
-        //     apiKey: process.env.OPENAI_KEY,
-        //     dangerouslyAllowBrowser: true,
-        // };
-
         const usePrompt = prompt ? prompt + opts.prompt : opts.prompt;
         const messages: ChatCompletionMessageParam[] = [
             { role: 'system', content: usePrompt },
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 188f07991..83e236cba 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -493,8 +493,6 @@ export class DocumentOptions {
     // cardSort_visibleSortGroups?: List<number>; // which sorting values are being filtered (shown)
     cardSort_isDesc?: BOOLt = new BoolInfo('whether the cards are sorted ascending or descending');
     // test?: STRt = new StrInfo('testing for filtering')
-    keywords?: MAPt = new MapInfo('keywords', true);
-
 }
 
 export const DocOptions = new DocumentOptions();
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 702d6e6e5..997d5c5a9 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -669,15 +669,10 @@ pie title Minerals in my tap water
     }
     static cardTools(): Button[] {
         return [
-            // { btnList: new List<string>(["Time", "Type", "Color", "Chat GPT", "Custom 1", "Custom 2", "Custom 3" ]),
-            //     title: "Sort Type",  toolTip: "Card Sort Type",   btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script:  '{ return setCardSort(value, _readOnly_); }'}},
-            // { title: "Font",     toolTip: "Font", width: 100,     btnType: ButtonType.DropdownList,                      toolType:"font",     ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'},
-            //   btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
             { title: "Time",      icon:"hourglass-half", toolTip:"Sort by most recent document creation",      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"time",   funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Type",      icon:"eye",            toolTip:"Sort by document type",                      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"docType",funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Color",     icon:"palette",        toolTip:"Sort by document color",                     btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Tags",     icon:"bolt",        toolTip:"Sort by document's tags",                     btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"tag",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            // { title: "AI Sort",     icon:"robot",        toolTip:"Have Chat GPT sort your cards for you !",   btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"chat",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Pile",     icon:"layer-group",        toolTip:"View the cards as a pile in the free form view !",   btnType: ButtonType.ClickButton,  expertMode: false, toolType:"pile",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Chat Popup", icon:"lightbulb",     toolTip:"Toggle the chat popup's visibility!", width: 45 ,btnType: ButtonType.ToggleButton, expertMode: false, toolType:"toggle-chat",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
 
@@ -686,62 +681,23 @@ pie title Minerals in my tap water
                 { title: "Ascending",     toolTip: "Sort the cards in ascending order",   btnType: ButtonType.ToggleButton, icon: "sort-up",  toolType:"up",  ignoreClick: true,  scripts: {onClick: '{ return showFreeform(this.toolType, _readOnly_);}' }},
                 { title: "Descending",   toolTip: "Sort the cards in descending order",btnType: ButtonType.ToggleButton, icon: "sort-down",toolType:"down",ignoreClick: true,  scripts: {onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
             ]},
-
-            // { title: "Filter", icon:"Filter",     toolTip:"Filter cards by tags", width: 150, subMenu: this.cardGroupTools(), expertMode: false, toolType:CollectionViewType.Card,  funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
-            
-            
-
-
-            
-
-            // // { title: "AIs",   icon:"Visibility",   toolTip:"Filter AI labels",        subMenu: this.cardGroupTools("robot"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("chat", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
-            // // { title: "Custom",  icon:"heart",     toolTip:"Add Like labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"like",                  funcs: {hidden:`showFreeform ("like", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            // { title: "1st", icon:"Visibility",     toolTip:"Filter likes", width: 150, subMenu: this.cardGroupTools("heart"),     expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("like", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
-            // // { title: "Custom",  icon:"star",      toolTip:"Add Star labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"star",                  funcs: {hidden:`showFreeform ("star", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            // { title: "2nd", icon:"Visibility",     toolTip:"Filter stars", width: 150, subMenu: this.cardGroupTools("star"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("star", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, 
-            // // { title: "Custom",  icon:"cloud", toolTip:"Add Idea labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"idea",                  funcs: {hidden:`showFreeform ("idea", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            // { title: "3rd", icon:"Visibility",     toolTip:"Filter ideas", width: 150, subMenu: this.cardGroupTools("cloud"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("idea", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
          ]
     }
-    // static labelTools(): Button[] {
-    //     return [
-    //         // { title: "Smart",    icon:"robot",     toolTip:"Have ChatGPT Label and sort your cards for you!",           btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"chat",                  funcs: {hidden:`showFreeform ("chat", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-    //         { title: "AIs",   icon:"AI Sort",   toolTip:"Filter AI labels",        subMenu: this.cardGroupTools("robot"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("chat", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
-    //         // { title: "Custom",  icon:"heart",     toolTip:"Add Like labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"like",                  funcs: {hidden:`showFreeform ("like", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-    //         { title: "1st", icon:"1st",     toolTip:"Filter likes", width: 10, subMenu: this.cardGroupTools("heart"),     expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("like", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
-    //         // { title: "Custom",  icon:"star",      toolTip:"Add Star labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"star",                  funcs: {hidden:`showFreeform ("star", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-    //         { title: "2nd", icon:"2nd",     toolTip:"Filter stars", width: 80, subMenu: this.cardGroupTools("star"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("star", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, 
-    //         // { title: "Custom",  icon:"cloud", toolTip:"Add Idea labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"idea",                  funcs: {hidden:`showFreeform ("idea", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-    //         { title: "3rd", icon:"3rd",     toolTip:"Filter ideas", width: 80, subMenu: this.cardGroupTools("cloud"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("idea", true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
-    //     ]
-    // }
+  
     static tagGroupTools(): Button[] {
-        if (!Doc.UserDoc().activeDashboard){
-            Doc.UserDoc().myFilterHotKeyTitles = new List<string>(['Star', 'Heart', 'Bolt', 'Cloud' ])
-
-            Doc.UserDoc()['Star'] = 'star'
-            Doc.UserDoc()['Heart'] = 'heart'
-            Doc.UserDoc()['Bolt'] = 'bolt'
-            Doc.UserDoc()['Cloud'] = 'cloud'
+        if (!Doc.UserDoc().activeDashboard) {
+        Doc.UserDoc().myFilterHotKeyTitles = new List<string>(['Star', 'Heart', 'Bolt', 'Cloud']);
         
-        }
-
-
+        ['Star', 'Heart', 'Bolt', 'Cloud'].forEach(key => {
+            Doc.UserDoc()[key] = key.toLowerCase();
+        });
+}
         // hack: if there's no dashboard, create default filters.  otherwise, just make sure that the Options button is preserved
         return (Doc.UserDoc().activeDashboard ? [] : [
             { title: "Star", isSystem: false, icon: "star", toolTip:"Click to toggle the star group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
             { title: "Heart", isSystem: false,icon: "heart", toolTip:"Click to toggle the heart group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
             { title: "Bolt", isSystem: false,icon: "bolt", toolTip:"Click to toggle the bolt group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
             { title: "Cloud", isSystem: false,icon: "cloud", toolTip:"Click to toggle the cloud group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
-            
-            
-            // { title: "Group 1", icon, toolTip:"Click to toggle group 1's visibility",  btnType: ButtonType.ToggleButton, width: 40,  expertMode: false, toolType:"1", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            // { title: "Group 2", icon, toolTip:"Click to toggle group 2's visibility",  btnType: ButtonType.ToggleButton, width: 40, expertMode: false, toolType:"2", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            // { title: "Group 3", icon, toolTip:"Click to toggle group 3's visibility",  btnType: ButtonType.ToggleButton, width: 40, expertMode: false, toolType:"3", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            // { title: "Group 4", icon, toolTip:"Click to toggle group 4's visibility",  btnType: ButtonType.ToggleButton,  width: 40, expertMode: false, toolType:"4", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            // { title: "", icon, toolTip:"Click to toggle group 5's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"5", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            // { title: "", icon, toolTip:"Click to toggle group 6's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"6", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            // { title: "", icon, toolTip:"Click to toggle group 7's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"7", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
         ]).concat([
             { title: "Options", isSystem: true,icon: "gear", toolTip:"Click to customize your filter panel", btnType: ButtonType.ClickButton,  expertMode: false, toolType:"opts", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}
         ])
@@ -856,19 +812,6 @@ pie title Minerals in my tap water
             { title: "View",    icon: "View",          toolTip: "View tools",           subMenu: CurrentUserUtils.viewTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Stack",   icon: "View",          toolTip: "Stacking tools",       subMenu: CurrentUserUtils.stackTools(),  expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
             
-            
-            // { title: "SortType", btnList: new List<string>(["Time", "Type", "Color", "ChatGPT", "Custom 1", "Custom 2", "Custom 3" ]),
-            //       toolTip: "Card Sort Type",   btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script:  '{ return showFreeform(value, _readOnly_); }'  },  
-            //     //   funcs: {hidden: `!SelectedDocType("card", this.expertMode)`}
-            //     },
-            // { title: "Visibility",   icon:"Visibility",   toolTip:"Filter AI labels",        subMenu: this.cardGroupTools("robot"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("chat", true)`, width: 100, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
-            // // { title: "Custom",  icon:"heart",     toolTip:"Add Like labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"like",                  funcs: {hidden:`showFreeform ("like", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            // { title: "Visibility", icon:"Visibility",     toolTip:"Filter likes", width: 10, subMenu: this.cardGroupTools("heart"),     expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("like", true)`, width: 100, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
-            // // { title: "Custom",  icon:"star",      toolTip:"Add Star labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"star",                  funcs: {hidden:`showFreeform ("star", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            // { title: "Visibility", icon:"Visibility",     toolTip:"Filter stars", width: 80, subMenu: this.cardGroupTools("star"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("star", true)`, width: 100, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, 
-            // // { title: "Custom",  icon:"cloud", toolTip:"Add Idea labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"idea",                  funcs: {hidden:`showFreeform ("idea", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            // { title: "Visibility", icon:"Visibility",     toolTip:"Filter ideas", width: 80, subMenu: this.cardGroupTools("cloud"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("idea", true)`,width: 100, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} },
-            
             { title: "Card",    icon: "Card",          toolTip: "Card View Tools",            subMenu: CurrentUserUtils.cardTools(),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
             // { title: "Create",   icon: "Create",         toolTip: "Assign card labels",   subMenu: CurrentUserUtils.labelTools(),  expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
@@ -877,13 +820,7 @@ pie title Minerals in my tap water
             { title: "Schema",  icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Schema is selected
             { title: "Filter", icon:"=",     toolTip:"Filter cards by tags", btnType: ButtonType.MultiToggleButton, width: 150, ignoreClick: true,toolType:DocumentType.COL, 
             subMenu: this.tagGroupTools(), funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, 
-            // [
-            //     { title: "Star", icon: "star", toolTip:"Click to toggle the star group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            //     { title: "Heart", icon: "heart", toolTip:"Click to toggle the heart group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            //     { title: "Bolt", icon: "bolt", toolTip:"Click to toggle the bolt group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            //     { title: "Cloud", icon: "cloud", toolTip:"Click to toggle the cloud group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-
-            // ]
+            
         },
            ];
            
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index c63d3d7cb..cb314e3f1 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -1,7 +1,7 @@
 // export const ts = (window as any).ts;
 // import * as typescriptlib from '!!raw-loader!../../../node_modules/typescript/lib/lib.d.ts'
 // import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts'
-import typescriptlib from 'type_decls.d';
+// import typescriptlib from 'type_decls.d';
 import * as ts from 'typescript';
 import { Doc, FieldType } from '../../fields/Doc';
 import { RefField } from '../../fields/RefField';
@@ -248,7 +248,7 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
     const funcScript = `(function(${paramString})${reqTypes} { ${body} })`;
     host.writeFile('file.ts', funcScript);
 
-    if (typecheck) host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib);
+    // if (typecheck) host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib);
     const program = ts.createProgram(['file.ts'], {}, host);
     const testResult = program.emit();
     const outputText = host.readFile('file.js');
diff --git a/src/client/views/FilterPanel.scss b/src/client/views/FilterPanel.scss
index 34c3d2fc1..fbdebde0d 100644
--- a/src/client/views/FilterPanel.scss
+++ b/src/client/views/FilterPanel.scss
@@ -350,19 +350,7 @@
 
 }
 
-.drawing-box{
-    position: absolute;
-    z-index: 10000;
-    border-color: black;
-    border-style: solid;
-    border-width: medium;
-    border-radius: 10%;
-    background-color: #323232;
-
-
 
-
-}
 // .sliderBox-outerDiv {
 //     width: 30%;// width: calc(100% - 14px);   // 14px accounts for handles that are at the max value of the slider that would extend outside the box
 //     height: 40; // height: 100%;
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index e15285007..e73d9c2c9 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -11,7 +11,7 @@ import { List } from '../../fields/List';
 import { RichTextField } from '../../fields/RichTextField';
 import { SearchUtil } from '../util/SearchUtil';
 import { SnappingManager } from '../util/SnappingManager';
-import { undoable } from '../util/UndoManager';
+import { undoable, undoBatch } from '../util/UndoManager';
 import { FieldsDropdown } from './FieldsDropdown';
 import './FilterPanel.scss';
 import { DocumentView } from './nodes/DocumentView';
@@ -20,26 +20,14 @@ import { ObservableReactComponent } from './ObservableReactComponent';
 import { Button } from '../util/CurrentUserUtils';
 import { ButtonType } from './nodes/FontIconBox/FontIconBox';
 import { DocCast } from '../../fields/Types';
-// import { Docs } from '../../documents/Documents';
-import { Docs } from '../documents/Documents';
 import { CurrentUserUtils } from '../util/CurrentUserUtils';
-import { DocumentOptions } from '../documents/Documents';
-import { DocUtils } from '../documents/DocUtils';
-import { dropActionType } from '../util/DropActionTypes';
-import { Toggle } from 'browndash-components';
-import { SettingsManager } from '../util/SettingsManager';
 import { StrCast } from '../../fields/Types';
-import { ToggleType } from 'browndash-components';
-import { MultiToggle } from 'browndash-components';
-import { Type } from 'browndash-components';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { DocData } from '../../fields/DocSymbols';
-import { DocumentType } from '../documents/DocumentTypes';
 import { Tooltip } from '@mui/material';
 import { useLocalObservable } from 'mobx-react';
 import { useRef } from 'react';
 import { useEffect } from 'react';
-import { useState } from 'react';
 
 
 interface filterProps {
@@ -242,12 +230,14 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
         return nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2));
     };
 
-    addHotkey(hotKey: string) {
+    /**
+     * Allows users to add a filter hotkey to the properties panel. Will also update the multitoggle at the top menu and the 
+     * icontags tht are displayed on the documents themselves
+     * @param hotKey tite of the new hotkey
+     */
+    addHotkey = (hotKey: string) => {
         const buttons = DocCast(Doc.UserDoc().myContextMenuBtns);
         const filter = DocCast(buttons.Filter);
-        const filter2 = DocCast(filter);
-        const but2 = Doc.UserDoc().myContextMenuBtns;
-
 
         const newKey: Button = {
             title: hotKey,
@@ -260,15 +250,11 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
             scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}' },
         };
 
-        //         const heyy = [...hi, newKey]
-
         const currHotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles)
 
         Doc.UserDoc().myFilterHotKeyTitles = new List<string>(currHotKeys.concat(hotKey))
 
         Doc.UserDoc()[hotKey] = 'bolt'
-        // Doc.UserDoc()['supppp'] = 'star'
-
 
         const newBtn = CurrentUserUtils.setupContextMenuBtn(newKey, filter);
         newBtn.isSystem = newBtn[DocData].isSystem = undefined;
@@ -276,32 +262,16 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
         const subDocs = DocListCast(filter.data)
         const opts = subDocs[subDocs.length-1]
         Doc.AddDocToList(filter, 'data', newBtn, opts, true);
-
-        
-
-        // console.log(filter[DocData].data + 'ok')
-        // // console.log(filter[DocData][0] + 'help')
-        // console.log(filter[DocData] + 'good grief')
-
-        // console.log(DocCast(DocCast(filter.data))[0])
-        // this.removeHotKey()
-
-        // console.log(DocCast(filter.data) + 'HI')
-        // console.log(DocListCast(filter.data) + 'WOOOOO')
-        // console.log(DocCast(filter.data)[0] + 'hm :(')
-
     }
 
     
-
-    hotKeyButtons() {
+    /**
+     * Renders the newly formed hotkey icon buttons
+     * @returns the buttons to be rendered
+     */
+    hotKeyButtons = () => {
         const selected = DocumentView.SelectedDocs().lastElement();
-
-        // console.log(StrListCast(Doc.UserDoc().myFilterHotKeyTitles) + "hiii")
-
         const hotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
-
-        // hotKeys.forEach(l => console.log(l + "render"))
     
         // Selecting a button should make it so that the icon on the top filter panel becomes said icon
         const buttons = hotKeys.map((hotKey, i) => (
@@ -506,6 +476,10 @@ interface HotKeyButtonProps {
     selected?: Doc
 }
 
+/**
+ * Renders the buttons that correspond to each icon tag in the properties view. Allows users to change the icon, 
+ * title, and delete. 
+ */
 const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey, selected}) => {
     const state = useLocalObservable(() => ({
         isActive: false,
@@ -534,36 +508,34 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey, select
 
     const handleClick = () => {
         state.toggleActive();
-        // console.log(state.isActive + "hmmm")
     };
 
     const hotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles)
+    const buttons = DocCast(Doc.UserDoc().myContextMenuBtns);
+    const filter = DocCast(buttons.Filter);
 
+    /**
+     * The doc of the button in the context menu that corresponds to the current hotkey
+     * @returns 
+     */
     const myHotKeyDoc = () => {
-        const buttons = DocCast(Doc.UserDoc().myContextMenuBtns);
-        const filter = DocCast(buttons.Filter);
         const hotKeyDocs = DocListCast(filter.data)
         return hotKeyDocs.filter(k => StrCast(k.title) === hotKey)[0]
 
     }
 
+    /**
+     * Removes a hotkey from list 
+     */
     const removeHotKey = () => {
-        const buttons = DocCast(Doc.UserDoc().myContextMenuBtns);
-        const filter = DocCast(buttons.Filter);
-
         Doc.RemoveDocFromList(filter, 'data', myHotKeyDoc());
 
-
-
-
-
-        // console.log((DocListCast(filter.data)[0].title) + "emmanuel")
-
-
-        // console.log(DocCast(filter.data) + 'HI')
-        // console.log(DocCast(filter.data)[0] + 'hm :(')
-
     }
+
+    /**
+     * Deselects if the user clicks outside the button
+     * @param event 
+     */
     const handleClickOutside = (event: MouseEvent) => {
         if (panelRef.current && !panelRef.current.contains(event.target as Node)) {
             state.deactivate();
@@ -571,23 +543,22 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey, select
                 state.stopEditing();
 
                 updateFromInput()
-
-
-
-                // Doc.UserDoc().myFilterHotKeyTitles = new List<string>(hotKeys.map(k => k === hotKey ? state.myHotKey : k));
-                // Doc.UserDoc()[state.myHotKey] = StrCast(Doc.UserDoc()[hotKey])
-
             }
         }
     };
 
-    const updateFromInput = () => {
-        const hi = myHotKeyDoc()
+    /**
+     * Updates the list of hotkeys based on the users input. replaces the old title with the new one and then assigns this new 
+     * hotkey with the current icon
+     */
+    const updateFromInput = undoable (() => {
+        const myDoc = myHotKeyDoc()
         Doc.UserDoc().myFilterHotKeyTitles = new List<string>(hotKeys.map(k => k === hotKey ? state.myHotKey : k));
         Doc.UserDoc()[state.myHotKey] = StrCast(Doc.UserDoc()[hotKey])
-        hi.title = state.myHotKey
-        hi.toolTip = `Click to toggle the ${state.myHotKey}'s group's visibility`     
-    }
+        Doc.UserDoc()[hotKey] = ''
+        myDoc.title = state.myHotKey
+        myDoc.toolTip = `Click to toggle the ${state.myHotKey}'s group's visibility`     
+    }, '')
 
 
     useEffect(() => {
@@ -599,31 +570,25 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey, select
 
     const iconOpts = ['star', 'heart', 'bolt', 'satellite', 'palette', 'robot', 'lightbulb', 'highlighter', 'book', 'chalkboard' ];
 
+    /**
+     * Panel of icons the user can choose from to represent their tag
+     */
     const iconPanel = iconOpts.map((icon, i) => (
-        <button key={i} onClick={(e: React.MouseEvent) => {
+        <button key={i} onClick={undoable((e: React.MouseEvent) => {
             e.stopPropagation;
             Doc.UserDoc()[hotKey] = icon;
-            const hi = myHotKeyDoc()
-            hi.icon = icon
-
-
-            
-            }} className='icon-panel-button'>
+            myHotKeyDoc().icon = icon 
+            }, '')} className='icon-panel-button'>
             <FontAwesomeIcon icon={icon as any} color = {SnappingManager.userColor}/>
         </button>
     ));
 
-    function isAttrFiltered(attr: string) {
-        if (selected && selected._childFilters !== undefined && selected.type === DocumentType.COL) {
-            return StrListCast(selected._childFilters).some(filter => filter.includes(attr));
-        } else {
-            return false;
-        }
-    }
-
+    /**
+     * Actually renders the buttons
+     */
 
     return (
-        <div className={`filterHotKey-button ${isAttrFiltered(hotKey) ? 'active' : ''}`} 
+        <div className={`filterHotKey-button`} 
             onClick={(e) => {
                 e.stopPropagation();
                 state.startEditing();
@@ -673,20 +638,12 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey, select
             )}
             <button className='hotKey-close' onClick={(e: React.MouseEvent) => { 
                 e.stopPropagation(); 
-                const hi = StrListCast(Doc.UserDoc().myFilterHotKeyTitles)
-                // hi.forEach((str) => {
-                //     console.log(str + "before");
-                // });
                 Doc.UserDoc().myFilterHotKeyTitles = new List<string>(hotKeys.filter(k => k !== hotKey)); 
                 removeHotKey()
 
-                // hi.forEach((str) => {
-                //     console.log(str + "after");
-                // });
-
             }}>
                 <FontAwesomeIcon icon={'x' as any} color={SnappingManager.userColor}/>
             </button>
         </div>
     );
-})
+})
\ No newline at end of file
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index daa8e1720..e940ba6f9 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -54,7 +54,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     private _widthUndo?: UndoManager.Batch;
 
     // eslint-disable-next-line no-use-before-define
-    public static Instance: PropertiesView | undefined;
+    public static Instance: PropertiesView;
     constructor(props: PropertiesViewProps) {
         super(props);
         makeObservable(this);
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 12aaf0b39..cda39b474 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -26,6 +26,7 @@ import { FieldViewProps } from './nodes/FieldView';
 import { StyleProp } from './StyleProp';
 import './StyleProvider.scss';
 import { IconTagBox } from './nodes/IconTagBox';
+import { DocData } from '../../fields/DocSymbols';
 
 function toggleLockedPosition(doc: Doc) {
     UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground');
@@ -368,6 +369,11 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
             };
             const tags = () => props?.DocumentView?.() && CollectionFreeFormDocumentView.from(props.DocumentView()) ? <TagsView View={props.DocumentView()}/> :  null;
 
+            const iconTags = () => {
+                if (doc && doc![DocData].showIconTags)
+                    {return (<IconTagBox doc= {doc}></IconTagBox>)}
+            }
+
             return (
                 <>
                     {paint()}
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 2390d162c..3d9f28a49 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -23,6 +23,8 @@ import { dropActionType } from '../../util/DropActionTypes';
 import { DocCast } from '../../../fields/Types';
 import { SelectionManager } from '../../util/SelectionManager';
 
+
+
 enum cardSortings {
     Time = 'time',
     Type = 'type',
@@ -32,6 +34,14 @@ enum cardSortings {
     Tag = 'tag',
     None = '',
 }
+
+/**
+ * New view type specifically for studying more dynamically. Allows you to reorder docs however you see fit, easily 
+ * sort and filter using presets, and customize your experience with chat gpt.
+ * 
+ * This file contains code as to how the docs are to be rendered (there place geographically and also in regards to sorting),
+ * and callback functions for the gpt popup
+ */
 @observer
 export class CollectionCardView extends CollectionSubView() {
     private _dropDisposer?: DragManager.DragDropDisposer;
@@ -40,10 +50,8 @@ export class CollectionCardView extends CollectionSubView() {
     private _textToDoc = new Map<string, Doc>();
 
     @observable _forceChildXf = false;
-    // @observable _isLoading = false;
     @observable _hoveredNodeIndex = -1;
     @observable _docRefs = new ObservableMap<Doc, DocumentView>();
-    _draggerRef = React.createRef<HTMLDivElement>();
     @observable _maxRowCount = 10;
     @observable _docDraggedIndex: number = -1;
     @observable _isACardBeingDragged: boolean = false;
@@ -99,12 +107,12 @@ export class CollectionCardView extends CollectionSubView() {
 
         // Reaction to cardSort changes
         this._disposers.sort = reaction(
-            () => this.cardSort,
-            cardSort => {
-                if (cardSort === cardSortings.Chat) {
+            () => GPTPopup.Instance.visible,
+            isVis => {
+                if (isVis) {
                     this.openChatPopup();
                 } else {
-                    GPTPopup.Instance.setVisible(false);
+                    this.Document.cardSort = (this.cardSort === cardSortings.Chat) ? '' : this.Document.cardSort;
                 }
             }
         );
@@ -296,7 +304,7 @@ export class CollectionCardView extends CollectionSubView() {
      * @param de drop event
      * @returns true if a card has been dropped, falls if not
      */
-    onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
+    onInternalDrop = undoable((e: Event, de: DragManager.DropEvent) => {
         if (de.complete.docDragData) {
             this._isACardBeingDragged = false;
             this._docDraggedIndex = -1;
@@ -304,7 +312,7 @@ export class CollectionCardView extends CollectionSubView() {
             return true;
         }
         return false;
-    };
+    }, '' );
 
     get sortedDocs() {
         return this.sort(this.childDocsWithoutLinks, this.cardSort, BoolCast(this.Document.cardSort_isDesc), this._docDraggedIndex);
@@ -511,8 +519,6 @@ export class CollectionCardView extends CollectionSubView() {
             image[DocData].description = response.trim();
             return response; // Return the response from gptImageLabel
         } catch (error) {
-            console.log('bad things have happened');
-
             console.log(error);
         }
         return '';
@@ -523,18 +529,22 @@ export class CollectionCardView extends CollectionSubView() {
      * usable code
      * @param gptOutput
      */
-    @action processGptOutput = (gptOutput: string, questionType: string, tag?: string) => {
-        console.log('HIIII');
-        console.log(StrCast(this.Document.cardSort) + 'cardSort');
+    @action 
+    
+    processGptOutput = undoable((gptOutput: string, questionType: string, tag?: string) => {
         // 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;
             });
         }
 
+        if (questionType ==='6'){
+            this.Document.cardSort = 'chat'
+        }
+
 
         listItems.forEach((item, index) => {
             const normalizedItem = item.trim();
@@ -545,7 +555,6 @@ export class CollectionCardView extends CollectionSubView() {
                 switch (questionType) {
                     case '6':
                         doc.chatIndex = index;
-                        console.log(index);
                         break;
                     case '1':
                         const allHotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
@@ -578,7 +587,7 @@ export class CollectionCardView extends CollectionSubView() {
                 console.warn(`No matching document found for item: ${normalizedItem}`);
             }
         });
-    };
+    }, '');
 
 
     /**
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 8b9f128e0..9e56d313e 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -198,44 +198,6 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
             checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "tag",
             setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort === "tag" ? doc.cardSort = '' : doc.cardSort = 'tag',
         }],
-        // ['heart', {
-        //     checkResult: (doc: Doc) => isAttrFiltered('heart'),
-        //     setDoc: (doc: Doc, dv: DocumentView) => {
-        //         isAttrFiltered('heart') ? Doc.setDocFilter(doc, 'heart', true, 'remove') : Doc.setDocFilter(doc, 'heart', true, 'match');
-
-        //     }
-        // }],
-        // ['star', {
-        //     checkResult: (doc: Doc) => isAttrFiltered('star'),
-
-        //     setDoc: (doc: Doc, dv: DocumentView) => {
-        //         isAttrFiltered('star') ? Doc.setDocFilter(doc, 'star', true, 'remove') : Doc.setDocFilter(doc, 'star', true, 'match');
-        //     }
-        // }],
-        // ['bolt', {
-        //     checkResult: (doc: Doc) => isAttrFiltered('bolt'),
-        //     setDoc: (doc: Doc, dv: DocumentView) => {
-        //         isAttrFiltered('bolt') ? Doc.setDocFilter(doc, 'bolt', true, 'remove') : Doc.setDocFilter(doc, 'bolt', true, 'match');
-
-        //     }
-        // }],
-        // ['cloud', {
-        //     checkResult: (doc: Doc) => isAttrFiltered('cloud'),
-        //     setDoc: (doc: Doc, dv: DocumentView) => {
-        //         isAttrFiltered('cloud') ? Doc.setDocFilter(doc, 'cloud', true, 'remove') : Doc.setDocFilter(doc, 'cloud', true, 'match');
-
-        //     }
-        // }],
-        // ['chat', {
-        //     checkResult: (doc: Doc) => {
-                
-        //         if (StrCast(doc?.cardSort) === "chat"){
-        //             return true
-        //         }} ,
-        //     setDoc: (doc: Doc, dv: DocumentView) => {
-        //         doc.cardSort === "chat" ? doc.cardSort = '' : doc.cardSort = 'chat';
-        //     },
-        // }],
         ['up', {
             checkResult: (doc: Doc) => BoolCast(!doc?.cardSort_isDesc),
             setDoc: (doc: Doc, dv: DocumentView) => {
@@ -251,10 +213,17 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
         ['toggle-chat', {
             checkResult: (doc: Doc) => GPTPopup.Instance.visible,
             setDoc: (doc: Doc, dv: DocumentView) => {
-                GPTPopup.Instance.setVisible(!GPTPopup.Instance.visible);
-                GPTPopup.Instance.setMode(GPTPopupMode.SORT);
-                doc.cardSort === "chat" ? doc.cardSort = '' : doc.cardSort = 'chat';
+                if (GPTPopup.Instance.visible){
+                    doc.cardSort = ''
+                    GPTPopup.Instance.setVisible(false);
+   
+                } else {
+                    GPTPopup.Instance.setVisible(true);
+                    GPTPopup.Instance.setMode(GPTPopupMode.CARD);
+                    GPTPopup.Instance.setCardsDoneLoading(true);
 
+                }
+                
 
             },
         }],
@@ -293,9 +262,6 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
     if (checkResult) {
         return map.get(attr)?.checkResult(selected);
     }
-    const filters = StrListCast(selected._childFilters).concat(StrListCast(selected?._childFiltersByRanges).filter((filter, i) => !(i % 3)));
-
-    // console.log(filters.some(filter => filter.includes('star'))+ "SUOOOOPPP")
 
     const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
     DocumentView.Selected().map(dv => map.get(attr)?.setDoc(dv.layoutDoc, dv));
@@ -303,7 +269,9 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines'
     return undefined;
 });
 
-
+/**
+ * Applies a filter to the selected document (or, if the settins button is pressed, opens the filter panel)
+ */
 ScriptingGlobals.add(function handleTags(value?: any, checkResult?: boolean) {
     const selected = DocumentView.SelectedDocs().lastElement();
 
@@ -312,7 +280,7 @@ ScriptingGlobals.add(function handleTags(value?: any, checkResult?: boolean) {
     }
 
         if (checkResult) {
-            return value=== 'opts' ? PropertiesView.Instance.openFilters : isAttrFiltered(value)
+            return value=== 'opts' ? PropertiesView.Instance?.openFilters : isAttrFiltered(value)
         }
 
         if (value != 'opts'){
@@ -321,8 +289,7 @@ ScriptingGlobals.add(function handleTags(value?: any, checkResult?: boolean) {
         else {
             SnappingManager.PropertiesWidth < 5 && SnappingManager.SetPropertiesWidth(0);
             SnappingManager.SetPropertiesWidth(MainView.Instance.propertiesWidth() < 15 ? Math.min(MainView.Instance._dashUIWidth - 50, 250) : 0);
-
-            PropertiesView.Instance.CloseAll()
+            PropertiesView.Instance?.CloseAll()
             PropertiesView.Instance.openFilters = true
         }
 
@@ -330,183 +297,6 @@ ScriptingGlobals.add(function handleTags(value?: any, checkResult?: boolean) {
         return undefined;
     }, '');
 
-
-
-// ScriptingGlobals.add(function cardHasLabel(label: string) {
-//     const selected = DocumentView.SelectedDocs().lastElement();
-//     const labelNum = Number(label) - 1;
-//     return labelNum < 4 || (selected && DocListCast(selected[Doc.LayoutFieldKey(selected)]).some(doc => doc[StrCast(selected.cardSort_customField)] == labelNum));
-// }, '');
-
-// ScriptingGlobals.add(function setCardSort(attr: "Time" | "Type"| "Color"| "ChatGPT"| "Custom 1"| "Custom 2"| "Custom 3", value?: any, checkResult?: boolean) {
-//     // const editorView = RichTextMenu.Instance?.TextView?.EditorView;
-//     const selected = DocumentView.SelectedDocs().lastElement();
-
-//     // prettier-ignore
-//     const map: Map<"Time" | "Type"| "Color"| "ChatGPT"| "Custom 1"| "Custom 2"| "Custom 3", { checkResult: (doc: Doc) => any; setDoc: (doc: Doc) => void;}> = new Map([
-//         ['Time', {
-
-//             checkResult: (doc: Doc) => {StrCast(doc?.cardSort);
-//             console.log(StrCast(doc?.cardSort + "card sort"))},
-//             setDoc: (doc: Doc) => {doc.cardSort = "time"
-//             console.log("hewwo")}
-
-//         ,
-//         }],
-//         ['Type', {
-//             checkResult: (doc: Doc) => StrCast(doc?.cardSort),
-//             setDoc: (doc: Doc) => doc.cardSort = "type",
-//         }],
-//         ['Color', {
-//             checkResult: (doc: Doc) => StrCast(doc?.cardSort),
-//             setDoc: (doc: Doc) => doc.cardSort = "color",
-//         }],
-//         // ['links', {
-//         //     checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "links",
-//         //     setDoc: (doc: Doc) => doc.cardSort = "links",
-//         // }],
-//         ['Custom 1', {
-//             checkResult: (doc: Doc) => StrCast(doc?.cardSort) + " 1",
-//             setDoc: (doc: Doc) => {
-//                 doc.cardSort = "custom";
-//                 doc.cardSort_customField = "like";
-//                 doc.cardSort_visibleSortGroups = new List<number>();
-//             }
-//         }],
-//         ['Custom 2', {
-//             checkResult: (doc: Doc) => StrCast(doc?.cardSort) + " 2",
-//             setDoc: (doc: Doc) => {
-//                 doc.cardSort = "custom";
-//                 doc.cardSort_customField = "star";
-//                 doc.cardSort_visibleSortGroups = new List<number>();
-//             }
-//         }],
-//         ['Custom 3', {
-//             checkResult: (doc: Doc) => StrCast(doc?.cardSort) + " 3",
-//             setDoc: (doc: Doc) => {
-//                 doc.cardSort = "custom";
-//                 doc.cardSort_customField = "idea";
-//                 doc.cardSort_visibleSortGroups = new List<number>();
-//             }
-//         }],
-//         ['ChatGPT', {
-//             checkResult: (doc: Doc) => StrCast(doc?.cardSort_customField),
-//             setDoc: (doc: Doc) => {
-//                 doc.cardSort = "custom";
-//                 doc.cardSort_customField = "chat";
-//                 doc.cardSort_visibleSortGroups = new List<number>();
-//             },
-//         }],
-//     ]);
-
-//     for (let i = 0; i < 8; i++) {
-//         map.set((i + 1 + '') as any, {
-//             checkResult: (doc: Doc) => NumListCast(doc?.cardSort_visibleSortGroups).includes(i),
-//             setDoc: (doc: Doc) => {
-//                 const list = NumListCast(doc.cardSort_visibleSortGroups);
-//                 doc.cardSort_visibleSortGroups = new List<number>(list.includes(i) ? list.filter(d => d !== i) : [...list, i]);
-//             },
-//         });
-//     }
-
-//     if (checkResult) {
-//         console.log(attr + "attricute")
-//         console.log(map.get(attr)?.checkResult(selected) + "check result")
-//         return map.get(attr)?.checkResult(selected);
-//     }
-
-//     console.log(attr + "attricute lol")
-
-//     // const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
-//     DocumentView.Selected().map(dv => map.get(attr)?.setDoc(dv.layoutDoc));
-//     // setTimeout(() => batch.end(), 100);
-//     return undefined;
-
-//     // map.get(attr)?.setDoc?.();
-//     // return undefined;
-// });
-
-// ScriptingGlobals.add(function setCardSort(value?: any, checkResult?: boolean) {
-//     // const editorView = RichTextMenu.Instance?.TextView?.EditorView;
-//     const selected = DocumentView.SelectedDocs().lastElement();
-//     if (checkResult) {
-//         // console.log(attr + "attricute")
-//         // console.log(map.get(attr)?.checkResult(selected) + "check result")
-//         console.log(StrCast(selected?.cardSort) + 'check');
-//         const hi = StrCast(selected?.cardSort);
-//         return StrCast(selected?.cardSortForDropDown) ?? 'Time';
-//     }
-//     function docFields(doc: Doc): void {
-//         switch (value) {
-//             case 'Custom 1':
-//                 doc.cardSort_customField = 'like';
-//                 break;
-//             case 'Custom 2':
-//                 doc.cardSort_customField = 'star';
-//                 break;
-//             case 'Custom 3':
-//                 doc.cardSort_customField = 'idea';
-//                 break;
-//             case 'Chat GPT':
-//                 doc.cardSort = 'custom';
-//                 doc.cardSort_customField = 'chat';
-//                 break;
-//             default:
-//                 break;
-//         }
-
-//         doc.cardSort_visibleSortGroups = new List<number>();
-//     }
-
-//     // const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
-//     DocumentView.Selected().map(dv => {
-//         dv.Document.cardSortForDropDown = value;
-
-//         if (value != 'Chat GPT') {
-//             dv.Document.cardSort = value.trim().split(/\s+/)[0].toLowerCase();
-//         }
-//         docFields(dv.Document);
-//     });
-
-//     return undefined;
-
-//     // map.get(attr)?.setDoc?.();
-//     // return undefined;
-// });
-
-// ScriptingGlobals.add(function setCardSortAttr(attr: 'time' | 'docType' | 'color', value: any, checkResult?: boolean) {
-//     // const editorView = RichTextMenu.Instance?.TextView?.EditorView;
-//     const selected = SelectionManager.Docs.lastElement();
-//     // prettier-ignore
-//     const map: Map<'time' | 'docType' | 'color', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([
-//         ['time', {
-//             checkResult: (doc:Doc) => StrCast(doc?.cardSort),
-//             setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "time",
-//         }],
-//         ['docType', {
-//             checkResult: (doc:Doc) => StrCast(doc?.cardSort),
-//             setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "type",
-//         }],
-//         ['color', {
-//             checkResult: (doc:Doc) => StrCast(doc?.cardSort),
-//             setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "color",
-//         }],
-//         // ['custom', {
-//         //     checkResult: () => RichTextMenu.Instance.textAlign,
-//         //     setDoc: () => value && editorView?.state ? RichTextMenu.Instance.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value),
-//         // }]
-//         // ,
-//     ]);
-
-//     if (checkResult) {
-//         return map.get(attr)?.checkResult(selected);
-//     }
-
-//     console.log('hey')
-//     SelectionManager.Views.map(dv => map.get(attr)?.setDoc(dv.layoutDoc, dv));
-//     console.log('success')
-// });
-
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highlight' | 'fontSize' | 'alignment', value: string | number, checkResult?: boolean) {
     const editorView = RichTextMenu.Instance?.TextView?.EditorView;
diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx
index 8aa6bff2b..370ce51d1 100644
--- a/src/client/views/nodes/IconTagBox.tsx
+++ b/src/client/views/nodes/IconTagBox.tsx
@@ -1,13 +1,9 @@
 import React from "react";
 import { observer } from "mobx-react";
 import { computed } from "mobx";
-
 import { ObservableReactComponent } from "../ObservableReactComponent";
 import { NumCast } from "../../../fields/Types";
-import { makeObservable } from "mobx";
 import { Doc } from "../../../fields/Doc";
-import { Reaction } from "mobx";
-import { reaction } from "mobx";
 import { numberRange } from "../../../Utils";
 import { Tooltip } from "@mui/material";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -15,16 +11,9 @@ import { undoable } from "../../util/UndoManager";
 import { BoolCast } from "../../../fields/Types";
 import { DocCast } from "../../../fields/Types";
 import './IconTagBox.scss';
-import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../fields/DocSymbols';
+import {DocData } from '../../../fields/DocSymbols';
 import { StrListCast } from "../../../fields/Doc";
 import { StrCast } from "../../../fields/Types";
-import { DocListCast } from "../../../fields/Doc";
-import { List } from "../../../fields/List";
-import { action } from "mobx";
-import { DragManager } from "../../util/DragManager";
-import { setupMoveUpEvents } from "../../../ClientUtils";
-import { returnFalse } from "../../../ClientUtils";
-import { emptyFunction } from "../../../Utils";
 import { CollectionViewType } from "../../documents/DocumentTypes";
 import { SnappingManager } from "../../util/SnappingManager";
 import { MainView } from "../MainView";
@@ -33,95 +22,28 @@ import { PropertiesView } from "../PropertiesView";
 
 export interface IconTagProps {
     doc: Doc;
-
-
 }
 
+/**
+ * Renders the icon tags that rest under the document. The icons rendered are determined by the values of 
+ * each icon in the userdoc.
+ */
 @observer
 export class IconTagBox extends ObservableReactComponent<IconTagProps> {
-    private ref: React.RefObject<HTMLDivElement>;
-    private height: number = 0;
-
-
     @computed
     get currentScale() {
-        // console.log(NumCast((this._props.doc.embedContainer as Doc)?._freeform_scale, 1))
         return NumCast((this._props.doc.embedContainer as Doc)?._freeform_scale, 1);
         
     }
 
     constructor(props: any) {
         super(props);
-        makeObservable(this);
-        this.ref = React.createRef();
     }
 
-    // componentDidMount(): void {
-    //     this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
-    //     this._props.doc._keywordHeight = this.height;
-
-    //     reaction(
-    //         () => this.currentScale,
-    //         () => {
-    //             if (this.currentScale < 1) {
-    //                 this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
-    //                 this._props.doc._keywordHeight = this.height;
-    //             }
-    //         }
-    //     );
-    // }
-
     componentDidUpdate(prevProps: Readonly<IconTagProps>): void {
-        // this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
         this._props.doc[DocData].tagHeight = 36*this.currentScale;
     }
 
-    // createCollection = () => {
-    //     // Get the documents that contain the keyword.
-    //     const selected = DocListCast(this.getKeywordCollectionDocs()!);
-    //     const newEmbeddings = selected.map(doc => Doc.MakeEmbedding(doc));
-
-    //     // Create a new collection and set up configurations.
-    //     const newCollection = ((doc: Doc) => {
-    //         const docData = doc[DocData];
-    //         docData.data = new List<Doc>(newEmbeddings);
-    //         docData.title = this._props.keyword;
-    //         doc._freeform_panX = doc._freeform_panY = 0;
-    //         return doc;
-    //     })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
-    //     newEmbeddings.forEach(embed => (embed.embedContainer = newCollection));
-    //     newCollection._width = 900;
-    //     newCollection._height = 900;
-    //     newCollection.layout_fitWidth = true;
-
-    //     // Add the collection to the keyword document's list of associated smart collections.
-    //     this._props.keywordDoc.collections = new List<Doc>([...DocListCast(this._props.keywordDoc.collections), newCollection]);
-    //     newCollection[DocData].data_labels = new List<string>([this._props.keyword]);
-    //     newCollection[DocData][`${this._props.keyword}`] = true;
-    //     newCollection[DocData].showLabels = true;
-    //     return newCollection;
-    // };
-
-    // @action
-    // handleDragStart = (e: React.PointerEvent) => {
-    //     if (this._props.isEditing) {
-    //         const clone = this.ref.current?.cloneNode(true) as HTMLElement;
-    //         if (!clone) return;
-
-    //         setupMoveUpEvents(
-    //             this,
-    //             e,
-    //             () => {
-    //                 const dragData = new DragManager.DocumentDragData([this.createCollection()]);
-    //                 DragManager.StartDocumentDrag([this.ref.current!], dragData, e.clientX, e.clientY, {});
-    //                 return true;
-    //             },
-    //             returnFalse,
-    //             emptyFunction
-    //         );
-    //         e.preventDefault();
-    //     }
-    // };
 
     /**
      * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups
@@ -130,25 +52,10 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
      * @returns
      */
     renderButtons = (doc: Doc): JSX.Element | null => {
-        // if (cardSort !== cardSortings.Custom) return null;
-
         const amButtons = (StrListCast(Doc.UserDoc().myFilterHotKeyTitles).length) + 1
 
         const keys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles)
-
-
-        // const amButtons = Math.max(
-        //     4,
-        //     this.childDocs?.reduce((set, d) => {
-        //         if (this.cardSort_customField) {
-        //             set.add(NumCast(d[this.cardSort_customField]));
-        //         }
-        //         return set;
-        //     }, new Set<number>()).size ?? 0
-        // );
-
-        // const activeButtonIndex = CollectionCardView.getButtonGroup(this.cardSort_customField, doc);
-
+        
         const totalWidth = (amButtons -1) * 35 + (amButtons -1) * 2 * 5 + 6;
 
         const iconMap =  (buttonID: number)  => {
@@ -179,16 +86,14 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
                         </button>
                     </Tooltip>
                 ))}
-
-                {/* <Tooltip title={<div className="dash-tooltip">Click to customize these icons</div>}>
-                        <button type="button" onClick={() => this.openHotKeyMenu() }>
-                            <FontAwesomeIcon icon='gear' style={{ color: '#17438a', height: '30px', width: '30px'}} />
-                        </button>
-                </Tooltip> */}
             </div>
         );
     };
 
+    /**
+     * Opens the filter panel in the properties menu
+     */
+
     openHotKeyMenu = () => {
         SnappingManager.PropertiesWidth < 5 && SnappingManager.SetPropertiesWidth(0);
         SnappingManager.SetPropertiesWidth(MainView.Instance.propertiesWidth() < 15 ? Math.min(MainView.Instance._dashUIWidth - 50, 250) : 0);
@@ -204,31 +109,19 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
      * @param doc
      */
       toggleButton = undoable((doc: Doc, icon: string) => {
-
-
-
-        // this.cardSort_customField && (doc[this.cardSort_customField] = buttonID);
-
-        // doc.cardSort_activeIcons = new List<string>()
-
-
-        // const list = StrListCast(doc.cardSort_activeIcons);
-        // doc.cardSort_activeIcons = new List<string>(list.includes(icon) ? list.filter(d => d !== icon) : [...list, icon]);
-
         BoolCast(doc[icon]) ? doc[icon] = false : doc[icon] = true
 
-
-
-        // StrListCast(doc.cardSort_activeIcons).push(iconMap[buttonID])
     }, 'toggle card tag');
 
 
+    /**
+     * Determines whether or not the given icon is active depending on the doc's data
+     * @param doc 
+     * @param icon 
+     * @returns 
+     */
     getButtonIcon = (doc: Doc, icon: any): JSX.Element => {
-
-        // const isActive = StrListCast(doc.cardSort_activeIcons).includes(icon)
         const isActive = doc[icon]
-
-        // console.log(StrListCast(doc.cardSort_activeIcons))
         const color = isActive ? '#4476f7' : '#323232';
 
         return <FontAwesomeIcon icon={icon} style={{ color , height: '30px', width: '30px'}} />;
@@ -244,8 +137,6 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
 
     }
 
-
-
 }
 
 
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 002e82332..53bedbb65 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -279,7 +279,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     }
 
     /**
-     * Generates a response to the user's questoin depending on the type of their question
+     * Generates a response to the user's question depending on the type of their question
      */
     generateCard = async () => {
         console.log(this.chatSortPrompt + "USER PROMPT")
@@ -798,7 +798,8 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
                         color={StrCast(SettingsManager.userVariantColor)}
                         tooltip="close"
                         icon={<CgClose size="16px" />}
-                        onClick={() => this.setVisible(false)}
+                        onClick={() => {
+                            this.setVisible(false)}}
                     />
                     
                 </>
diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts
index bee79a38d..fea88cdcb 100644
--- a/src/typings/index.d.ts
+++ b/src/typings/index.d.ts
@@ -12,7 +12,7 @@ declare module 'fit-curve';
 declare module 'iink-js';
 declare module 'pdfjs-dist/web/pdf_viewer';
 declare module 'react-jsx-parser';
-declare module 'type_decls.d';
+// declare module 'type_decls.d';
 
 declare module '@react-pdf/renderer' {
     import * as React from 'react';
-- 
cgit v1.2.3-70-g09d2


From 64635a0a0e07a107c4019e366feb2fe5b01c8b04 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Tue, 17 Sep 2024 13:43:40 -0400
Subject: ready to pull

---
 package-lock.json                                  | 153 ++++++++++++---------
 src/client/documents/Documents.ts                  |   7 -
 src/client/views/DocumentButtonBar.tsx             |  54 +++++---
 src/client/views/DocumentDecorations.tsx           |   4 -
 src/client/views/StyleProvider.tsx                 |   3 +-
 .../views/collections/CollectionCardDeckView.scss  |  58 --------
 .../views/collections/CollectionCardDeckView.tsx   |  19 +--
 src/client/views/global/globalScripts.ts           |  12 +-
 src/client/views/nodes/IconTagBox.scss             |  20 +--
 src/client/views/pdf/GPTPopup/GPTPopup.scss        |  12 +-
 10 files changed, 145 insertions(+), 197 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index fabc6d90a..d20aac0c5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -94,7 +94,7 @@
                 "D": "^1.0.0",
                 "d3": "^7.8.5",
                 "depcheck": "^1.4.7",
-                "dotenv": "^16.3.1",
+                "dotenv": "^16.4.5",
                 "eslint-webpack-plugin": "^4.1.0",
                 "exif": "^0.6.0",
                 "exifr": "^7.1.3",
@@ -133,6 +133,7 @@
                 "image-data-uri": "^2.0.1",
                 "image-size": "^1.0.2",
                 "image-size-stream": "^1.1.0",
+                "install": "^0.13.0",
                 "is-plain-obj": "^4.1.0",
                 "jimp": "^1.0.4",
                 "jpeg-autorotate": "^9.0.0",
@@ -157,7 +158,7 @@
                 "node-stream-zip": "^1.15.0",
                 "nodemailer": "^6.9.7",
                 "nodemon": "^3.0.2",
-                "npm": "^10.2.5",
+                "npm": "^10.8.1",
                 "openai": "^4.26.0",
                 "p-limit": "^6.1.0",
                 "passport": "^0.7.0",
@@ -191,6 +192,7 @@
                 "react-compound-slider": "^3.4.0",
                 "react-datepicker": "^7.3.0",
                 "react-dom": "^18.2.0",
+                "react-draggable": "^4.4.6",
                 "react-grid-layout": "^1.4.4",
                 "react-icons": "^5.0.1",
                 "react-jsx-parser": "^2.0.0",
@@ -249,7 +251,7 @@
                 "xregexp": "^5.1.1"
             },
             "devDependencies": {
-                "@eslint/js": "^9.1.1",
+                "@eslint/js": "^9.10.0",
                 "@types/adm-zip": "^0.5.5",
                 "@types/animejs": "^3.1.12",
                 "@types/archiver": "^6.0.2",
@@ -298,9 +300,9 @@
                 "@types/youtube": "^0.1.0",
                 "chai": "^5.0.0",
                 "cross-env": "^7.0.3",
-                "eslint": "^9.9.0",
-                "eslint-plugin-react": "^7.34.1",
-                "globals": "^15.1.0",
+                "eslint": "^9.10.0",
+                "eslint-plugin-react": "^7.36.1",
+                "globals": "^15.9.0",
                 "jsdom": "^25.0.0",
                 "mocha": "^10.2.0",
                 "prettier": "^3.1.0",
@@ -309,7 +311,7 @@
                 "ts-loader": "^9.5.1",
                 "ts-node": "^10.9.1",
                 "ts-node-dev": "^2.0.0",
-                "typescript-eslint": "^8.2.0",
+                "typescript-eslint": "^8.6.0",
                 "webpack-dev-server": "^5.0.4"
             },
             "engines": {
@@ -2698,9 +2700,9 @@
             }
         },
         "node_modules/@eslint/js": {
-            "version": "9.9.1",
-            "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.9.1.tgz",
-            "integrity": "sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==",
+            "version": "9.10.0",
+            "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz",
+            "integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==",
             "engines": {
                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
             }
@@ -2713,6 +2715,17 @@
                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
             }
         },
+        "node_modules/@eslint/plugin-kit": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz",
+            "integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==",
+            "dependencies": {
+                "levn": "^0.4.1"
+            },
+            "engines": {
+                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+            }
+        },
         "node_modules/@ffmpeg/core": {
             "version": "0.12.6",
             "resolved": "https://registry.npmjs.org/@ffmpeg/core/-/core-0.12.6.tgz",
@@ -10064,16 +10077,16 @@
             "dev": true
         },
         "node_modules/@typescript-eslint/eslint-plugin": {
-            "version": "8.4.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.4.0.tgz",
-            "integrity": "sha512-rg8LGdv7ri3oAlenMACk9e+AR4wUV0yrrG+XKsGKOK0EVgeEDqurkXMPILG2836fW4ibokTB5v4b6Z9+GYQDEw==",
+            "version": "8.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz",
+            "integrity": "sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==",
             "dev": true,
             "dependencies": {
                 "@eslint-community/regexpp": "^4.10.0",
-                "@typescript-eslint/scope-manager": "8.4.0",
-                "@typescript-eslint/type-utils": "8.4.0",
-                "@typescript-eslint/utils": "8.4.0",
-                "@typescript-eslint/visitor-keys": "8.4.0",
+                "@typescript-eslint/scope-manager": "8.6.0",
+                "@typescript-eslint/type-utils": "8.6.0",
+                "@typescript-eslint/utils": "8.6.0",
+                "@typescript-eslint/visitor-keys": "8.6.0",
                 "graphemer": "^1.4.0",
                 "ignore": "^5.3.1",
                 "natural-compare": "^1.4.0",
@@ -10097,15 +10110,15 @@
             }
         },
         "node_modules/@typescript-eslint/parser": {
-            "version": "8.4.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.4.0.tgz",
-            "integrity": "sha512-NHgWmKSgJk5K9N16GIhQ4jSobBoJwrmURaLErad0qlLjrpP5bECYg+wxVTGlGZmJbU03jj/dfnb6V9bw+5icsA==",
+            "version": "8.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.6.0.tgz",
+            "integrity": "sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow==",
             "dev": true,
             "dependencies": {
-                "@typescript-eslint/scope-manager": "8.4.0",
-                "@typescript-eslint/types": "8.4.0",
-                "@typescript-eslint/typescript-estree": "8.4.0",
-                "@typescript-eslint/visitor-keys": "8.4.0",
+                "@typescript-eslint/scope-manager": "8.6.0",
+                "@typescript-eslint/types": "8.6.0",
+                "@typescript-eslint/typescript-estree": "8.6.0",
+                "@typescript-eslint/visitor-keys": "8.6.0",
                 "debug": "^4.3.4"
             },
             "engines": {
@@ -10125,13 +10138,13 @@
             }
         },
         "node_modules/@typescript-eslint/scope-manager": {
-            "version": "8.4.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.4.0.tgz",
-            "integrity": "sha512-n2jFxLeY0JmKfUqy3P70rs6vdoPjHK8P/w+zJcV3fk0b0BwRXC/zxRTEnAsgYT7MwdQDt/ZEbtdzdVC+hcpF0A==",
+            "version": "8.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.6.0.tgz",
+            "integrity": "sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==",
             "dev": true,
             "dependencies": {
-                "@typescript-eslint/types": "8.4.0",
-                "@typescript-eslint/visitor-keys": "8.4.0"
+                "@typescript-eslint/types": "8.6.0",
+                "@typescript-eslint/visitor-keys": "8.6.0"
             },
             "engines": {
                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -10142,13 +10155,13 @@
             }
         },
         "node_modules/@typescript-eslint/type-utils": {
-            "version": "8.4.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.4.0.tgz",
-            "integrity": "sha512-pu2PAmNrl9KX6TtirVOrbLPLwDmASpZhK/XU7WvoKoCUkdtq9zF7qQ7gna0GBZFN0hci0vHaSusiL2WpsQk37A==",
+            "version": "8.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.6.0.tgz",
+            "integrity": "sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==",
             "dev": true,
             "dependencies": {
-                "@typescript-eslint/typescript-estree": "8.4.0",
-                "@typescript-eslint/utils": "8.4.0",
+                "@typescript-eslint/typescript-estree": "8.6.0",
+                "@typescript-eslint/utils": "8.6.0",
                 "debug": "^4.3.4",
                 "ts-api-utils": "^1.3.0"
             },
@@ -10166,9 +10179,9 @@
             }
         },
         "node_modules/@typescript-eslint/types": {
-            "version": "8.4.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.4.0.tgz",
-            "integrity": "sha512-T1RB3KQdskh9t3v/qv7niK6P8yvn7ja1mS7QK7XfRVL6wtZ8/mFs/FHf4fKvTA0rKnqnYxl/uHFNbnEt0phgbw==",
+            "version": "8.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz",
+            "integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==",
             "dev": true,
             "engines": {
                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -10179,13 +10192,13 @@
             }
         },
         "node_modules/@typescript-eslint/typescript-estree": {
-            "version": "8.4.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.4.0.tgz",
-            "integrity": "sha512-kJ2OIP4dQw5gdI4uXsaxUZHRwWAGpREJ9Zq6D5L0BweyOrWsL6Sz0YcAZGWhvKnH7fm1J5YFE1JrQL0c9dd53A==",
+            "version": "8.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.6.0.tgz",
+            "integrity": "sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==",
             "dev": true,
             "dependencies": {
-                "@typescript-eslint/types": "8.4.0",
-                "@typescript-eslint/visitor-keys": "8.4.0",
+                "@typescript-eslint/types": "8.6.0",
+                "@typescript-eslint/visitor-keys": "8.6.0",
                 "debug": "^4.3.4",
                 "fast-glob": "^3.3.2",
                 "is-glob": "^4.0.3",
@@ -10234,15 +10247,15 @@
             }
         },
         "node_modules/@typescript-eslint/utils": {
-            "version": "8.4.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.4.0.tgz",
-            "integrity": "sha512-swULW8n1IKLjRAgciCkTCafyTHHfwVQFt8DovmaF69sKbOxTSFMmIZaSHjqO9i/RV0wIblaawhzvtva8Nmm7lQ==",
+            "version": "8.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.6.0.tgz",
+            "integrity": "sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==",
             "dev": true,
             "dependencies": {
                 "@eslint-community/eslint-utils": "^4.4.0",
-                "@typescript-eslint/scope-manager": "8.4.0",
-                "@typescript-eslint/types": "8.4.0",
-                "@typescript-eslint/typescript-estree": "8.4.0"
+                "@typescript-eslint/scope-manager": "8.6.0",
+                "@typescript-eslint/types": "8.6.0",
+                "@typescript-eslint/typescript-estree": "8.6.0"
             },
             "engines": {
                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -10256,12 +10269,12 @@
             }
         },
         "node_modules/@typescript-eslint/visitor-keys": {
-            "version": "8.4.0",
-            "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.4.0.tgz",
-            "integrity": "sha512-zTQD6WLNTre1hj5wp09nBIDiOc2U5r/qmzo7wxPn4ZgAjHql09EofqhF9WF+fZHzL5aCyaIpPcT2hyxl73kr9A==",
+            "version": "8.6.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.6.0.tgz",
+            "integrity": "sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==",
             "dev": true,
             "dependencies": {
-                "@typescript-eslint/types": "8.4.0",
+                "@typescript-eslint/types": "8.6.0",
                 "eslint-visitor-keys": "^3.4.3"
             },
             "engines": {
@@ -17701,15 +17714,16 @@
             }
         },
         "node_modules/eslint": {
-            "version": "9.9.1",
-            "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.9.1.tgz",
-            "integrity": "sha512-dHvhrbfr4xFQ9/dq+jcVneZMyRYLjggWjk6RVsIiHsP8Rz6yZ8LvZ//iU4TrZF+SXWG+JkNF2OyiZRvzgRDqMg==",
+            "version": "9.10.0",
+            "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz",
+            "integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==",
             "dependencies": {
                 "@eslint-community/eslint-utils": "^4.2.0",
                 "@eslint-community/regexpp": "^4.11.0",
                 "@eslint/config-array": "^0.18.0",
                 "@eslint/eslintrc": "^3.1.0",
-                "@eslint/js": "9.9.1",
+                "@eslint/js": "9.10.0",
+                "@eslint/plugin-kit": "^0.1.0",
                 "@humanwhocodes/module-importer": "^1.0.1",
                 "@humanwhocodes/retry": "^0.3.0",
                 "@nodelib/fs.walk": "^1.2.8",
@@ -17732,7 +17746,6 @@
                 "is-glob": "^4.0.0",
                 "is-path-inside": "^3.0.3",
                 "json-stable-stringify-without-jsonify": "^1.0.1",
-                "levn": "^0.4.1",
                 "lodash.merge": "^4.6.2",
                 "minimatch": "^3.1.2",
                 "natural-compare": "^1.4.0",
@@ -17759,9 +17772,9 @@
             }
         },
         "node_modules/eslint-plugin-react": {
-            "version": "7.35.2",
-            "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.2.tgz",
-            "integrity": "sha512-Rbj2R9zwP2GYNcIak4xoAMV57hrBh3hTaR0k7hVjwCQgryE/pw5px4b13EYjduOI0hfXyZhwBxaGpOTbWSGzKQ==",
+            "version": "7.36.1",
+            "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.36.1.tgz",
+            "integrity": "sha512-/qwbqNXZoq+VP30s1d4Nc1C5GTxjJQjk4Jzs4Wq2qzxFM7dSmuG2UkIjg2USMLh3A/aVcUNrK7v0J5U1XEGGwA==",
             "dev": true,
             "dependencies": {
                 "array-includes": "^3.1.8",
@@ -20914,6 +20927,14 @@
                 "inspect-function": "^0.3.1"
             }
         },
+        "node_modules/install": {
+            "version": "0.13.0",
+            "resolved": "https://registry.npmjs.org/install/-/install-0.13.0.tgz",
+            "integrity": "sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==",
+            "engines": {
+                "node": ">= 0.10"
+            }
+        },
         "node_modules/internal-slot": {
             "version": "1.0.7",
             "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",
@@ -32710,14 +32731,14 @@
             "integrity": "sha512-7sI4e/bZijOzyURng88oOFZCISQPTHozfE2sUu5AviFYk5QV7fYGb6YiDl+vKjF/pICA354JImBImL9XJWUvdQ=="
         },
         "node_modules/typescript-eslint": {
-            "version": "8.4.0",
-            "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.4.0.tgz",
-            "integrity": "sha512-67qoc3zQZe3CAkO0ua17+7aCLI0dU+sSQd1eKPGq06QE4rfQjstVXR6woHO5qQvGUa550NfGckT4tzh3b3c8Pw==",
+            "version": "8.6.0",
+            "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.6.0.tgz",
+            "integrity": "sha512-eEhhlxCEpCd4helh3AO1hk0UP2MvbRi9CtIAJTVPQjuSXOOO2jsEacNi4UdcJzZJbeuVg1gMhtZ8UYb+NFYPrA==",
             "dev": true,
             "dependencies": {
-                "@typescript-eslint/eslint-plugin": "8.4.0",
-                "@typescript-eslint/parser": "8.4.0",
-                "@typescript-eslint/utils": "8.4.0"
+                "@typescript-eslint/eslint-plugin": "8.6.0",
+                "@typescript-eslint/parser": "8.6.0",
+                "@typescript-eslint/utils": "8.6.0"
             },
             "engines": {
                 "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 623a5251e..aac15059e 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -488,14 +488,7 @@ export class DocumentOptions {
     userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)');
 
     cardSort?: STRt = new StrInfo('way cards are sorted in deck view');
-    // cardSortForDropDown?: STRt = new StrInfo('needed for dropdown and i dont know why')
-    // cardSort_customField?: STRt = new StrInfo('field key used for sorting cards');
-    cardSort_activeIcons?: List<string>; //icons each card is tagges with
-    // cardSort_visibleSortGroups?: List<string>; // which sorting values are being filtered (shown)
-
-    // cardSort_visibleSortGroups?: List<number>; // which sorting values are being filtered (shown)
     cardSort_isDesc?: BOOLt = new BoolInfo('whether the cards are sorted ascending or descending');
-    // test?: STRt = new StrInfo('testing for filtering')
 }
 
 export const DocOptions = new DocumentOptions();
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index ea859a1e5..ccde1de80 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -9,7 +9,7 @@ import * as React from 'react';
 import { FaEdit } from 'react-icons/fa';
 import { returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../ClientUtils';
 import { emptyFunction } from '../../Utils';
-import { Doc, DocListCast } from '../../fields/Doc';
+import { Doc } from '../../fields/Doc';
 import { Cast, DocCast } from '../../fields/Types';
 import { DocUtils, IsFollowLinkScript } from '../documents/DocUtils';
 import { CalendarManager } from '../util/CalendarManager';
@@ -263,6 +263,26 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
         );
     }
 
+    @computed
+    get calendarButton() {
+        const targetDoc = this.view0?.Document;
+        return !targetDoc ? null : (
+            <Tooltip title={<div className="dash-calendar-button">Open calendar menu</div>}>
+                <div
+                    className="documentButtonBar-icon"
+                    style={{ color: 'white' }}
+                    onClick={() => {
+                        CalendarManager.Instance.open(this.view0, targetDoc);
+                    }}>
+                    <FontAwesomeIcon className="documentdecorations-icon" icon={faCalendarDays as IconLookup} />
+                </div>
+            </Tooltip>
+        );
+    }
+
+    /**
+     * Allows for both the keywords and the icon tags to be shown using a quasi- multitoggle
+     */
     @computed
     get keywordButton() {
         const targetDoc = this.view0?.Document;
@@ -279,21 +299,10 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                             size="sm"
                             icon={icon}
                             onClick={e => {
-                                // console.log('wtfff')
-                                // name === 'tags' ?? 
                                 if (name === 'tags'){
-                                    undoable(e => (targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags)), 'show icon tags')
+                                    (targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags))
                                 } else {
-                                    undoable(e => {
-                                        const showing = DocumentView.Selected().some(dv => dv.layoutDoc._layout_showTags);
-                                        DocumentView.Selected().forEach(dv => {
-                                            dv.layoutDoc._layout_showTags = !showing;
-                                            if (e.shiftKey)
-                                                DocListCast(dv.Document[Doc.LayoutFieldKey(dv.Document) + '_annotations']).forEach(doc => {
-                                                    if (doc.face) doc.hidden = showing;
-                                                });
-                                        });
-                                    }, 'show Doc tags')
+                                    (targetDoc && (targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels))
                                 }
 
 
@@ -309,15 +318,23 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
 
 
 
-        return !DocumentView.Selected().length ? null : (
+        return !targetDoc ? null : (
             <div className='documentButtonBar-icon'>
             <div className="documentButtonBar-pinTypes" style = {{width: '40px'}}>
                 {metaBtn('tags', 'star')}
                 {metaBtn("keywords", 'id-card')}
              </div>
 
-            <Tooltip title={<div className="dash-keyword-button">Open keyword / icon tag menu</div>}>
-                <div className="documentButtonBar-icon" style={{ color: 'white' }}>
+            <Tooltip title={<div className="dash-keyword-button">Open keyword menu</div>}>
+                <div
+                    className="documentButtonBar-icon"
+                    style={{ color: 'white' }}
+                    onClick={() => {
+                        // targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags;
+                    }}
+                    >
+
+                   
                     <FontAwesomeIcon className="documentdecorations-icon" icon="tag" />
                 </div>
             </Tooltip>
@@ -495,10 +512,11 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                 {!DocumentView.Selected().some(v => v.allLinks.length) ? null : <div className="documentButtonBar-button">{this.followLinkButton}</div>}
                 <div className="documentButtonBar-button">{this.pinButton}</div>
                 <div className="documentButtonBar-button">{this.recordButton}</div>
+                <div className="documentButtonBar-button">{this.calendarButton}</div>
                 <div className="documentButtonBar-button">{this.keywordButton}</div>
                 {!Doc.UserDoc().documentLinksButton_fullMenu ? null : <div className="documentButtonBar-button">{this.shareButton}</div>}
                 <div className="documentButtonBar-button">{this.menuButton}</div>
             </div>
         );
     }
-}
+}
\ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 45ce681aa..37b1f04e7 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -773,10 +773,6 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         const freeformDoc = DocumentView.Selected().some(v => CollectionFreeFormDocumentView.from(v));
       
 
-        const keyWordTrans = doc[DocData].showLabels ? NumCast(doc[DocData].keywordHeight) : 0
-        const tagTrans = doc[DocData].showIconTags ? NumCast(doc[DocData].tagHeight) : 0
-
-
         return (
             <div className="documentDecorations" style={{ display: this._showNothing && !freeformDoc ? 'none' : undefined }}>
                 <div
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index cda39b474..54698ab2d 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -367,13 +367,12 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                     </Tooltip>
                 );
             };
-            const tags = () => props?.DocumentView?.() && CollectionFreeFormDocumentView.from(props.DocumentView()) ? <TagsView View={props.DocumentView()}/> :  null;
+            const tags = () => props?.DocumentView?.() ? <TagsView Views={[props.DocumentView()]}/> :  null;
 
             const iconTags = () => {
                 if (doc && doc![DocData].showIconTags)
                     {return (<IconTagBox doc= {doc}></IconTagBox>)}
             }
-
             return (
                 <>
                     {paint()}
diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
index 1728f7aee..5ccc3d9a8 100644
--- a/src/client/views/collections/CollectionCardDeckView.scss
+++ b/src/client/views/collections/CollectionCardDeckView.scss
@@ -12,7 +12,6 @@
         height: 35px;
         border-radius: 50%;
         background-color: $dark-gray;
-        // border-color: $medium-blue;
         margin: 5px; // transform: translateY(-50px);
     }
 }
@@ -20,7 +19,6 @@
 .card-wrapper {
     display: grid;
     grid-template-columns: repeat(10, 1fr);
-    // width: 100%;
     transform-origin: top left;
 
     position: absolute;
@@ -31,33 +29,6 @@
     transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955);
 }
 
-// .card-button-container {
-//     display: flex;
-//     padding: 3px;
-//     // width: 300px;
-//     // height:100px;
-//     pointer-events: none; /* This ensures the container does not capture hover events */
-
-//     background-color: rgb(218, 218, 218); /* Background color of the container */
-//     border-radius: 50px; /* Rounds the corners of the container */
-//     transform: translateY(25px);
-//     // box-shadow: 0 4px 8px rgba(0,0,0,0.1); /* Optional: Adds shadow for depth */
-//     align-items: center; /* Centers buttons vertically */
-//     justify-content: start; /* Centers buttons horizontally */
-
-//     button {
-//         pointer-events: auto; /* Re-enable pointer events for the buttons */
-    
-//         width: 70px;
-//         height: 70px;
-//         border-radius: 50%;
-//         background-color: $dark-gray;
-//         // border-color: $medium-blue;
-//         margin: 5px; // transform: translateY(-50px);
-//         background-color: transparent;
-//     }
-// }
-
 .no-card-span{
     position: relative;
     width: fit-content;
@@ -70,28 +41,6 @@
 
 
 
-// button:hover {
-//     transform: translateY(-50px);
-// }
-
-// .card-wrapper::after {
-//     content: "";
-//     width: 100%; /* Forces wrapping */
-// }
-
-// .card-wrapper > .card-item:nth-child(10n)::after {
-//     content: "";
-//     width: 100%; /* Forces wrapping after every 10th item */
-// }
-
-// .card-row{
-//     display: flex;
-//     position: absolute;
-//     align-items: center;
-//     transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955);
-
-// }
-
 .card-item-inactive,
 .card-item-active,
 .card-item {
@@ -101,18 +50,11 @@
     flex-direction: column;
 }
 
-// .card-item:hover {
-//     box-shadow: 0 20px 20px $medium-blue;
-//     transform: scale(1.05);
-    
-
-// }
 
 .card-item-inactive {
     opacity: 0.5;
 }
 
 .card-item-active {
-  //  position: absolute;
     z-index: 100;
 }
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 3d9f28a49..bb932b8b2 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -1,9 +1,9 @@
 import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { ClientUtils, DashColor, returnFalse, returnZero } from '../../../ClientUtils';
-import { emptyFunction, numberRange } from '../../../Utils';
-import { Doc, NumListCast, StrListCast } from '../../../fields/Doc';
+import { ClientUtils, returnFalse, returnZero } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
+import { Doc, StrListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { BoolCast, Cast, DateCast, NumCast, RTFCast, ScriptCast, StrCast } from '../../../fields/Types';
@@ -144,18 +144,7 @@ export class CollectionCardView extends CollectionSubView() {
      * custom group
      */
     @computed get childDocsWithoutLinks() {
-        const regularDocs = this.childDocs.filter(l => l.type !== DocumentType.LINK);
-        const activeGroups = StrListCast(this.Document.cardSort_visibleSortGroups);
-
-        if (activeGroups.length > 0) {
-            return regularDocs.filter(doc => {
-                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;
+        return this.childDocs.filter(l => l.type !== DocumentType.LINK);
     }
 
     /**
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 53e10d44d..65bd524fe 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -147,7 +147,7 @@ ScriptingGlobals.add(function showFreeform(attr: 'hcenter' | 'vcenter' | 'grid'
     }
 
     // prettier-ignore
-    const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'chat' | 'up' | 'down' | 'pile' | 'toggle-chat' | 'tag', 
+    const map: Map<'flashcards' | 'hcenter' | 'vcenter' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'chat' | 'up' | 'down' | 'pile' | 'toggle-chat' | 'tag', 
     { 
         waitForRender?: boolean; 
         checkResult: (doc: Doc) => boolean; 
@@ -169,9 +169,13 @@ ScriptingGlobals.add(function showFreeform(attr: 'hcenter' | 'vcenter' | 'grid'
                 else (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce();
             },
         }],
-        ['center', {
-            checkResult: (doc: Doc) => BoolCast(doc?._stacking_alignCenter, false),
-            setDoc: (doc: Doc) => { doc._stacking_alignCenter = !doc._stacking_alignCenter; },
+        ['vcenter', {
+            checkResult: (doc:Doc) => !StrCast(doc?._layout_dontCenter).includes('y'),
+            setDoc: (doc:Doc) => { doc._layout_dontCenter = StrCast(doc.layout_dontCenter).includes('y') ? StrCast(doc.layout_dontCenter).replace(/y/,"") : StrCast(doc.layout_dontCenter) + 'y'; },
+        }],
+        ['hcenter', {
+            checkResult: (doc:Doc) => !StrCast(doc?._layout_dontCenter).includes('x'),
+            setDoc: (doc:Doc) => { doc._layout_dontCenter = StrCast(doc.layout_dontCenter).includes('x') ? StrCast(doc.layout_dontCenter).replace(/x/,"") : 'x'+ StrCast(doc.layout_dontCenter); },
         }],
         ['clusters', {
             waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire
diff --git a/src/client/views/nodes/IconTagBox.scss b/src/client/views/nodes/IconTagBox.scss
index 8c0f92c90..211a961c1 100644
--- a/src/client/views/nodes/IconTagBox.scss
+++ b/src/client/views/nodes/IconTagBox.scss
@@ -4,27 +4,21 @@
     display: flex;
     padding: 3px;
     position: absolute;
-    // width: 300px;
-    // height:100px;
-    pointer-events: none; /* This ensures the container does not capture hover events */
-
-    background-color: rgb(218, 218, 218); /* Background color of the container */
-    border-radius: 50px; /* Rounds the corners of the container */
+    pointer-events: none;
+    background-color: rgb(218, 218, 218); 
+    border-radius: 50px;
     transform: translateY(25px);
-    // box-shadow: 0 4px 8px rgba(0,0,0,0.1); /* Optional: Adds shadow for depth */
-    align-items: center; /* Centers buttons vertically */
-    justify-content: start; /* Centers buttons horizontally */
+    align-items: center; 
+    justify-content: start;
 
     button {
-        pointer-events: auto; /* Re-enable pointer events for the buttons */
+        pointer-events: auto;
         transform: translateY(-7.5px);
-
         width: 30px;
         height: 30px;
         border-radius: 50%;
         background-color: $dark-gray;
-        // border-color: $medium-blue;
-        margin: 5px; // transform: translateY(-50px);
+        margin: 5px; 
         background-color: transparent;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index 1defd1a7f..0247dc10c 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -104,9 +104,9 @@ $highlightedText: #82e0ff;
             display: flex;
             flex-direction: column;
             width: 100%;
-            max-height: calc(100vh - 80px); /* Height minus the input box and some padding */
+            max-height: calc(100vh - 80px);
             overflow-y: auto;
-            padding-bottom: 60px; /* Space for the input */
+            padding-bottom: 60px; 
         }
     
         .chat-bubbles {
@@ -150,15 +150,7 @@ $highlightedText: #82e0ff;
         
     }
 
-    // button {
-    //     font-size: 9px;
-    //     padding: 10px;
-    //     color: #ffffff;
-    //     width: 100%;
-    //     background-color: $button;
-    //     border-radius: 5px;
 
-    // }
 
     .text-btn {
         &:hover {
-- 
cgit v1.2.3-70-g09d2


From 893a7cb517490ad5fc1f4737ff610779a57fdee7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 17 Sep 2024 20:43:16 -0400
Subject: cleaning up card view stuff post-merge

---
 eslint.config.mjs                                  |  66 ++++++++++--
 src/ClientUtils.ts                                 |  34 ------
 src/client/views/MainView.tsx                      |   4 +-
 .../views/collections/CollectionCardDeckView.tsx   |  78 ++++++--------
 src/client/views/global/globalScripts.ts           |  28 +++--
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |   2 +-
 src/client/views/nodes/IconTagBox.tsx              | 120 +++++++++------------
 7 files changed, 153 insertions(+), 179 deletions(-)

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

diff --git a/eslint.config.mjs b/eslint.config.mjs
index c69209327..619966f20 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -1,13 +1,57 @@
-import globals from "globals";
-import pluginJs from "@eslint/js";
-import tseslint from "typescript-eslint";
-import pluginReact from "eslint-plugin-react";
-
+import pluginJs from '@eslint/js';
+import pluginReactConfig from 'eslint-plugin-react/configs/recommended.js';
+import globals from 'globals';
+import tseslint from 'typescript-eslint';
 
 export default [
-  {files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"]},
-  {languageOptions: { globals: globals.browser }},
-  pluginJs.configs.recommended,
-  ...tseslint.configs.recommended,
-  pluginReact.configs.flat.recommended,
-];
\ No newline at end of file
+    {
+        languageOptions: { globals: { ...globals.browser, ...globals.node } },
+    },
+    pluginJs.configs.recommended,
+    ...tseslint.configs.recommended,
+    {
+        rules: {
+            'node/no-missing-import': 0,
+            'no-console': 'off',
+            'func-names': 'off',
+            'no-process-exit': 'off',
+            'object-shorthand': 'off',
+            'class-methods-use-this': 'off',
+            'single-quote': 'off',
+            'max-classes-per-file': 0,
+
+            'react/jsx-filename-extension': [
+                2,
+                {
+                    extensions: ['.js', '.jsx', '.ts', '.tsx'],
+                },
+            ],
+
+            'import/prefer-default-export': 'off',
+            'no-unused-expressions': 'off',
+            '@typescript-eslint/no-unused-expressions': 'off',
+            'prefer-template': 'off',
+            'no-inner-declarations': 'off',
+            'no-plusplus': 'off',
+            'no-multi-assign': 'off',
+            'no-underscore-dangle': 'off',
+            'no-nested-ternary': 'off',
+            'lines-between-class-members': 'off',
+            'no-shadow': 'off',
+            '@typescript-eslint/no-shadow': 'warn',
+            'no-unused-vars': 'off',
+            '@typescript-eslint/no-unused-vars': 'error',
+            '@typescript-eslint/no-namespace': 'off',
+            'react/destructuring-assignment': 0,
+            'prefer-arrow-callback': 'error',
+            'no-return-assign': 'error',
+            'no-await-in-loop': 'error',
+            'no-loop-func': 'error',
+            'no-cond-assign': 'error',
+            'no-use-before-define': 'error',
+            'no-explicit-any': 'error',
+            'no-restricted-globals': ['error', 'event'],
+        },
+    },
+    pluginReactConfig,
+];
diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts
index 58648c5b6..01eda7e98 100644
--- a/src/ClientUtils.ts
+++ b/src/ClientUtils.ts
@@ -234,40 +234,6 @@ export namespace ClientUtils {
         return 'rgba(' + col.r + ',' + col.g + ',' + col.b + (col.a !== undefined ? ',' + col.a : '') + ')';
     }
 
-    export function hexToHsv(hex: string): [number, number, number] {
-        if (!hex) return [0, 0, 0]; // Default to black if hex is not defined
-        const r = parseInt(hex.slice(1, 3), 16) / 255;
-        const g = parseInt(hex.slice(3, 5), 16) / 255;
-        const b = parseInt(hex.slice(5, 7), 16) / 255;
-        const max = Math.max(r, g, b),
-            min = Math.min(r, g, b);
-        const d = max - min;
-        let h: number;
-        const s = max === 0 ? 0 : d / max;
-        const v = max;
-    
-        switch (max) {
-            case min:
-                h = 0;
-                break;
-            case r:
-                h = (g - b) / d + (g < b ? 6 : 0);
-                break;
-            case g:
-                h = (b - r) / d + 2;
-                break;
-            case b:
-                h = (r - g) / d + 4;
-                break;
-            default:
-                h = 0;
-                break;
-        }
-        h /= 6;
-        return [h, s, v];
-    };
-    
-
     export function HSLtoRGB(h: number, s: number, l: number) {
         // Must be fractions of 1
         // s /= 100;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 74a788d94..8b8f85dfb 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -87,7 +87,7 @@ export class MainView extends ObservableReactComponent<object> {
 
     @observable private _windowWidth: number = 0;
     @observable private _windowHeight: number = 0;
-    @observable  _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row)
+    @observable private _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row)
     @observable private _dashUIHeight: number = 0; // height of entire main dashboard region including top menu buttons
     @observable private _panelContent: string = 'none';
     @observable private _sidebarContent: Doc = Doc.MyLeftSidebarPanel;
@@ -549,7 +549,7 @@ export class MainView extends ObservableReactComponent<object> {
                 fa.faCloud,
                 fa.faBolt,
                 fa.faLightbulb,
-                fa.faX
+                fa.faX,
             ]
         );
     }
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index bb932b8b2..c029d17c6 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -1,7 +1,7 @@
-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, returnFalse, returnZero } from '../../../ClientUtils';
+import { ClientUtils, DashColor, returnFalse, returnZero } from '../../../ClientUtils';
 import { emptyFunction } from '../../../Utils';
 import { Doc, StrListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
@@ -18,13 +18,11 @@ import { StyleProp } from '../StyleProp';
 import { DocumentView } from '../nodes/DocumentView';
 import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
 import './CollectionCardDeckView.scss';
-import { CollectionSubView } from './CollectionSubView';
+import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
 import { dropActionType } from '../../util/DropActionTypes';
 import { DocCast } from '../../../fields/Types';
 import { SelectionManager } from '../../util/SelectionManager';
 
-
-
 enum cardSortings {
     Time = 'time',
     Type = 'type',
@@ -36,9 +34,9 @@ enum cardSortings {
 }
 
 /**
- * New view type specifically for studying more dynamically. Allows you to reorder docs however you see fit, easily 
+ * New view type specifically for studying more dynamically. Allows you to reorder docs however you see fit, easily
  * sort and filter using presets, and customize your experience with chat gpt.
- * 
+ *
  * This file contains code as to how the docs are to be rendered (there place geographically and also in regards to sorting),
  * and callback functions for the gpt popup
  */
@@ -57,7 +55,6 @@ export class CollectionCardView extends CollectionSubView() {
     @observable _isACardBeingDragged: boolean = false;
     @observable overIndex: number = -1;
 
-   
     static imageUrlToBase64 = async (imageUrl: string): Promise<string> => {
         try {
             const response = await fetch(imageUrl);
@@ -75,7 +72,7 @@ export class CollectionCardView extends CollectionSubView() {
         }
     };
 
-    constructor(props: any) {
+    constructor(props: SubCollectionViewProps) {
         super(props);
         makeObservable(this);
         this.setRegenerateCallback();
@@ -84,9 +81,7 @@ export class CollectionCardView extends CollectionSubView() {
     /**
      * Callback to ensure gpt's text versions of the child docs are updated
      */
-    setRegenerateCallback = () => {
-        GPTPopup.Instance.setRegenerateCallback(this.childPairStringListAndUpdateSortDesc);
-    }
+    setRegenerateCallback = () => GPTPopup.Instance.setRegenerateCallback(this.childPairStringListAndUpdateSortDesc);
 
     /**
      * update's gpt's doc-text list and initializes callbacks
@@ -99,7 +94,7 @@ export class CollectionCardView extends CollectionSubView() {
         GPTPopup.Instance.onQuizRandom = () => this.quizMode();
     };
 
-    componentDidMount(): void {
+    componentDidMount() {
         this.Document.childFilters_boolean = 'OR';
         this.childDocsWithoutLinks.forEach(c => {
             c[DocData].showIconTags = true;
@@ -112,7 +107,7 @@ export class CollectionCardView extends CollectionSubView() {
                 if (isVis) {
                     this.openChatPopup();
                 } else {
-                    this.Document.cardSort = (this.cardSort === cardSortings.Chat) ? '' : this.Document.cardSort;
+                    this.Document.cardSort = this.cardSort === cardSortings.Chat ? '' : this.Document.cardSort;
                 }
             }
         );
@@ -148,13 +143,13 @@ export class CollectionCardView extends CollectionSubView() {
     }
 
     /**
-     * When in quiz mode, randomly selects a document 
+     * 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
@@ -163,7 +158,6 @@ export class CollectionCardView extends CollectionSubView() {
         return Math.ceil(this.sortedDocs.length / 10);
     }
 
-    
     @action
     setHoveredNodeIndex = (index: number) => {
         if (!DocumentView.SelectedDocs().includes(this.childDocs[index])) {
@@ -289,7 +283,7 @@ export class CollectionCardView extends CollectionSubView() {
 
     /**
      * Resets all the doc dragging vairables once a card is dropped
-     * @param e 
+     * @param e
      * @param de drop event
      * @returns true if a card has been dropped, falls if not
      */
@@ -301,13 +295,12 @@ export class CollectionCardView extends CollectionSubView() {
             return true;
         }
         return false;
-    }, '' );
+    }, '');
 
-    get sortedDocs() {
+    @computed get sortedDocs() {
         return this.sort(this.childDocsWithoutLinks, this.cardSort, BoolCast(this.Document.cardSort_isDesc), this._docDraggedIndex);
     }
 
-
     /**
      * 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
@@ -345,7 +338,7 @@ export class CollectionCardView extends CollectionSubView() {
 
         // 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
@@ -355,31 +348,27 @@ export class CollectionCardView extends CollectionSubView() {
      * @param isDesc
      * @returns
      */
-    @action sort = (docs: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => {
+    sort = (docs: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => {
         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))];
+                        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)];
                 }
             })();
 
             const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0;
-
-            if (isDesc) {
-                return out;
-            }
-
-            return -out;
+            return isDesc ? out : -out;
         });
         if (dragIndex != -1) {
             const draggedDoc = DragManager.docsBeingDragged[0];
@@ -389,12 +378,11 @@ export class CollectionCardView extends CollectionSubView() {
             docs.splice(dragIndex, 0, draggedDoc);
         }
 
-        return docs;
+        return [...docs]; // need to spread docs into a new object list since sort() modifies the incoming list which confuses mobx caching
     };
 
     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}
@@ -514,12 +502,11 @@ export class CollectionCardView extends CollectionSubView() {
     };
 
     /**
-     * Processes gpt's output depending on the type of question the user asked. Converts gpt's string output to 
+     * Processes gpt's output depending on the type of question the user asked. Converts gpt's string output to
      * usable code
      * @param gptOutput
      */
-    @action 
-    
+    @action
     processGptOutput = undoable((gptOutput: string, questionType: string, tag?: string) => {
         // Split the string into individual list items
         const listItems = gptOutput.split('======').filter(item => item.trim() !== '');
@@ -530,11 +517,10 @@ export class CollectionCardView extends CollectionSubView() {
             });
         }
 
-        if (questionType ==='6'){
-            this.Document.cardSort = 'chat'
+        if (questionType === '6') {
+            this.Document.cardSort = 'chat';
         }
 
-
         listItems.forEach((item, index) => {
             const normalizedItem = item.trim();
             // find the corresponding Doc in the textToDoc map
@@ -578,7 +564,6 @@ export class CollectionCardView extends CollectionSubView() {
         });
     }, '');
 
-
     /**
      * Opens up the chat popup and starts the process for smart sorting.
      */
@@ -586,13 +571,13 @@ export class CollectionCardView extends CollectionSubView() {
         GPTPopup.Instance.setVisible(true);
         GPTPopup.Instance.setMode(GPTPopupMode.CARD);
         GPTPopup.Instance.setCardsDoneLoading(true); // Set dataDoneLoading to true after data is loaded
-        await this.childPairStringListAndUpdateSortDesc()
+        await this.childPairStringListAndUpdateSortDesc();
     };
 
     /**
      * Actually renders all the cards
      */
-    renderCards = () => {
+    renderCards = (sortedDocs: Doc[]) => {
         const anySelected = this.childDocs.some(doc => DocumentView.SelectedDocs().includes(doc));
         const isEmpty = this.childDocsWithoutLinks.length === 0;
         const isDesc = BoolCast(this.Document.cardSort_isDesc);
@@ -606,12 +591,11 @@ export class CollectionCardView extends CollectionSubView() {
         }
 
         // Map sorted documents to their rendered components
-        return this.sortedDocs.map((doc, index) => {
-            const realIndex = this.sortedDocs.indexOf(doc);
+        return sortedDocs.map((doc, index) => {
+            const realIndex = sortedDocs.indexOf(doc);
             const calcRowIndex = this.overflowIndexCalc(realIndex);
             const amCards = this.overflowAmCardsCalc(realIndex);
             const isSelected = DocumentView.SelectedDocs().includes(doc);
-            const isDragging = DragManager.docsBeingDragged.includes(doc);
 
             const childScreenToLocal = () => {
                 this._forceChildXf;
@@ -625,7 +609,7 @@ 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, sortedDocs.length - rowIndex * this._maxRowCount) / 2;
                 return (rowCenterIndex - indexInRow) * 100 - 50;
             };
             return (
@@ -680,7 +664,7 @@ export class CollectionCardView extends CollectionSubView() {
                         gridAutoRows: `${100 / this.numRows}%`,
                     }}
                     onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
-                    {this.renderCards()}
+                    {this.renderCards(this.sortedDocs)}
                 </div>
             </div>
         );
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 65bd524fe..99bd537b1 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -283,23 +283,21 @@ ScriptingGlobals.add(function handleTags(value?: any, checkResult?: boolean) {
         return StrListCast(selected._childFilters).some(filter => filter.includes(attr));
     }
 
-        if (checkResult) {
-            return value=== 'opts' ? PropertiesView.Instance?.openFilters : isAttrFiltered(value)
-        }
+    if (checkResult) {
+        return value === 'opts' ? PropertiesView.Instance?.openFilters : isAttrFiltered(value);
+    }
 
-        if (value != 'opts'){
-            isAttrFiltered(value) ? Doc.setDocFilter(selected, value, true, 'remove') : Doc.setDocFilter(selected, value, true, 'match');
-        }
-        else {
-            SnappingManager.PropertiesWidth < 5 && SnappingManager.SetPropertiesWidth(0);
-            SnappingManager.SetPropertiesWidth(MainView.Instance.propertiesWidth() < 15 ? Math.min(MainView.Instance._dashUIWidth - 50, 250) : 0);
-            PropertiesView.Instance?.CloseAll()
-            PropertiesView.Instance.openFilters = true
-        }
+    if (value != 'opts') {
+        isAttrFiltered(value) ? Doc.setDocFilter(selected, value, true, 'remove') : Doc.setDocFilter(selected, value, true, 'match');
+    } else {
+        SnappingManager.PropertiesWidth < 5 && SnappingManager.SetPropertiesWidth(0);
+        SnappingManager.SetPropertiesWidth(MainView.Instance.propertiesWidth() < 15 ? 250 : 0);
+        PropertiesView.Instance?.CloseAll();
+        PropertiesView.Instance.openFilters = true;
+    }
 
-    
-        return undefined;
-    }, '');
+    return undefined;
+}, '');
 
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highlight' | 'fontSize' | 'alignment', value: string | number, checkResult?: boolean) {
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 0fe112634..7feb17df1 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -157,7 +157,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
 
         let noviceList: string[] = [];
         let text: string | undefined;
-        let getStyle: (val: string) => { [key: string]: string } = () => ({});
+        const getStyle: (val: string) => { [key: string]: string } = () => ({});
         let icon: IconProp = 'caret-down';
         const isViewDropdown = script?.script.originalScript.startsWith('{ return setView');
         if (isViewDropdown) {
diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx
index 370ce51d1..e6c0961ed 100644
--- a/src/client/views/nodes/IconTagBox.tsx
+++ b/src/client/views/nodes/IconTagBox.tsx
@@ -1,31 +1,30 @@
-import React from "react";
-import { observer } from "mobx-react";
-import { computed } from "mobx";
-import { ObservableReactComponent } from "../ObservableReactComponent";
-import { NumCast } from "../../../fields/Types";
-import { Doc } from "../../../fields/Doc";
-import { numberRange } from "../../../Utils";
-import { Tooltip } from "@mui/material";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { undoable } from "../../util/UndoManager";
-import { BoolCast } from "../../../fields/Types";
-import { DocCast } from "../../../fields/Types";
+import React from 'react';
+import { observer } from 'mobx-react';
+import { computed } from 'mobx';
+import { ObservableReactComponent } from '../ObservableReactComponent';
+import { NumCast } from '../../../fields/Types';
+import { Doc } from '../../../fields/Doc';
+import { numberRange } from '../../../Utils';
+import { Tooltip } from '@mui/material';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { undoable } from '../../util/UndoManager';
+import { BoolCast } from '../../../fields/Types';
+import { DocCast } from '../../../fields/Types';
 import './IconTagBox.scss';
-import {DocData } from '../../../fields/DocSymbols';
-import { StrListCast } from "../../../fields/Doc";
-import { StrCast } from "../../../fields/Types";
-import { CollectionViewType } from "../../documents/DocumentTypes";
-import { SnappingManager } from "../../util/SnappingManager";
-import { MainView } from "../MainView";
-import { PropertiesView } from "../PropertiesView";
-
+import { DocData } from '../../../fields/DocSymbols';
+import { StrListCast } from '../../../fields/Doc';
+import { StrCast } from '../../../fields/Types';
+import { CollectionViewType } from '../../documents/DocumentTypes';
+import { SnappingManager } from '../../util/SnappingManager';
+import { MainView } from '../MainView';
+import { PropertiesView } from '../PropertiesView';
 
 export interface IconTagProps {
     doc: Doc;
 }
 
 /**
- * Renders the icon tags that rest under the document. The icons rendered are determined by the values of 
+ * Renders the icon tags that rest under the document. The icons rendered are determined by the values of
  * each icon in the userdoc.
  */
 @observer
@@ -33,7 +32,6 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
     @computed
     get currentScale() {
         return NumCast((this._props.doc.embedContainer as Doc)?._freeform_scale, 1);
-        
     }
 
     constructor(props: any) {
@@ -41,10 +39,9 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
     }
 
     componentDidUpdate(prevProps: Readonly<IconTagProps>): void {
-        this._props.doc[DocData].tagHeight = 36*this.currentScale;
+        this._props.doc[DocData].tagHeight = 36 * this.currentScale;
     }
 
-
     /**
      * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups
      * @param doc
@@ -52,36 +49,32 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
      * @returns
      */
     renderButtons = (doc: Doc): JSX.Element | null => {
-        const amButtons = (StrListCast(Doc.UserDoc().myFilterHotKeyTitles).length) + 1
+        const amButtons = StrListCast(Doc.UserDoc().myFilterHotKeyTitles).length + 1;
 
-        const keys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles)
-        
-        const totalWidth = (amButtons -1) * 35 + (amButtons -1) * 2 * 5 + 6;
+        const keys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
 
-        const iconMap =  (buttonID: number)  => {
-            return StrCast(Doc.UserDoc()[keys[buttonID]])
+        const totalWidth = (amButtons - 1) * 35 + (amButtons - 1) * 2 * 5 + 6;
 
+        const iconMap = (buttonID: number) => {
+            return StrCast(Doc.UserDoc()[keys[buttonID]]);
         };
 
-        const isCard = DocCast(this._props.doc.embedContainer).type_collection === CollectionViewType.Card
-
-
+        const isCard = DocCast(this._props.doc.embedContainer).type_collection === CollectionViewType.Card;
 
         return (
             <div
-            className="card-button-container"
-            style={{
-                transformOrigin: 'top left',
-                transform: `scale(${ isCard ? 2 : 0.6 / this.currentScale})
-                translateY(${doc[DocData].showLabels ? ((NumCast(doc[DocData].keywordHeight)*(1-this.currentScale))) : 0}px)
+                className="card-button-container"
+                style={{
+                    transformOrigin: 'top left',
+                    transform: `scale(${isCard ? 2 : 0.6 / this.currentScale})
+                translateY(${doc[DocData].showLabels ? NumCast(doc[DocData].keywordHeight) * (1 - this.currentScale) : 0}px)
                 `,
-                width: `${totalWidth}px`,
-                fontSize: '50px'
-            }}
-            >           
-                {numberRange(amButtons-1).map(i => (
+                    width: `${totalWidth}px`,
+                    fontSize: '50px',
+                }}>
+                {numberRange(amButtons - 1).map(i => (
                     <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from the {iconMap(i)} group</div>}>
-                        <button key = {i} type="button" onClick={() => this.toggleButton(doc, iconMap(i) )}>
+                        <button key={i} type="button" onClick={() => this.toggleButton(doc, iconMap(i))}>
                             {this.getButtonIcon(doc, iconMap(i))}
                         </button>
                     </Tooltip>
@@ -96,47 +89,36 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
 
     openHotKeyMenu = () => {
         SnappingManager.PropertiesWidth < 5 && SnappingManager.SetPropertiesWidth(0);
-        SnappingManager.SetPropertiesWidth(MainView.Instance.propertiesWidth() < 15 ? Math.min(MainView.Instance._dashUIWidth - 50, 250) : 0);
+        SnappingManager.SetPropertiesWidth(MainView.Instance.propertiesWidth() < 15 ? 250 : 0);
 
-        PropertiesView.Instance.CloseAll()
-        PropertiesView.Instance.openFilters = true
-    }
+        PropertiesView.Instance.CloseAll();
+        PropertiesView.Instance.openFilters = true;
+    };
 
-      /**
+    /**
      * Toggles the buttons between on and off when creating custom sort groupings/changing those created by gpt
      * @param childPairIndex
      * @param buttonID
      * @param doc
      */
-      toggleButton = undoable((doc: Doc, icon: string) => {
-        BoolCast(doc[icon]) ? doc[icon] = false : doc[icon] = true
-
+    toggleButton = undoable((doc: Doc, icon: string) => {
+        BoolCast(doc[icon]) ? (doc[icon] = false) : (doc[icon] = true);
     }, 'toggle card tag');
 
-
     /**
      * Determines whether or not the given icon is active depending on the doc's data
-     * @param doc 
-     * @param icon 
-     * @returns 
+     * @param doc
+     * @param icon
+     * @returns
      */
     getButtonIcon = (doc: Doc, icon: any): JSX.Element => {
-        const isActive = doc[icon]
+        const isActive = doc[icon];
         const color = isActive ? '#4476f7' : '#323232';
 
-        return <FontAwesomeIcon icon={icon} style={{ color , height: '30px', width: '30px'}} />;
+        return <FontAwesomeIcon icon={icon} style={{ color, height: '30px', width: '30px' }} />;
     };
 
-    render (){
-        return (
-            <>
-                {this.renderButtons(this._props.doc)}
-
-            </>
-        )
-
+    render() {
+        return <>{this.renderButtons(this._props.doc)}</>;
     }
-
 }
-
-
-- 
cgit v1.2.3-70-g09d2


From bde7bd192b1a81e3fd1c4ead13e821b95b42f3cf Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 17 Sep 2024 21:02:35 -0400
Subject: updating packages and more cleanup of cardview merge

---
 package-lock.json                                  | 1098 ++++++++++----------
 package.json                                       |    6 +-
 src/client/views/DocumentDecorations.tsx           |    3 -
 src/client/views/StyleProvider.tsx                 |    7 +-
 .../views/collections/CollectionCardDeckView.tsx   |    7 +-
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |    4 +-
 src/client/views/pdf/AnchorMenu.tsx                |    2 +-
 7 files changed, 585 insertions(+), 542 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index d20aac0c5..78a1281d0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -51,7 +51,7 @@
                 "@types/reveal": "^4.2.0",
                 "@types/supercluster": "^7.1.3",
                 "@types/textfit": "^2.4.4",
-                "@types/web": "^0.0.159",
+                "@types/web": "^0.0.163",
                 "@types/webpack-hot-middleware": "^2.25.9",
                 "@webscopeio/react-textarea-autocomplete": "^4.9.2",
                 "adm-zip": "^0.5.10",
@@ -109,7 +109,7 @@
                 "file-saver": "^2.0.5",
                 "find-in-files": "^0.5.0",
                 "fit-curve": "^0.2.0",
-                "flexlayout-react": "^0.7.15",
+                "flexlayout-react": "^0.8.0",
                 "fluent-ffmpeg": "^2.1.2",
                 "forever-agent": "^0.6.1",
                 "fork-ts-checker-webpack-plugin": "^9.0.2",
@@ -119,7 +119,7 @@
                 "function-plot": "^1.23.3",
                 "golden-layout": "^2.6.0",
                 "google-auth-library": "^9.4.1",
-                "googleapis": "^142.0.0",
+                "googleapis": "^144.0.0",
                 "googlephotos": "^0.3.5",
                 "got": "^14.0.0",
                 "howler": "^2.2.4",
@@ -451,9 +451,9 @@
             }
         },
         "node_modules/@azure/core-auth": {
-            "version": "1.7.2",
-            "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.7.2.tgz",
-            "integrity": "sha512-Igm/S3fDYmnMq1uKS38Ae1/m37B3zigdlZw+kocwEhh5GjyKjPrXKO2J6rzpC1wAxrNil/jX9BJRqBshyjnF3g==",
+            "version": "1.8.0",
+            "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.8.0.tgz",
+            "integrity": "sha512-YvFMowkXzLbXNM11yZtVLhUCmuG0ex7JKOH366ipjmHBhL3vpDcPAeWF+jf0X+jVXwFqo3UhsWUq4kH0ZPdu/g==",
             "dependencies": {
                 "@azure/abort-controller": "^2.0.0",
                 "@azure/core-util": "^1.1.0",
@@ -563,12 +563,12 @@
             }
         },
         "node_modules/@azure/core-rest-pipeline": {
-            "version": "1.16.3",
-            "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.16.3.tgz",
-            "integrity": "sha512-VxLk4AHLyqcHsfKe4MZ6IQ+D+ShuByy+RfStKfSjxJoL3WBWq17VNmrz8aT8etKzqc2nAeIyLxScjpzsS4fz8w==",
+            "version": "1.17.0",
+            "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.17.0.tgz",
+            "integrity": "sha512-62Vv8nC+uPId3j86XJ0WI+sBf0jlqTqPUFCBNrGtlaUeQUIXWV/D8GE5A1d+Qx8H7OQojn2WguC8kChD6v0shA==",
             "dependencies": {
                 "@azure/abort-controller": "^2.0.0",
-                "@azure/core-auth": "^1.4.0",
+                "@azure/core-auth": "^1.8.0",
                 "@azure/core-tracing": "^1.0.1",
                 "@azure/core-util": "^1.9.0",
                 "@azure/logger": "^1.0.0",
@@ -603,9 +603,9 @@
             }
         },
         "node_modules/@azure/core-util": {
-            "version": "1.9.2",
-            "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.9.2.tgz",
-            "integrity": "sha512-l1Qrqhi4x1aekkV+OlcqsJa4AnAkj5p0JV8omgwjaV9OAbP41lvrMvs+CptfetKkeEaGRGSzby7sjPZEX7+kkQ==",
+            "version": "1.10.0",
+            "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.10.0.tgz",
+            "integrity": "sha512-dqLWQsh9Nro1YQU+405POVtXnwrIVqPyfUzc4zXCbThTg7+vNNaiMkwbX9AMXKyoFYFClxmB3s25ZFr3+jZkww==",
             "dependencies": {
                 "@azure/abort-controller": "^2.0.0",
                 "tslib": "^2.6.2"
@@ -2590,9 +2590,9 @@
             }
         },
         "node_modules/@eslint-community/regexpp": {
-            "version": "4.11.0",
-            "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz",
-            "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==",
+            "version": "4.11.1",
+            "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz",
+            "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==",
             "engines": {
                 "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
             }
@@ -2754,29 +2754,29 @@
             }
         },
         "node_modules/@floating-ui/core": {
-            "version": "1.6.7",
-            "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.7.tgz",
-            "integrity": "sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g==",
+            "version": "1.6.8",
+            "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz",
+            "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==",
             "dependencies": {
-                "@floating-ui/utils": "^0.2.7"
+                "@floating-ui/utils": "^0.2.8"
             }
         },
         "node_modules/@floating-ui/dom": {
-            "version": "1.6.10",
-            "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.10.tgz",
-            "integrity": "sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A==",
+            "version": "1.6.11",
+            "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.11.tgz",
+            "integrity": "sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==",
             "dependencies": {
                 "@floating-ui/core": "^1.6.0",
-                "@floating-ui/utils": "^0.2.7"
+                "@floating-ui/utils": "^0.2.8"
             }
         },
         "node_modules/@floating-ui/react": {
-            "version": "0.26.23",
-            "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.23.tgz",
-            "integrity": "sha512-9u3i62fV0CFF3nIegiWiRDwOs7OW/KhSUJDNx2MkQM3LbE5zQOY01sL3nelcVBXvX7Ovvo3A49I8ql+20Wg/Hw==",
+            "version": "0.26.24",
+            "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.24.tgz",
+            "integrity": "sha512-2ly0pCkZIGEQUq5H8bBK0XJmc1xIK/RM3tvVzY3GBER7IOD1UgmC2Y2tjj4AuS+TC+vTE1KJv2053290jua0Sw==",
             "dependencies": {
-                "@floating-ui/react-dom": "^2.1.1",
-                "@floating-ui/utils": "^0.2.7",
+                "@floating-ui/react-dom": "^2.1.2",
+                "@floating-ui/utils": "^0.2.8",
                 "tabbable": "^6.0.0"
             },
             "peerDependencies": {
@@ -2785,9 +2785,9 @@
             }
         },
         "node_modules/@floating-ui/react-dom": {
-            "version": "2.1.1",
-            "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.1.tgz",
-            "integrity": "sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==",
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz",
+            "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==",
             "dependencies": {
                 "@floating-ui/dom": "^1.0.0"
             },
@@ -2797,9 +2797,9 @@
             }
         },
         "node_modules/@floating-ui/utils": {
-            "version": "0.2.7",
-            "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.7.tgz",
-            "integrity": "sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA=="
+            "version": "0.2.8",
+            "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz",
+            "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig=="
         },
         "node_modules/@formatjs/ecma402-abstract": {
             "version": "2.0.0",
@@ -3010,9 +3010,9 @@
             "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="
         },
         "node_modules/@iconify/utils": {
-            "version": "2.1.32",
-            "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.1.32.tgz",
-            "integrity": "sha512-LeifFZPPKu28O3AEDpYJNdEbvS4/ojAPyIW+pF/vUpJTYnbTiXUHkCh0bwgFRzKvdpb8H4Fbfd/742++MF4fPQ==",
+            "version": "2.1.33",
+            "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.1.33.tgz",
+            "integrity": "sha512-jP9h6v/g0BIZx0p7XGJJVtkVnydtbgTgt9mVNcGDYwaa7UhdHdI9dvoq+gKj9sijMSJKxUPEG2JyjsgXjxL7Kw==",
             "dependencies": {
                 "@antfu/install-pkg": "^0.4.0",
                 "@antfu/utils": "^0.7.10",
@@ -3081,9 +3081,9 @@
             }
         },
         "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
-            "version": "6.0.1",
-            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
-            "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+            "version": "6.1.0",
+            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+            "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
             "engines": {
                 "node": ">=12"
             },
@@ -3197,13 +3197,13 @@
             }
         },
         "node_modules/@jimp/core": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.3.0.tgz",
-            "integrity": "sha512-3+ndSrQYQxyyKyUSdXyk29vHt9vc3zg+3aFrVX2RHzAMeLdjkQHqzQc/7v6VxRk6BtOh1v/VPtmsXHXVg1vVhA==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.0.tgz",
+            "integrity": "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w==",
             "dependencies": {
-                "@jimp/file-ops": "1.3.0",
-                "@jimp/types": "1.3.0",
-                "@jimp/utils": "1.3.0",
+                "@jimp/file-ops": "1.6.0",
+                "@jimp/types": "1.6.0",
+                "@jimp/utils": "1.6.0",
                 "await-to-js": "^3.0.0",
                 "exif-parser": "^0.1.12",
                 "file-type": "^16.0.0",
@@ -3214,13 +3214,13 @@
             }
         },
         "node_modules/@jimp/diff": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.3.0.tgz",
-            "integrity": "sha512-eGFfZi8UjSZ6gGu9kpQmGPRRDt5fMV3V1qKRn0cTqtBsECAnKPG5PPT1dvnjDDBbtAOH81jhIr//ko8H5WV8jg==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.0.tgz",
+            "integrity": "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw==",
             "dependencies": {
-                "@jimp/plugin-resize": "1.3.0",
-                "@jimp/types": "1.3.0",
-                "@jimp/utils": "1.3.0",
+                "@jimp/plugin-resize": "1.6.0",
+                "@jimp/types": "1.6.0",
+                "@jimp/utils": "1.6.0",
                 "pixelmatch": "^5.3.0"
             },
             "engines": {
@@ -3228,21 +3228,21 @@
             }
         },
         "node_modules/@jimp/file-ops": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.3.0.tgz",
-            "integrity": "sha512-DzbSLgUdOGT9T9qf+Ik/hBA8e4zA9tWyHnJJA/N9sJHwHNDCMtoaX3KY5ssHuRGmcngGKLwbeGjddnZXF4oIVA==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.0.tgz",
+            "integrity": "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ==",
             "engines": {
                 "node": ">=18"
             }
         },
         "node_modules/@jimp/js-bmp": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.3.0.tgz",
-            "integrity": "sha512-sc3jvJJOMHoUtP9mlnuBCkwhHy9T2KiSfdV3XKg81v+bg9O0LudB33v3Y0dtGxSo/WOL2V6PVNIWTRf7gu+hJw==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.0.tgz",
+            "integrity": "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw==",
             "dependencies": {
-                "@jimp/core": "1.3.0",
-                "@jimp/types": "1.3.0",
-                "@jimp/utils": "1.3.0",
+                "@jimp/core": "1.6.0",
+                "@jimp/types": "1.6.0",
+                "@jimp/utils": "1.6.0",
                 "bmp-ts": "^1.0.9"
             },
             "engines": {
@@ -3250,12 +3250,12 @@
             }
         },
         "node_modules/@jimp/js-gif": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.3.0.tgz",
-            "integrity": "sha512-hhAd/TpZSp6AIuRjYKFGGEsu9HzQG16Q9lZHPcTZz1TlxtTUqW60AJvFrGvnUZbTecDj3JnI3TipX8aeqZpBDg==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.0.tgz",
+            "integrity": "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g==",
             "dependencies": {
-                "@jimp/core": "1.3.0",
-                "@jimp/types": "1.3.0",
+                "@jimp/core": "1.6.0",
+                "@jimp/types": "1.6.0",
                 "gifwrap": "^0.10.1",
                 "omggif": "^1.0.10"
             },
@@ -3264,12 +3264,12 @@
             }
         },
         "node_modules/@jimp/js-jpeg": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.3.0.tgz",
-            "integrity": "sha512-SrZL35FvKsgySS5kpqYWbVsi0rswcgxw3oRTVOy55q8F045fE4U0YqlnRdkg77dxdahUOIWVhvn5+0V+Re5F5A==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.0.tgz",
+            "integrity": "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA==",
             "dependencies": {
-                "@jimp/core": "1.3.0",
-                "@jimp/types": "1.3.0",
+                "@jimp/core": "1.6.0",
+                "@jimp/types": "1.6.0",
                 "jpeg-js": "^0.4.4"
             },
             "engines": {
@@ -3277,12 +3277,12 @@
             }
         },
         "node_modules/@jimp/js-png": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.3.0.tgz",
-            "integrity": "sha512-9x8uFueVupNocQQ5WEFat61M31MdRDV7O3QBDR2iEsbVeQ1+LE9Tvvm9r1PG9W91KZYzG4IYPPsogQ0TFEixqA==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.0.tgz",
+            "integrity": "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg==",
             "dependencies": {
-                "@jimp/core": "1.3.0",
-                "@jimp/types": "1.3.0",
+                "@jimp/core": "1.6.0",
+                "@jimp/types": "1.6.0",
                 "pngjs": "^7.0.0"
             },
             "engines": {
@@ -3290,12 +3290,12 @@
             }
         },
         "node_modules/@jimp/js-tiff": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.3.0.tgz",
-            "integrity": "sha512-uin5WkVm0M8ZgotIANXU0sfVkNApsKws1ZSqsc9NZf0MYJsZkz/w8D4ld6hXkFCEQcJ/TMu7aeMZSP+I8cbmOg==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.0.tgz",
+            "integrity": "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw==",
             "dependencies": {
-                "@jimp/core": "1.3.0",
-                "@jimp/types": "1.3.0",
+                "@jimp/core": "1.6.0",
+                "@jimp/types": "1.6.0",
                 "utif2": "^4.1.0"
             },
             "engines": {
@@ -3303,12 +3303,12 @@
             }
         },
         "node_modules/@jimp/plugin-blit": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.3.0.tgz",
-            "integrity": "sha512-5OjEUM0jtzQ6KJBTougs8ozbUAxYyJiKZqceFb8mqKVMjuiM94N9425mIDDgOl5MDFwAESeTMIchKCAPMOv2FQ==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.0.tgz",
+            "integrity": "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA==",
             "dependencies": {
-                "@jimp/types": "1.3.0",
-                "@jimp/utils": "1.3.0",
+                "@jimp/types": "1.6.0",
+                "@jimp/utils": "1.6.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3316,23 +3316,23 @@
             }
         },
         "node_modules/@jimp/plugin-blur": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.3.0.tgz",
-            "integrity": "sha512-108RHeCvHFJqpQvuaydhxwJLBwdjEWKLW6ZXWWCnCadrpKbH2yqu9P6oUhHS7atLjQ0ZBzXcM+Wj2VYR7XU8ng==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.0.tgz",
+            "integrity": "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw==",
             "dependencies": {
-                "@jimp/core": "1.3.0",
-                "@jimp/utils": "1.3.0"
+                "@jimp/core": "1.6.0",
+                "@jimp/utils": "1.6.0"
             },
             "engines": {
                 "node": ">=18"
             }
         },
         "node_modules/@jimp/plugin-circle": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.3.0.tgz",
-            "integrity": "sha512-Lkz1uwD2wgysuu4TDzAVQ26+urr+siYlO/qXnMYHui9k0P735S6B6EsWrzssLDGOtqevQyYcx5u6h0Kv4lzehg==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.0.tgz",
+            "integrity": "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw==",
             "dependencies": {
-                "@jimp/types": "1.3.0",
+                "@jimp/types": "1.6.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3340,13 +3340,13 @@
             }
         },
         "node_modules/@jimp/plugin-color": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.3.0.tgz",
-            "integrity": "sha512-HLFtZB86W2nVnfZT+LAsyooF9efapWPmxuOKECeevunb1zHieO1ni19QXJfcqtt+cVj8UxIBGC4v9IFDJ9PGYw==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.0.tgz",
+            "integrity": "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA==",
             "dependencies": {
-                "@jimp/core": "1.3.0",
-                "@jimp/types": "1.3.0",
-                "@jimp/utils": "1.3.0",
+                "@jimp/core": "1.6.0",
+                "@jimp/types": "1.6.0",
+                "@jimp/utils": "1.6.0",
                 "tinycolor2": "^1.6.0",
                 "zod": "^3.23.8"
             },
@@ -3355,15 +3355,15 @@
             }
         },
         "node_modules/@jimp/plugin-contain": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.3.0.tgz",
-            "integrity": "sha512-1xPJ/CC4hh8IDrZFCtwQezw0RFzdrFvatzXkmfZD0cRyUXtYQ8VzExeK9MXLWi2+/nfufh+2SIhThTQ8xIzLBw==",
-            "dependencies": {
-                "@jimp/core": "1.3.0",
-                "@jimp/plugin-blit": "1.3.0",
-                "@jimp/plugin-resize": "1.3.0",
-                "@jimp/types": "1.3.0",
-                "@jimp/utils": "1.3.0",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.0.tgz",
+            "integrity": "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ==",
+            "dependencies": {
+                "@jimp/core": "1.6.0",
+                "@jimp/plugin-blit": "1.6.0",
+                "@jimp/plugin-resize": "1.6.0",
+                "@jimp/types": "1.6.0",
+                "@jimp/utils": "1.6.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3371,14 +3371,14 @@
             }
         },
         "node_modules/@jimp/plugin-cover": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.3.0.tgz",
-            "integrity": "sha512-Q8vlXepruKU+A55PS2A+d7TPwIoYthnX61ae+TQa+/4DjYk6XZA2YlmUFhq7P3RH5p288N/84ILNSIwH5YCqAw==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.0.tgz",
+            "integrity": "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA==",
             "dependencies": {
-                "@jimp/core": "1.3.0",
-                "@jimp/plugin-crop": "1.3.0",
-                "@jimp/plugin-resize": "1.3.0",
-                "@jimp/types": "1.3.0",
+                "@jimp/core": "1.6.0",
+                "@jimp/plugin-crop": "1.6.0",
+                "@jimp/plugin-resize": "1.6.0",
+                "@jimp/types": "1.6.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3386,13 +3386,13 @@
             }
         },
         "node_modules/@jimp/plugin-crop": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.3.0.tgz",
-            "integrity": "sha512-AoCTYFgcDEH+sqc2IQ5CI0CgYrQZSFfZ6q4zSXkWA+irs1nDbjJeA+0vClkYxJNSkk2wqB0fys69OBoqfDabrw==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.0.tgz",
+            "integrity": "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang==",
             "dependencies": {
-                "@jimp/core": "1.3.0",
-                "@jimp/types": "1.3.0",
-                "@jimp/utils": "1.3.0",
+                "@jimp/core": "1.6.0",
+                "@jimp/types": "1.6.0",
+                "@jimp/utils": "1.6.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3400,12 +3400,12 @@
             }
         },
         "node_modules/@jimp/plugin-displace": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.3.0.tgz",
-            "integrity": "sha512-8t/R0SjE7YWujeMLbUT2js9WIeyFbeQXxAiCPt4AJy1BUD56sbcWIx1zJzrL52eF+bG4AS8oLOp5arL+P7ocmQ==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.0.tgz",
+            "integrity": "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q==",
             "dependencies": {
-                "@jimp/types": "1.3.0",
-                "@jimp/utils": "1.3.0",
+                "@jimp/types": "1.6.0",
+                "@jimp/utils": "1.6.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3413,23 +3413,23 @@
             }
         },
         "node_modules/@jimp/plugin-dither": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.3.0.tgz",
-            "integrity": "sha512-oE6kHne88OOcJBu+fk9KvMMB71UZUO4B2YYoaHVjGbtciPG9FbBBGqgD9oULVWhHuICZdDnfgFF0hhemQuQloQ==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.0.tgz",
+            "integrity": "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ==",
             "dependencies": {
-                "@jimp/types": "1.3.0"
+                "@jimp/types": "1.6.0"
             },
             "engines": {
                 "node": ">=18"
             }
         },
         "node_modules/@jimp/plugin-fisheye": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.3.0.tgz",
-            "integrity": "sha512-VLaqY/IxrqHyjKeWpUwJZpAqug4DE26hM/8ejfPm5FmofAS1dI0deecDfbthRbw17hnPVcAiTkZ6QTiQL71Z4w==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.0.tgz",
+            "integrity": "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA==",
             "dependencies": {
-                "@jimp/types": "1.3.0",
-                "@jimp/utils": "1.3.0",
+                "@jimp/types": "1.6.0",
+                "@jimp/utils": "1.6.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3437,11 +3437,11 @@
             }
         },
         "node_modules/@jimp/plugin-flip": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.3.0.tgz",
-            "integrity": "sha512-cHeefBsjBYLbjqq8TFJHQD/6sgJLvb6XPdB8wVvKe682Y8jIilxlEhZJeUFXwsMrKKhbXNZxmtSc/pEIYCo6cA==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.0.tgz",
+            "integrity": "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg==",
             "dependencies": {
-                "@jimp/types": "1.3.0",
+                "@jimp/types": "1.6.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3449,19 +3449,19 @@
             }
         },
         "node_modules/@jimp/plugin-hash": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.3.0.tgz",
-            "integrity": "sha512-LAUi9FFT3Kp2bI2hWXQ75t1pl6q6ZyKH/jJQZg8DPL+fFK0//TIsw/g0VxW5lZoV3mHUUorQlsZzWtNi/DGtWA==",
-            "dependencies": {
-                "@jimp/core": "1.3.0",
-                "@jimp/js-bmp": "1.3.0",
-                "@jimp/js-jpeg": "1.3.0",
-                "@jimp/js-png": "1.3.0",
-                "@jimp/js-tiff": "1.3.0",
-                "@jimp/plugin-color": "1.3.0",
-                "@jimp/plugin-resize": "1.3.0",
-                "@jimp/types": "1.3.0",
-                "@jimp/utils": "1.3.0",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.0.tgz",
+            "integrity": "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q==",
+            "dependencies": {
+                "@jimp/core": "1.6.0",
+                "@jimp/js-bmp": "1.6.0",
+                "@jimp/js-jpeg": "1.6.0",
+                "@jimp/js-png": "1.6.0",
+                "@jimp/js-tiff": "1.6.0",
+                "@jimp/plugin-color": "1.6.0",
+                "@jimp/plugin-resize": "1.6.0",
+                "@jimp/types": "1.6.0",
+                "@jimp/utils": "1.6.0",
                 "any-base": "^1.1.0"
             },
             "engines": {
@@ -3469,11 +3469,11 @@
             }
         },
         "node_modules/@jimp/plugin-mask": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.3.0.tgz",
-            "integrity": "sha512-fpU6rZ75c1gD6/8zsiPQW57+doX3KfexZ3lVYToyd720HPO/qfG9lzwfY30tJVXArI4DMbG8qN7lXKgGeWwGqw==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.0.tgz",
+            "integrity": "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA==",
             "dependencies": {
-                "@jimp/types": "1.3.0",
+                "@jimp/types": "1.6.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3481,15 +3481,15 @@
             }
         },
         "node_modules/@jimp/plugin-print": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.3.0.tgz",
-            "integrity": "sha512-WeN35Fo9Bushm6VGUdQXqXrVIFDYECeKLKN+LlAqQ/YnIlUiTirPlcyGHzEBKD8uXDCmjBYqxwadcPvRDVwFEw==",
-            "dependencies": {
-                "@jimp/core": "1.3.0",
-                "@jimp/js-jpeg": "1.3.0",
-                "@jimp/js-png": "1.3.0",
-                "@jimp/plugin-blit": "1.3.0",
-                "@jimp/types": "1.3.0",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.0.tgz",
+            "integrity": "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A==",
+            "dependencies": {
+                "@jimp/core": "1.6.0",
+                "@jimp/js-jpeg": "1.6.0",
+                "@jimp/js-png": "1.6.0",
+                "@jimp/plugin-blit": "1.6.0",
+                "@jimp/types": "1.6.0",
                 "parse-bmfont-ascii": "^1.0.6",
                 "parse-bmfont-binary": "^1.0.6",
                 "parse-bmfont-xml": "^1.1.6",
@@ -3501,9 +3501,9 @@
             }
         },
         "node_modules/@jimp/plugin-quantize": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.3.0.tgz",
-            "integrity": "sha512-4Hgp5UNN8DGeX1ULNANPwlHCyuaZYZPJ/mpe/lnCN4jLI/SeBzR4g8tU+srNF6arPwRXrLNQV6T/ehAa7zhbkg==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.0.tgz",
+            "integrity": "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg==",
             "dependencies": {
                 "image-q": "^4.0.0",
                 "zod": "^3.23.8"
@@ -3513,12 +3513,12 @@
             }
         },
         "node_modules/@jimp/plugin-resize": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.3.0.tgz",
-            "integrity": "sha512-9fMw6ff/3kzDwQ2rGNYCJ2jc9IHsxQh9eaoPb4SkVHxzq+O3yka3M2Vjf41gaYhQ5Pt5QLgQ9uYUA+2kp1RF1g==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz",
+            "integrity": "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==",
             "dependencies": {
-                "@jimp/core": "1.3.0",
-                "@jimp/types": "1.3.0",
+                "@jimp/core": "1.6.0",
+                "@jimp/types": "1.6.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3526,15 +3526,15 @@
             }
         },
         "node_modules/@jimp/plugin-rotate": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.3.0.tgz",
-            "integrity": "sha512-lpzk37tzk7b5RG5U6P/E3vk+bwU86TnNZOc1LHQeTOEpfFAPMTJl6w+OlLLxVNJ7HUQege/8P47N2onQX00wXw==",
-            "dependencies": {
-                "@jimp/core": "1.3.0",
-                "@jimp/plugin-crop": "1.3.0",
-                "@jimp/plugin-resize": "1.3.0",
-                "@jimp/types": "1.3.0",
-                "@jimp/utils": "1.3.0",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.0.tgz",
+            "integrity": "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw==",
+            "dependencies": {
+                "@jimp/core": "1.6.0",
+                "@jimp/plugin-crop": "1.6.0",
+                "@jimp/plugin-resize": "1.6.0",
+                "@jimp/types": "1.6.0",
+                "@jimp/utils": "1.6.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3542,15 +3542,15 @@
             }
         },
         "node_modules/@jimp/plugin-threshold": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.3.0.tgz",
-            "integrity": "sha512-lKBzZEgjI/zM51/muGyL2juGEkK361/yFpRcmjafIijq0sHNww1rhqSa0AhO80iCmN77A4Ym/oelY0qh4mtlLQ==",
-            "dependencies": {
-                "@jimp/core": "1.3.0",
-                "@jimp/plugin-color": "1.3.0",
-                "@jimp/plugin-hash": "1.3.0",
-                "@jimp/types": "1.3.0",
-                "@jimp/utils": "1.3.0",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.0.tgz",
+            "integrity": "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w==",
+            "dependencies": {
+                "@jimp/core": "1.6.0",
+                "@jimp/plugin-color": "1.6.0",
+                "@jimp/plugin-hash": "1.6.0",
+                "@jimp/types": "1.6.0",
+                "@jimp/utils": "1.6.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3558,9 +3558,9 @@
             }
         },
         "node_modules/@jimp/types": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.3.0.tgz",
-            "integrity": "sha512-K4RaTmDTqZqbjjwOtxzVH9QyCgQBukXjKOmdgNuCmu7ugrpeTeWV7SvrwZZPhTt31lmja8A3a3Dw1ScVZdtmyA==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.0.tgz",
+            "integrity": "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg==",
             "dependencies": {
                 "zod": "^3.23.8"
             },
@@ -3569,11 +3569,11 @@
             }
         },
         "node_modules/@jimp/utils": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.3.0.tgz",
-            "integrity": "sha512-QIye6IWJaQ3Q9+6rxgQiFI1I7MwrQZYxlhYhPolJv+BfCXBT8XWJymV8J75vlnjvz3kN2AMXAEU7c7w0h2Tz3Q==",
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.0.tgz",
+            "integrity": "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA==",
             "dependencies": {
-                "@jimp/types": "1.3.0",
+                "@jimp/types": "1.6.0",
                 "tinycolor2": "^1.6.0"
             },
             "engines": {
@@ -3881,20 +3881,20 @@
             }
         },
         "node_modules/@mui/core-downloads-tracker": {
-            "version": "6.0.2",
-            "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.0.2.tgz",
-            "integrity": "sha512-Cg68oOlAfbJgMgvbCwcX3Y3HdygCl6X1nREYTdEWcEKUQhNarrC45Cc35mP+zA7p3ZXE/7FLiaTCCgwuSoef/Q==",
+            "version": "6.1.0",
+            "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.0.tgz",
+            "integrity": "sha512-covEnIn/2er5YdtuukDRA52kmARhKrHjOvPsyTFMQApZdrTBI4h8jbEy2mxZqwMwcAFS9coonQXnEZKL1rUNdQ==",
             "funding": {
                 "type": "opencollective",
                 "url": "https://opencollective.com/mui-org"
             }
         },
         "node_modules/@mui/icons-material": {
-            "version": "6.0.2",
-            "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.0.2.tgz",
-            "integrity": "sha512-WaTPSvKcx8X7NdWAHzJWDZv+YXvK0MUY8+JI/r4/q2GgIa5RW+n4+08CGX6jB7sWhU1R3zy28NfsDUwwQjOThw==",
+            "version": "6.1.0",
+            "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.0.tgz",
+            "integrity": "sha512-HxfB0jxwiMTYMN8gAnYn3avbF1aDrqBEuGIj6JDQ3YkLl650E1Wy8AIhwwyP47wdrv0at9aAR0iOO6VLb74A9w==",
             "dependencies": {
-                "@babel/runtime": "^7.25.0"
+                "@babel/runtime": "^7.25.6"
             },
             "engines": {
                 "node": ">=14.0.0"
@@ -3904,7 +3904,7 @@
                 "url": "https://opencollective.com/mui-org"
             },
             "peerDependencies": {
-                "@mui/material": "^6.0.2",
+                "@mui/material": "^6.1.0",
                 "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
                 "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
             },
@@ -3915,15 +3915,15 @@
             }
         },
         "node_modules/@mui/material": {
-            "version": "6.0.2",
-            "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.0.2.tgz",
-            "integrity": "sha512-KrnkJFSyhsAh8V30DNUbWyRyxMi4ZHjFg1ikQGx+mUAIffFTYIEx9Q+Kxd3vCT0FUFGOmbsuh6F6yRhpybsjkg==",
+            "version": "6.1.0",
+            "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.0.tgz",
+            "integrity": "sha512-4MJ46vmy1xbm8x+ZdRcWm8jEMMowdS8pYlhKQzg/qoKhOcLhImZvf2Jn6z9Dj6gl+lY+C/0MxaHF/avAAGys3Q==",
             "dependencies": {
-                "@babel/runtime": "^7.25.0",
-                "@mui/core-downloads-tracker": "^6.0.2",
-                "@mui/system": "^6.0.2",
+                "@babel/runtime": "^7.25.6",
+                "@mui/core-downloads-tracker": "^6.1.0",
+                "@mui/system": "^6.1.0",
                 "@mui/types": "^7.2.16",
-                "@mui/utils": "^6.0.2",
+                "@mui/utils": "^6.1.0",
                 "@popperjs/core": "^2.11.8",
                 "@types/react-transition-group": "^4.4.11",
                 "clsx": "^2.1.1",
@@ -3942,7 +3942,7 @@
             "peerDependencies": {
                 "@emotion/react": "^11.5.0",
                 "@emotion/styled": "^11.3.0",
-                "@mui/material-pigment-css": "^6.0.2",
+                "@mui/material-pigment-css": "^6.1.0",
                 "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
                 "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
                 "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -3963,12 +3963,12 @@
             }
         },
         "node_modules/@mui/private-theming": {
-            "version": "6.0.2",
-            "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.0.2.tgz",
-            "integrity": "sha512-emddFcRhA0hPGVIwIbW5g0V8vtCgw2g/H/A7jTdGe7dpCWEPpp6jPIXRRKcEUWgmg91R6rBNfV+LFHxBxmZXOQ==",
+            "version": "6.1.0",
+            "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.0.tgz",
+            "integrity": "sha512-+L5qccs4gwsR0r1dgjqhN24QEQRkqIbfOdxILyMbMkuI50x6wNyt9XrV+J3WtjtZTMGJCrUa5VmZBE6OEPGPWA==",
             "dependencies": {
-                "@babel/runtime": "^7.25.0",
-                "@mui/utils": "^6.0.2",
+                "@babel/runtime": "^7.25.6",
+                "@mui/utils": "^6.1.0",
                 "prop-types": "^15.8.1"
             },
             "engines": {
@@ -3989,12 +3989,13 @@
             }
         },
         "node_modules/@mui/styled-engine": {
-            "version": "6.0.2",
-            "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.0.2.tgz",
-            "integrity": "sha512-qd3Vlhted0SYVGotnCfVNcxff7vW2WN0fclbAexff60NeNS1qs/H/CImHEHUBiUGeNWMPRochbN6VF1arQ7/jA==",
+            "version": "6.1.0",
+            "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.0.tgz",
+            "integrity": "sha512-MZ+vtaCkjamrT41+b0Er9OMenjAtP/32+L6fARL9/+BZKuV2QbR3q3TmavT2x0NhDu35IM03s4yKqj32Ziqnyg==",
             "dependencies": {
-                "@babel/runtime": "^7.25.0",
+                "@babel/runtime": "^7.25.6",
                 "@emotion/cache": "^11.13.1",
+                "@emotion/sheet": "^1.4.0",
                 "csstype": "^3.1.3",
                 "prop-types": "^15.8.1"
             },
@@ -4020,15 +4021,15 @@
             }
         },
         "node_modules/@mui/system": {
-            "version": "6.0.2",
-            "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.0.2.tgz",
-            "integrity": "sha512-AZv1/C4PuHgWFTA8YraIzl3FTVLdRz0RIMRwEADWZBdIhnuTHS/4+r8qE9+3CcpTHg1WsEu8btaO3AhQahSM9A==",
+            "version": "6.1.0",
+            "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.0.tgz",
+            "integrity": "sha512-NumkGDqT6EdXfcoFLYQ+M4XlTW5hH3+aK48xAbRqKPXJfxl36CBt4DLduw/Voa5dcayGus9T6jm1AwU2hoJ5hQ==",
             "dependencies": {
-                "@babel/runtime": "^7.25.0",
-                "@mui/private-theming": "^6.0.2",
-                "@mui/styled-engine": "^6.0.2",
+                "@babel/runtime": "^7.25.6",
+                "@mui/private-theming": "^6.1.0",
+                "@mui/styled-engine": "^6.1.0",
                 "@mui/types": "^7.2.16",
-                "@mui/utils": "^6.0.2",
+                "@mui/utils": "^6.1.0",
                 "clsx": "^2.1.1",
                 "csstype": "^3.1.3",
                 "prop-types": "^15.8.1"
@@ -4072,11 +4073,11 @@
             }
         },
         "node_modules/@mui/utils": {
-            "version": "6.0.2",
-            "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.0.2.tgz",
-            "integrity": "sha512-TeFrYsxcmeoDSlkoPhX+LjIuuqC5Pyj+xz2kRceKCkUpwMNTEeVOfowXDPe+mboZwmpJ5ZxP4eiAgQMdeEasjg==",
+            "version": "6.1.0",
+            "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.0.tgz",
+            "integrity": "sha512-oT8ZzMISRUhTVpdbYzY0CgrCBb3t/YEdcaM13tUnuTjZ15pdA6g5lx15ZJUdgYXV6PbJdw7tDQgMEr4uXK5TXQ==",
             "dependencies": {
-                "@babel/runtime": "^7.25.0",
+                "@babel/runtime": "^7.25.6",
                 "@mui/types": "^7.2.16",
                 "@types/prop-types": "^15.7.12",
                 "clsx": "^2.1.1",
@@ -7210,9 +7211,9 @@
             "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="
         },
         "node_modules/@sindresorhus/is": {
-            "version": "7.0.0",
-            "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.0.0.tgz",
-            "integrity": "sha512-WDTlVTyvFivSOuyvMeedzg2hdoBLZ3f1uNVuEida2Rl9BrfjrIRjWA/VZIrMRLvSwJYCAlCRA3usDt1THytxWQ==",
+            "version": "7.0.1",
+            "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.0.1.tgz",
+            "integrity": "sha512-QWLl2P+rsCJeofkDNIT3WFmb6NrRud1SUYW8dIhXK/46XFV8Q/g7Bsvib0Askb0reRLe+WYPeeE+l5cH7SlkuQ==",
             "engines": {
                 "node": ">=18"
             },
@@ -9579,9 +9580,9 @@
             "dev": true
         },
         "node_modules/@types/mocha": {
-            "version": "10.0.7",
-            "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.7.tgz",
-            "integrity": "sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==",
+            "version": "10.0.8",
+            "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.8.tgz",
+            "integrity": "sha512-HfMcUmy9hTMJh66VNcmeC9iVErIZJli2bszuXc6julh5YGuRb/W5OnkHjwLNYdFlMis0sY3If5SEAp+PktdJjw==",
             "dev": true
         },
         "node_modules/@types/ms": {
@@ -9590,9 +9591,9 @@
             "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
         },
         "node_modules/@types/node": {
-            "version": "22.5.4",
-            "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
-            "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
+            "version": "22.5.5",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz",
+            "integrity": "sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==",
             "dependencies": {
                 "undici-types": "~6.19.2"
             }
@@ -9616,9 +9617,9 @@
             }
         },
         "node_modules/@types/nodemailer": {
-            "version": "6.4.15",
-            "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.15.tgz",
-            "integrity": "sha512-0EBJxawVNjPkng1zm2vopRctuWVCxk34JcIlRuXSf54habUWdz1FB7wHDqOqvDa8Mtpt0Q3LTXQkAs2LNyK5jQ==",
+            "version": "6.4.16",
+            "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.16.tgz",
+            "integrity": "sha512-uz6hN6Pp0upXMcilM61CoKyjT7sskBoOWpptkjjJp8jIMlTdc3xG01U7proKkXzruMS4hS0zqtHNkNPFB20rKQ==",
             "dev": true,
             "dependencies": {
                 "@types/node": "*"
@@ -9706,14 +9707,15 @@
             "integrity": "sha512-+gbBHbNCVGGYw1S9lAIIvrHW47UYOhMIFUsJcMkMrzy1Jf0vulBN3XQIjPgnoOXveMuHnF3b57fXROnY/Or7eg=="
         },
         "node_modules/@types/prop-types": {
-            "version": "15.7.12",
-            "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
-            "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
+            "version": "15.7.13",
+            "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
+            "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA=="
         },
         "node_modules/@types/qs": {
-            "version": "6.9.15",
-            "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz",
-            "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg=="
+            "version": "6.9.16",
+            "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz",
+            "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==",
+            "dev": true
         },
         "node_modules/@types/range-parser": {
             "version": "1.2.7",
@@ -9731,9 +9733,9 @@
             }
         },
         "node_modules/@types/react": {
-            "version": "18.3.5",
-            "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.5.tgz",
-            "integrity": "sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==",
+            "version": "18.3.7",
+            "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.7.tgz",
+            "integrity": "sha512-KUnDCJF5+AiZd8owLIeVHqmW9yM4sqmDVf2JRJiBMFkGvkoZ4/WyV2lL4zVsoinmRS/W3FeEdZLEWFRofnT2FQ==",
             "dependencies": {
                 "@types/prop-types": "*",
                 "csstype": "^3.0.2"
@@ -9991,9 +9993,9 @@
             "dev": true
         },
         "node_modules/@types/web": {
-            "version": "0.0.159",
-            "resolved": "https://registry.npmjs.org/@types/web/-/web-0.0.159.tgz",
-            "integrity": "sha512-BHPaU+yHqHOrua8iFksPmgLCXEt1LE/2sPB+MPTRuxDFm4z6gBgmDiXUCGleyHiOLT6R+fklgR99hq/iFGVO1w=="
+            "version": "0.0.163",
+            "resolved": "https://registry.npmjs.org/@types/web/-/web-0.0.163.tgz",
+            "integrity": "sha512-5Pg2gKfulo186wFnv+YXx0luJGWQ94cCY2/Dy8lU5WAE50FdBoOK45uBbp8FceOSpLJ4UW3dmTW5tvsN9uuX7A=="
         },
         "node_modules/@types/webgl-ext": {
             "version": "0.0.30",
@@ -10303,55 +10305,55 @@
             "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="
         },
         "node_modules/@vue/compiler-core": {
-            "version": "3.5.1",
-            "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.1.tgz",
-            "integrity": "sha512-WdjF+NSgFYdWttHevHw5uaJFtKPalhmxhlu2uREj8cLP0uyKKIR60/JvSZNTp0x+NSd63iTiORQTx3+tt55NWQ==",
+            "version": "3.5.6",
+            "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.6.tgz",
+            "integrity": "sha512-r+gNu6K4lrvaQLQGmf+1gc41p3FO2OUJyWmNqaIITaJU6YFiV5PtQSFZt8jfztYyARwqhoCayjprC7KMvT3nRA==",
             "dependencies": {
                 "@babel/parser": "^7.25.3",
-                "@vue/shared": "3.5.1",
+                "@vue/shared": "3.5.6",
                 "entities": "^4.5.0",
                 "estree-walker": "^2.0.2",
                 "source-map-js": "^1.2.0"
             }
         },
         "node_modules/@vue/compiler-dom": {
-            "version": "3.5.1",
-            "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.1.tgz",
-            "integrity": "sha512-Ao23fB1lINo18HLCbJVApvzd9OQe8MgmQSgyY5+umbWj2w92w9KykVmJ4Iv2US5nak3ixc2B+7Km7JTNhQ8kSQ==",
+            "version": "3.5.6",
+            "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.6.tgz",
+            "integrity": "sha512-xRXqxDrIqK8v8sSScpistyYH0qYqxakpsIvqMD2e5sV/PXQ1mTwtXp4k42yHK06KXxKSmitop9e45Ui/3BrTEw==",
             "dependencies": {
-                "@vue/compiler-core": "3.5.1",
-                "@vue/shared": "3.5.1"
+                "@vue/compiler-core": "3.5.6",
+                "@vue/shared": "3.5.6"
             }
         },
         "node_modules/@vue/compiler-sfc": {
-            "version": "3.5.1",
-            "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.1.tgz",
-            "integrity": "sha512-DFizMNH8eDglLhlfwJ0+ciBsztaYe3fY/zcZjrqL1ljXvUw/UpC84M1d7HpBTCW68SNqZyIxrs1XWmf+73Y65w==",
+            "version": "3.5.6",
+            "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.6.tgz",
+            "integrity": "sha512-pjWJ8Kj9TDHlbF5LywjVso+BIxCY5wVOLhkEXRhuCHDxPFIeX1zaFefKs8RYoHvkSMqRWt93a0f2gNJVJixHwg==",
             "dependencies": {
                 "@babel/parser": "^7.25.3",
-                "@vue/compiler-core": "3.5.1",
-                "@vue/compiler-dom": "3.5.1",
-                "@vue/compiler-ssr": "3.5.1",
-                "@vue/shared": "3.5.1",
+                "@vue/compiler-core": "3.5.6",
+                "@vue/compiler-dom": "3.5.6",
+                "@vue/compiler-ssr": "3.5.6",
+                "@vue/shared": "3.5.6",
                 "estree-walker": "^2.0.2",
                 "magic-string": "^0.30.11",
-                "postcss": "^8.4.44",
+                "postcss": "^8.4.47",
                 "source-map-js": "^1.2.0"
             }
         },
         "node_modules/@vue/compiler-ssr": {
-            "version": "3.5.1",
-            "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.1.tgz",
-            "integrity": "sha512-C1hpSHQgRM8bg+5XWWD7CkFaVpSn9wZHCLRd10AmxqrH17d4EMP6+XcZpwBOM7H1jeStU5naEapZZWX0kso1tQ==",
+            "version": "3.5.6",
+            "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.6.tgz",
+            "integrity": "sha512-VpWbaZrEOCqnmqjE83xdwegtr5qO/2OPUC6veWgvNqTJ3bYysz6vY3VqMuOijubuUYPRpG3OOKIh9TD0Stxb9A==",
             "dependencies": {
-                "@vue/compiler-dom": "3.5.1",
-                "@vue/shared": "3.5.1"
+                "@vue/compiler-dom": "3.5.6",
+                "@vue/shared": "3.5.6"
             }
         },
         "node_modules/@vue/shared": {
-            "version": "3.5.1",
-            "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.1.tgz",
-            "integrity": "sha512-NdcTRoO4KuW2RSFgpE2c+E/R/ZHaRzWPxAGxhmxZaaqLh6nYCXx7lc9a88ioqOCxCaV2SFJmujkxbUScW7dNsQ=="
+            "version": "3.5.6",
+            "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.6.tgz",
+            "integrity": "sha512-eidH0HInnL39z6wAt6SFIwBrvGOpDWsDxlw3rCgo1B+CQ1781WzQUSU3YjxgdkcJo9Q8S6LmXTkvI+cLHGkQfA=="
         },
         "node_modules/@webassemblyjs/ast": {
             "version": "1.12.1",
@@ -10610,9 +10612,9 @@
             }
         },
         "node_modules/acorn-walk": {
-            "version": "8.3.3",
-            "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz",
-            "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==",
+            "version": "8.3.4",
+            "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+            "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
             "dev": true,
             "dependencies": {
                 "acorn": "^8.11.0"
@@ -11168,9 +11170,9 @@
             }
         },
         "node_modules/babel-loader": {
-            "version": "9.1.3",
-            "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz",
-            "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==",
+            "version": "9.2.1",
+            "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz",
+            "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==",
             "dependencies": {
                 "find-cache-dir": "^4.0.0",
                 "schema-utils": "^4.0.0"
@@ -11442,9 +11444,9 @@
             "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
         },
         "node_modules/body-parser": {
-            "version": "1.20.2",
-            "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
-            "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
+            "version": "1.20.3",
+            "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
+            "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
             "dependencies": {
                 "bytes": "3.1.2",
                 "content-type": "~1.0.5",
@@ -11454,7 +11456,7 @@
                 "http-errors": "2.0.0",
                 "iconv-lite": "0.4.24",
                 "on-finished": "2.4.1",
-                "qs": "6.11.0",
+                "qs": "6.13.0",
                 "raw-body": "2.5.2",
                 "type-is": "~1.6.18",
                 "unpipe": "1.0.0"
@@ -15064,9 +15066,9 @@
             }
         },
         "node_modules/caniuse-lite": {
-            "version": "1.0.30001655",
-            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz",
-            "integrity": "sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg==",
+            "version": "1.0.30001660",
+            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz",
+            "integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==",
             "funding": [
                 {
                     "type": "opencollective",
@@ -16183,23 +16185,17 @@
             }
         },
         "node_modules/cssstyle": {
-            "version": "4.0.1",
-            "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.0.1.tgz",
-            "integrity": "sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==",
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz",
+            "integrity": "sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==",
             "dev": true,
             "dependencies": {
-                "rrweb-cssom": "^0.6.0"
+                "rrweb-cssom": "^0.7.1"
             },
             "engines": {
                 "node": ">=18"
             }
         },
-        "node_modules/cssstyle/node_modules/rrweb-cssom": {
-            "version": "0.6.0",
-            "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz",
-            "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==",
-            "dev": true
-        },
         "node_modules/csstype": {
             "version": "3.1.3",
             "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@@ -16835,11 +16831,11 @@
             "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
         },
         "node_modules/debug": {
-            "version": "4.3.6",
-            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
-            "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
+            "version": "4.3.7",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+            "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
             "dependencies": {
-                "ms": "2.1.2"
+                "ms": "^2.1.3"
             },
             "engines": {
                 "node": ">=6.0"
@@ -17376,9 +17372,9 @@
             "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
         },
         "node_modules/electron-to-chromium": {
-            "version": "1.5.13",
-            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz",
-            "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q=="
+            "version": "1.5.24",
+            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.24.tgz",
+            "integrity": "sha512-0x0wLCmpdKFCi9ulhvYZebgcPmHTkFVUfU2wzDykadkslKwT4oAmDTHEKLnlrDsMGZe4B+ksn8quZfZjYsBetA=="
         },
         "node_modules/emoji-regex": {
             "version": "9.2.2",
@@ -17394,9 +17390,9 @@
             }
         },
         "node_modules/encodeurl": {
-            "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
-            "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+            "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
             "engines": {
                 "node": ">= 0.8"
             }
@@ -17505,9 +17501,9 @@
             }
         },
         "node_modules/envinfo": {
-            "version": "7.13.0",
-            "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz",
-            "integrity": "sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==",
+            "version": "7.14.0",
+            "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz",
+            "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==",
             "bin": {
                 "envinfo": "dist/cli.js"
             },
@@ -18135,36 +18131,36 @@
             }
         },
         "node_modules/express": {
-            "version": "4.19.2",
-            "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
-            "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
+            "version": "4.21.0",
+            "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
+            "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
             "dependencies": {
                 "accepts": "~1.3.8",
                 "array-flatten": "1.1.1",
-                "body-parser": "1.20.2",
+                "body-parser": "1.20.3",
                 "content-disposition": "0.5.4",
                 "content-type": "~1.0.4",
                 "cookie": "0.6.0",
                 "cookie-signature": "1.0.6",
                 "debug": "2.6.9",
                 "depd": "2.0.0",
-                "encodeurl": "~1.0.2",
+                "encodeurl": "~2.0.0",
                 "escape-html": "~1.0.3",
                 "etag": "~1.8.1",
-                "finalhandler": "1.2.0",
+                "finalhandler": "1.3.1",
                 "fresh": "0.5.2",
                 "http-errors": "2.0.0",
-                "merge-descriptors": "1.0.1",
+                "merge-descriptors": "1.0.3",
                 "methods": "~1.1.2",
                 "on-finished": "2.4.1",
                 "parseurl": "~1.3.3",
-                "path-to-regexp": "0.1.7",
+                "path-to-regexp": "0.1.10",
                 "proxy-addr": "~2.0.7",
-                "qs": "6.11.0",
+                "qs": "6.13.0",
                 "range-parser": "~1.2.1",
                 "safe-buffer": "5.2.1",
-                "send": "0.18.0",
-                "serve-static": "1.15.0",
+                "send": "0.19.0",
+                "serve-static": "1.16.2",
                 "setprototypeof": "1.2.0",
                 "statuses": "2.0.1",
                 "type-is": "~1.6.18",
@@ -18527,12 +18523,12 @@
             }
         },
         "node_modules/finalhandler": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
-            "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+            "version": "1.3.1",
+            "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
+            "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
             "dependencies": {
                 "debug": "2.6.9",
-                "encodeurl": "~1.0.2",
+                "encodeurl": "~2.0.0",
                 "escape-html": "~1.0.3",
                 "on-finished": "2.4.1",
                 "parseurl": "~1.3.3",
@@ -18653,9 +18649,9 @@
             "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw=="
         },
         "node_modules/flexlayout-react": {
-            "version": "0.7.15",
-            "resolved": "https://registry.npmjs.org/flexlayout-react/-/flexlayout-react-0.7.15.tgz",
-            "integrity": "sha512-ydTMdEoQO5BniylxVkSxa59rEY0+96lqqRII+QK+yq6028eHywPuxZawt4g45y5pMb9ptP4N9HPAQXAFsxwowQ==",
+            "version": "0.8.0",
+            "resolved": "https://registry.npmjs.org/flexlayout-react/-/flexlayout-react-0.8.0.tgz",
+            "integrity": "sha512-nTkEOxVw438/6ugzKb0nBFRzJkWPYOgx8laGhDmTsXMuJQMEEC4dxyRSswcjt8IWsCKl8zSQHlsxSG+PrdJENQ==",
             "peerDependencies": {
                 "react": "^18.0.0",
                 "react-dom": "^18.0.0"
@@ -18690,9 +18686,9 @@
             }
         },
         "node_modules/follow-redirects": {
-            "version": "1.15.8",
-            "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.8.tgz",
-            "integrity": "sha512-xgrmBhBToVKay1q2Tao5LI26B83UhrB/vM1avwVSDzt8rx3rO6AizBAaF46EgksTVr+rFTQaqZZ9MVBfUe4nig==",
+            "version": "1.15.9",
+            "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+            "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
             "funding": [
                 {
                     "type": "individual",
@@ -19591,9 +19587,9 @@
             }
         },
         "node_modules/googleapis": {
-            "version": "142.0.0",
-            "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-142.0.0.tgz",
-            "integrity": "sha512-LsU1ynez4/KNPwnFMSDI93pBEsETNdQPCrT3kz2qgiNg5H2pW4dKW+1VmENMkZ4u9lMxA89nnXD3nqWBJ0rruQ==",
+            "version": "144.0.0",
+            "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-144.0.0.tgz",
+            "integrity": "sha512-ELcWOXtJxjPX4vsKMh+7V+jZvgPwYMlEhQFiu2sa9Qmt5veX8nwXPksOWGGN6Zk4xCiLygUyaz7xGtcMO+Onxw==",
             "dependencies": {
                 "google-auth-library": "^9.0.0",
                 "googleapis-common": "^7.0.0"
@@ -19833,9 +19829,9 @@
             }
         },
         "node_modules/hast-util-from-html": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.2.tgz",
-            "integrity": "sha512-HwOHwxdt2zC5KQ/CNoybBntRook2zJvfZE/u5/Ap7aLPe22bDqen7KwGkOqOyzL5zIqKwiYX/OTtE0FWgr6XXA==",
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz",
+            "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==",
             "dependencies": {
                 "@types/hast": "^3.0.0",
                 "devlop": "^1.1.0",
@@ -20805,9 +20801,9 @@
             "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
         },
         "node_modules/inline-style-parser": {
-            "version": "0.2.3",
-            "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.3.tgz",
-            "integrity": "sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g=="
+            "version": "0.2.4",
+            "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz",
+            "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="
         },
         "node_modules/inspect-function": {
             "version": "0.3.4",
@@ -21775,37 +21771,37 @@
             }
         },
         "node_modules/jimp": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.3.0.tgz",
-            "integrity": "sha512-eJnVMuqDQ545taNLp13gVZynnoOvE0xZ2Oti9alkld47dNhmFHBmFTBMTYaZr7zceGTf54RGdr7C4d2WUNwc0g==",
-            "dependencies": {
-                "@jimp/core": "1.3.0",
-                "@jimp/diff": "1.3.0",
-                "@jimp/js-bmp": "1.3.0",
-                "@jimp/js-gif": "1.3.0",
-                "@jimp/js-jpeg": "1.3.0",
-                "@jimp/js-png": "1.3.0",
-                "@jimp/js-tiff": "1.3.0",
-                "@jimp/plugin-blit": "1.3.0",
-                "@jimp/plugin-blur": "1.3.0",
-                "@jimp/plugin-circle": "1.3.0",
-                "@jimp/plugin-color": "1.3.0",
-                "@jimp/plugin-contain": "1.3.0",
-                "@jimp/plugin-cover": "1.3.0",
-                "@jimp/plugin-crop": "1.3.0",
-                "@jimp/plugin-displace": "1.3.0",
-                "@jimp/plugin-dither": "1.3.0",
-                "@jimp/plugin-fisheye": "1.3.0",
-                "@jimp/plugin-flip": "1.3.0",
-                "@jimp/plugin-hash": "1.3.0",
-                "@jimp/plugin-mask": "1.3.0",
-                "@jimp/plugin-print": "1.3.0",
-                "@jimp/plugin-quantize": "1.3.0",
-                "@jimp/plugin-resize": "1.3.0",
-                "@jimp/plugin-rotate": "1.3.0",
-                "@jimp/plugin-threshold": "1.3.0",
-                "@jimp/types": "1.3.0",
-                "@jimp/utils": "1.3.0"
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.0.tgz",
+            "integrity": "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg==",
+            "dependencies": {
+                "@jimp/core": "1.6.0",
+                "@jimp/diff": "1.6.0",
+                "@jimp/js-bmp": "1.6.0",
+                "@jimp/js-gif": "1.6.0",
+                "@jimp/js-jpeg": "1.6.0",
+                "@jimp/js-png": "1.6.0",
+                "@jimp/js-tiff": "1.6.0",
+                "@jimp/plugin-blit": "1.6.0",
+                "@jimp/plugin-blur": "1.6.0",
+                "@jimp/plugin-circle": "1.6.0",
+                "@jimp/plugin-color": "1.6.0",
+                "@jimp/plugin-contain": "1.6.0",
+                "@jimp/plugin-cover": "1.6.0",
+                "@jimp/plugin-crop": "1.6.0",
+                "@jimp/plugin-displace": "1.6.0",
+                "@jimp/plugin-dither": "1.6.0",
+                "@jimp/plugin-fisheye": "1.6.0",
+                "@jimp/plugin-flip": "1.6.0",
+                "@jimp/plugin-hash": "1.6.0",
+                "@jimp/plugin-mask": "1.6.0",
+                "@jimp/plugin-print": "1.6.0",
+                "@jimp/plugin-quantize": "1.6.0",
+                "@jimp/plugin-resize": "1.6.0",
+                "@jimp/plugin-rotate": "1.6.0",
+                "@jimp/plugin-threshold": "1.6.0",
+                "@jimp/types": "1.6.0",
+                "@jimp/utils": "1.6.0"
             },
             "engines": {
                 "node": ">=18"
@@ -21879,9 +21875,9 @@
             "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
         },
         "node_modules/js-datepicker": {
-            "version": "5.18.2",
-            "resolved": "https://registry.npmjs.org/js-datepicker/-/js-datepicker-5.18.2.tgz",
-            "integrity": "sha512-lBh5tIwb1ewDVlNHuwczoLiMhTFWnGYuHSA7ONAPKyMeofIFZDFGeOZ71UTY/Mk2evJJt+L66ec/RiXUy8XzEg=="
+            "version": "5.18.3",
+            "resolved": "https://registry.npmjs.org/js-datepicker/-/js-datepicker-5.18.3.tgz",
+            "integrity": "sha512-M1+UKeRkLT0p04JhnSpNugveFSfy6VfEeNTyAyFef6unKIqQx4oQqx66lsBi0FRKHb4HtQsmJ8V4L4SPj1lbRQ=="
         },
         "node_modules/js-stringify": {
             "version": "1.0.2",
@@ -22293,9 +22289,9 @@
             }
         },
         "node_modules/launch-editor": {
-            "version": "2.8.2",
-            "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.8.2.tgz",
-            "integrity": "sha512-eF5slEUZXmi6WvFzI3dYcv+hA24/iKnROf24HztcURJpSz9RBmBgz5cNCVOeguouf1llrwy6Yctl4C4HM+xI8g==",
+            "version": "2.9.1",
+            "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.9.1.tgz",
+            "integrity": "sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==",
             "dev": true,
             "dependencies": {
                 "picocolors": "^1.0.0",
@@ -23024,9 +23020,9 @@
             }
         },
         "node_modules/mdast-util-mdx-expression": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.0.tgz",
-            "integrity": "sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==",
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
+            "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==",
             "dependencies": {
                 "@types/estree-jsx": "^1.0.0",
                 "@types/hast": "^3.0.0",
@@ -23187,9 +23183,12 @@
             }
         },
         "node_modules/merge-descriptors": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
-            "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+            "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
         },
         "node_modules/merge-stream": {
             "version": "2.0.0",
@@ -23206,9 +23205,9 @@
             }
         },
         "node_modules/mermaid": {
-            "version": "11.1.0",
-            "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.1.0.tgz",
-            "integrity": "sha512-ICexrwPRzU1USFcpAdrVVGjCwEajD+iAwu2LVHi59D6VbXmFhwfB9TbCL3sA6NBR1tl5qUjQSAOdc9lOKlXnEw==",
+            "version": "11.2.1",
+            "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.2.1.tgz",
+            "integrity": "sha512-F8TEaLVVyxTUmvKswVFyOkjPrlJA5h5vNR1f7ZnSWSpqxgEZG1hggtn/QCa7znC28bhlcrNh10qYaIiill7q4A==",
             "dependencies": {
                 "@braintree/sanitize-url": "^7.0.1",
                 "@iconify/utils": "^2.1.32",
@@ -23963,9 +23962,9 @@
             "integrity": "sha512-yc0LhH6tItlvfLBugVUEtgawwFU2sIe+cSdmRJJCTMZ5GEJyLxNyC/NIOAOGk67Fa8GNpOttO3Xz/1bHpXFD/g=="
         },
         "node_modules/mobx": {
-            "version": "6.13.1",
-            "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.13.1.tgz",
-            "integrity": "sha512-ekLRxgjWJr8hVxj9ZKuClPwM/iHckx3euIJ3Np7zLVNtqJvfbbq7l370W/98C8EabdQ1pB5Jd3BbDWxJPNnaOg==",
+            "version": "6.13.2",
+            "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.13.2.tgz",
+            "integrity": "sha512-GIubI2qf+P6lG6rSEG0T2pg3jV9/0+O0ncF09+0umRe75+Cbnh1KNLM1GvbTY9RSc7QuU+LcPNZfxDY8B+3XRg==",
             "funding": {
                 "type": "opencollective",
                 "url": "https://opencollective.com/mobx"
@@ -24121,12 +24120,6 @@
                 "node": ">=10"
             }
         },
-        "node_modules/mocha/node_modules/ms": {
-            "version": "2.1.3",
-            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
-            "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
-            "dev": true
-        },
         "node_modules/mocha/node_modules/supports-color": {
             "version": "8.1.1",
             "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
@@ -24143,9 +24136,9 @@
             }
         },
         "node_modules/mongodb": {
-            "version": "6.8.0",
-            "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.8.0.tgz",
-            "integrity": "sha512-HGQ9NWDle5WvwMnrvUxsFYPd3JEbqD3RgABHBQRuoCEND0qzhsd0iH5ypHsf1eJ+sXmvmyKpP+FLOKY8Il7jMw==",
+            "version": "6.9.0",
+            "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.9.0.tgz",
+            "integrity": "sha512-UMopBVx1LmEUbW/QE0Hw18u583PEDVQmUmVzzBRH0o/xtE9DBRA5ZYLOjpLIa03i8FXjzvQECJcqoMvCXftTUA==",
             "dependencies": {
                 "@mongodb-js/saslprep": "^1.1.5",
                 "bson": "^6.7.0",
@@ -24220,9 +24213,9 @@
             }
         },
         "node_modules/mongoose": {
-            "version": "8.6.1",
-            "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.6.1.tgz",
-            "integrity": "sha512-dppGcYqvsdg+VcnqXR5b467V4a+iNhmvkfYNpEPi6AjaUxnz6ioEDmrMLOi+sOWjvoHapuwPOigV4f2l7HC6ag==",
+            "version": "8.6.3",
+            "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.6.3.tgz",
+            "integrity": "sha512-++yRmm7hjMbqVA/8WeiygTnEfrFbiy+OBjQi49GFJIvCQuSYE56myyQWo4j5hbpcHjhHQU8NukMNGTwAWFWjIw==",
             "dependencies": {
                 "bson": "^6.7.0",
                 "kareem": "2.6.3",
@@ -24240,10 +24233,50 @@
                 "url": "https://opencollective.com/mongoose"
             }
         },
-        "node_modules/mongoose/node_modules/ms": {
-            "version": "2.1.3",
-            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
-            "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+        "node_modules/mongoose/node_modules/mongodb": {
+            "version": "6.8.0",
+            "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.8.0.tgz",
+            "integrity": "sha512-HGQ9NWDle5WvwMnrvUxsFYPd3JEbqD3RgABHBQRuoCEND0qzhsd0iH5ypHsf1eJ+sXmvmyKpP+FLOKY8Il7jMw==",
+            "dependencies": {
+                "@mongodb-js/saslprep": "^1.1.5",
+                "bson": "^6.7.0",
+                "mongodb-connection-string-url": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=16.20.1"
+            },
+            "peerDependencies": {
+                "@aws-sdk/credential-providers": "^3.188.0",
+                "@mongodb-js/zstd": "^1.1.0",
+                "gcp-metadata": "^5.2.0",
+                "kerberos": "^2.0.1",
+                "mongodb-client-encryption": ">=6.0.0 <7",
+                "snappy": "^7.2.2",
+                "socks": "^2.7.1"
+            },
+            "peerDependenciesMeta": {
+                "@aws-sdk/credential-providers": {
+                    "optional": true
+                },
+                "@mongodb-js/zstd": {
+                    "optional": true
+                },
+                "gcp-metadata": {
+                    "optional": true
+                },
+                "kerberos": {
+                    "optional": true
+                },
+                "mongodb-client-encryption": {
+                    "optional": true
+                },
+                "snappy": {
+                    "optional": true
+                },
+                "socks": {
+                    "optional": true
+                }
+            }
         },
         "node_modules/mpath": {
             "version": "0.9.0",
@@ -24270,9 +24303,9 @@
             "integrity": "sha512-hug+mpbSSKnH13rFqy3zm+XiG+QTStiDAgMTHK355TIstQE0qBkBtSJsa5YHP94AuarVX9b/4dcebdTRZ9YiEw=="
         },
         "node_modules/ms": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-            "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+            "version": "2.1.3",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+            "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
         },
         "node_modules/multicast-dns": {
             "version": "7.2.5",
@@ -24517,9 +24550,9 @@
             }
         },
         "node_modules/nodemon": {
-            "version": "3.1.4",
-            "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz",
-            "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==",
+            "version": "3.1.5",
+            "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.5.tgz",
+            "integrity": "sha512-V5UtfYc7hjFD4SI3EzD5TR8ChAHEZ+Ns7Z5fBk8fAbTVAj+q3G+w7sHJrHxXBkVn6ApLVTljau8wfHwqmGUjMw==",
             "dependencies": {
                 "chokidar": "^3.5.2",
                 "debug": "^4",
@@ -27214,19 +27247,17 @@
             }
         },
         "node_modules/openai": {
-            "version": "4.57.3",
-            "resolved": "https://registry.npmjs.org/openai/-/openai-4.57.3.tgz",
-            "integrity": "sha512-mTz5/SmulkkeSpqbSr6WNLRU6krkyhnbfRUC8XfaXbj1T6xUorKEELjZvbRSzI714JLOk1MeFkqYS9H4WHhqDQ==",
+            "version": "4.62.0",
+            "resolved": "https://registry.npmjs.org/openai/-/openai-4.62.0.tgz",
+            "integrity": "sha512-cPSsarEXoJENNwYMx/Xh/wuvnyYf8lPSR4zDVSnRvbcMHmKkDIzXhUVvPPfuI4M4T83x25gVnlW7huWEGKG+SA==",
             "dependencies": {
                 "@types/node": "^18.11.18",
                 "@types/node-fetch": "^2.6.4",
-                "@types/qs": "^6.9.15",
                 "abort-controller": "^3.0.0",
                 "agentkeepalive": "^4.2.1",
                 "form-data-encoder": "1.7.2",
                 "formdata-node": "^4.3.2",
-                "node-fetch": "^2.6.7",
-                "qs": "^6.10.3"
+                "node-fetch": "^2.6.7"
             },
             "bin": {
                 "openai": "bin/cli"
@@ -27649,9 +27680,9 @@
             "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
         },
         "node_modules/path-to-regexp": {
-            "version": "0.1.7",
-            "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
-            "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
+            "version": "0.1.10",
+            "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
+            "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
         },
         "node_modules/path-type": {
             "version": "4.0.0",
@@ -27992,9 +28023,9 @@
             }
         },
         "node_modules/postcss": {
-            "version": "8.4.45",
-            "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz",
-            "integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==",
+            "version": "8.4.47",
+            "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
+            "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
             "funding": [
                 {
                     "type": "opencollective",
@@ -28011,8 +28042,8 @@
             ],
             "dependencies": {
                 "nanoid": "^3.3.7",
-                "picocolors": "^1.0.1",
-                "source-map-js": "^1.2.0"
+                "picocolors": "^1.1.0",
+                "source-map-js": "^1.2.1"
             },
             "engines": {
                 "node": "^10 || ^12 || >=14"
@@ -28462,11 +28493,11 @@
             }
         },
         "node_modules/qs": {
-            "version": "6.11.0",
-            "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
-            "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+            "version": "6.13.0",
+            "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
+            "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
             "dependencies": {
-                "side-channel": "^1.0.4"
+                "side-channel": "^1.0.6"
             },
             "engines": {
                 "node": ">=0.6"
@@ -28940,15 +28971,15 @@
             "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
         },
         "node_modules/react-jsx-parser": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/react-jsx-parser/-/react-jsx-parser-2.0.0.tgz",
-            "integrity": "sha512-1siP+vM47gWE+x2VTF0ano5jJPRilOf7sRIoy4dzb1FmNoDpwe3htuzgcEfvzsc7YThzwmMQNUfy4vAv1ATs2g==",
-            "hasInstallScript": true,
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/react-jsx-parser/-/react-jsx-parser-2.1.0.tgz",
+            "integrity": "sha512-cGp8ceqA6j7OylsqeK2kuCGRitHpWzximsxsQyqkQO+1fwyG82Mb1l2nx9ImrfdCO6GT2kgt7C6cX9vJ46B7ow==",
             "dependencies": {
                 "acorn": "^8.12.1",
-                "acorn-jsx": "^5.3.2",
-                "browserslist": "^4.23.1",
-                "core-js": "^3.37.1"
+                "acorn-jsx": "^5.3.2"
+            },
+            "engines": {
+                "bun": "^1.1.27"
             },
             "optionalDependencies": {
                 "@types/react": "^18.3.3",
@@ -29388,9 +29419,9 @@
             "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A=="
         },
         "node_modules/regenerate-unicode-properties": {
-            "version": "10.1.1",
-            "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz",
-            "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==",
+            "version": "10.2.0",
+            "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz",
+            "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==",
             "dependencies": {
                 "regenerate": "^1.4.2"
             },
@@ -29964,9 +29995,9 @@
             }
         },
         "node_modules/rimraf/node_modules/lru-cache": {
-            "version": "11.0.0",
-            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz",
-            "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==",
+            "version": "11.0.1",
+            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz",
+            "integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==",
             "engines": {
                 "node": "20 || >=22"
             }
@@ -30126,11 +30157,11 @@
             "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
         },
         "node_modules/sass": {
-            "version": "1.78.0",
-            "resolved": "https://registry.npmjs.org/sass/-/sass-1.78.0.tgz",
-            "integrity": "sha512-AaIqGSrjo5lA2Yg7RvFZrlXDBCp3nV4XP73GrLGvdRWWwk+8H3l0SDvq/5bA4eF+0RFPLuWUk3E+P1U/YqnpsQ==",
+            "version": "1.79.1",
+            "resolved": "https://registry.npmjs.org/sass/-/sass-1.79.1.tgz",
+            "integrity": "sha512-+mA7svoNKeL0DiJqZGeR/ZGUu8he4I8o3jyUcOFyo4eBJrwNgIMmAEwCMo/N2Y3wdjOBcRzoNxZIOtrtMX8EXg==",
             "dependencies": {
-                "chokidar": ">=3.0.0 <4.0.0",
+                "chokidar": "^4.0.0",
                 "immutable": "^4.0.0",
                 "source-map-js": ">=0.6.2 <2.0.0"
             },
@@ -30180,6 +30211,32 @@
                 }
             }
         },
+        "node_modules/sass/node_modules/chokidar": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.0.tgz",
+            "integrity": "sha512-mxIojEAQcuEvT/lyXq+jf/3cO/KoA6z4CeNDGGevTybECPOMFCnQy3OPahluUkbqgPNGw5Bi78UC7Po6Lhy+NA==",
+            "dependencies": {
+                "readdirp": "^4.0.1"
+            },
+            "engines": {
+                "node": ">= 14.16.0"
+            },
+            "funding": {
+                "url": "https://paulmillr.com/funding/"
+            }
+        },
+        "node_modules/sass/node_modules/readdirp": {
+            "version": "4.0.1",
+            "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.1.tgz",
+            "integrity": "sha512-GkMg9uOTpIWWKbSsgwb5fA4EavTR+SG/PMPoAY8hkhHfEEY0/vqljY+XHqtDf2cr2IJtoNRDbrrEpZUiZCkYRw==",
+            "engines": {
+                "node": ">= 14.16.0"
+            },
+            "funding": {
+                "type": "individual",
+                "url": "https://paulmillr.com/funding/"
+            }
+        },
         "node_modules/sax": {
             "version": "1.4.1",
             "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
@@ -30314,9 +30371,9 @@
             "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="
         },
         "node_modules/send": {
-            "version": "0.18.0",
-            "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
-            "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+            "version": "0.19.0",
+            "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
+            "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
             "dependencies": {
                 "debug": "2.6.9",
                 "depd": "2.0.0",
@@ -30349,6 +30406,14 @@
             "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
             "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
         },
+        "node_modules/send/node_modules/encodeurl": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+            "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+            "engines": {
+                "node": ">= 0.8"
+            }
+        },
         "node_modules/send/node_modules/mime": {
             "version": "1.6.0",
             "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
@@ -30360,11 +30425,6 @@
                 "node": ">=4"
             }
         },
-        "node_modules/send/node_modules/ms": {
-            "version": "2.1.3",
-            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
-            "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
-        },
         "node_modules/serialize-javascript": {
             "version": "6.0.2",
             "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
@@ -30465,14 +30525,14 @@
             }
         },
         "node_modules/serve-static": {
-            "version": "1.15.0",
-            "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
-            "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+            "version": "1.16.2",
+            "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+            "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
             "dependencies": {
-                "encodeurl": "~1.0.2",
+                "encodeurl": "~2.0.0",
                 "escape-html": "~1.0.3",
                 "parseurl": "~1.3.3",
-                "send": "0.18.0"
+                "send": "0.19.0"
             },
             "engines": {
                 "node": ">= 0.8.0"
@@ -30762,11 +30822,11 @@
             }
         },
         "node_modules/simple-xml-to-json": {
-            "version": "1.2.2",
-            "resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.2.tgz",
-            "integrity": "sha512-bmJJf5YiYL60eOQk3gaVxbM6vgYuwrFydCEAA2x3jccHUTsAffiPyblS/yQGr8GDUQVxSDm3WwLNL5HmRqDUcg==",
+            "version": "1.2.3",
+            "resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.3.tgz",
+            "integrity": "sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA==",
             "engines": {
-                "node": ">=14.20.0"
+                "node": ">=20.12.2"
             }
         },
         "node_modules/skmeans": {
@@ -30907,9 +30967,9 @@
             }
         },
         "node_modules/source-map-js": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
-            "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+            "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
             "engines": {
                 "node": ">=0.10.0"
             }
@@ -31167,9 +31227,9 @@
             "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
         },
         "node_modules/streamx": {
-            "version": "2.20.0",
-            "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.20.0.tgz",
-            "integrity": "sha512-ZGd1LhDeGFucr1CUCTBOS58ZhEendd0ttpGT3usTvosS4ntIwKN9LJFp+OeCSprsCPL14BXVRZlHGRY1V9PVzQ==",
+            "version": "2.20.1",
+            "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.20.1.tgz",
+            "integrity": "sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA==",
             "dependencies": {
                 "fast-fifo": "^1.3.2",
                 "queue-tick": "^1.0.1",
@@ -31231,9 +31291,9 @@
             "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
         },
         "node_modules/string-width/node_modules/ansi-regex": {
-            "version": "6.0.1",
-            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
-            "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+            "version": "6.1.0",
+            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+            "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
             "engines": {
                 "node": ">=12"
             },
@@ -31483,11 +31543,11 @@
             }
         },
         "node_modules/style-to-object": {
-            "version": "1.0.7",
-            "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.7.tgz",
-            "integrity": "sha512-uSjr59G5u6fbxUfKbb8GcqMGT3Xs9v5IbPkjb0S16GyOeBLAzSRK0CixBv5YrYvzO6TDLzIS6QCn78tkqWngPw==",
+            "version": "1.0.8",
+            "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz",
+            "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==",
             "dependencies": {
-                "inline-style-parser": "0.2.3"
+                "inline-style-parser": "0.2.4"
             }
         },
         "node_modules/styled-components": {
@@ -31681,9 +31741,9 @@
             "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
         },
         "node_modules/terser": {
-            "version": "5.31.6",
-            "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz",
-            "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==",
+            "version": "5.33.0",
+            "resolved": "https://registry.npmjs.org/terser/-/terser-5.33.0.tgz",
+            "integrity": "sha512-JuPVaB7s1gdFKPKTelwUyRq5Sid2A3Gko2S0PncwdBq7kN9Ti9HPWDQ06MPsEDGsZeVESjKEnyGy68quBk1w6g==",
             "dependencies": {
                 "@jridgewell/source-map": "^0.3.3",
                 "acorn": "^8.8.2",
@@ -31788,9 +31848,9 @@
             "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
         },
         "node_modules/text-decoder": {
-            "version": "1.1.1",
-            "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.1.tgz",
-            "integrity": "sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA==",
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.0.tgz",
+            "integrity": "sha512-n1yg1mOj9DNpk3NeZOx7T6jchTbyJS3i3cucbNN6FcdPriMZx7NsgrGpWWdWZZGxD7ES1XB+3uoqHMgOKaN+fg==",
             "dependencies": {
                 "b4a": "^1.6.4"
             }
@@ -32618,9 +32678,9 @@
             }
         },
         "node_modules/type-fest": {
-            "version": "4.26.0",
-            "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.0.tgz",
-            "integrity": "sha512-OduNjVJsFbifKb57UqZ2EMP1i4u64Xwow3NYXUtBbD4vIwJdQd4+xl8YDou1dlm4DVrtwT/7Ky8z8WyCULVfxw==",
+            "version": "4.26.1",
+            "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.1.tgz",
+            "integrity": "sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==",
             "engines": {
                 "node": ">=16"
             },
@@ -32714,9 +32774,9 @@
             }
         },
         "node_modules/typescript": {
-            "version": "5.5.4",
-            "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
-            "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
+            "version": "5.6.2",
+            "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
+            "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
             "bin": {
                 "tsc": "bin/tsc",
                 "tsserver": "bin/tsserver"
@@ -32834,9 +32894,9 @@
             "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
         },
         "node_modules/unicode-canonical-property-names-ecmascript": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz",
-            "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==",
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
+            "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==",
             "engines": {
                 "node": ">=4"
             }
@@ -32854,9 +32914,9 @@
             }
         },
         "node_modules/unicode-match-property-value-ecmascript": {
-            "version": "2.1.0",
-            "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz",
-            "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==",
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz",
+            "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==",
             "engines": {
                 "node": ">=4"
             }
@@ -33154,20 +33214,6 @@
             "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
             "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="
         },
-        "node_modules/url/node_modules/qs": {
-            "version": "6.13.0",
-            "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
-            "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
-            "dependencies": {
-                "side-channel": "^1.0.6"
-            },
-            "engines": {
-                "node": ">=0.6"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/ljharb"
-            }
-        },
         "node_modules/use-composed-ref": {
             "version": "1.3.0",
             "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz",
@@ -33660,9 +33706,9 @@
             }
         },
         "node_modules/webpack-dev-middleware/node_modules/memfs": {
-            "version": "4.11.1",
-            "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.11.1.tgz",
-            "integrity": "sha512-LZcMTBAgqUUKNXZagcZxvXXfgF1bHX7Y7nQ0QyEiNbRJgE29GhgPd8Yna1VQcLlPiHt/5RFJMWYN9Uv/VPNvjQ==",
+            "version": "4.11.2",
+            "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.11.2.tgz",
+            "integrity": "sha512-VcR7lEtgQgv7AxGkrNNeUAimFLT+Ov8uGu1LuOfbe/iF/dKoh/QgpoaMZlhfejvLtMxtXYyeoT7Ar1jEbWdbPA==",
             "dependencies": {
                 "@jsonjoy.com/json-pack": "^1.0.3",
                 "@jsonjoy.com/util": "^1.3.0",
@@ -34162,9 +34208,9 @@
             }
         },
         "node_modules/wrap-ansi/node_modules/ansi-regex": {
-            "version": "6.0.1",
-            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
-            "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+            "version": "6.1.0",
+            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+            "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
             "engines": {
                 "node": ">=12"
             },
diff --git a/package.json b/package.json
index 1f3d86be9..967549197 100644
--- a/package.json
+++ b/package.json
@@ -130,7 +130,7 @@
         "@types/reveal": "^4.2.0",
         "@types/supercluster": "^7.1.3",
         "@types/textfit": "^2.4.4",
-        "@types/web": "^0.0.159",
+        "@types/web": "^0.0.163",
         "@types/webpack-hot-middleware": "^2.25.9",
         "@webscopeio/react-textarea-autocomplete": "^4.9.2",
         "adm-zip": "^0.5.10",
@@ -188,7 +188,7 @@
         "file-saver": "^2.0.5",
         "find-in-files": "^0.5.0",
         "fit-curve": "^0.2.0",
-        "flexlayout-react": "^0.7.15",
+        "flexlayout-react": "^0.8.0",
         "fluent-ffmpeg": "^2.1.2",
         "forever-agent": "^0.6.1",
         "fork-ts-checker-webpack-plugin": "^9.0.2",
@@ -198,7 +198,7 @@
         "function-plot": "^1.23.3",
         "golden-layout": "^2.6.0",
         "google-auth-library": "^9.4.1",
-        "googleapis": "^142.0.0",
+        "googleapis": "^144.0.0",
         "googlephotos": "^0.3.5",
         "got": "^14.0.0",
         "howler": "^2.2.4",
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 37b1f04e7..5e7908725 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -708,7 +708,6 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2);
         const radiusHandle = (borderRadius / docMax) * maxDist;
         const radiusHandleLocation = Math.min(radiusHandle, maxDist);
-        
 
         const sharingMenu =
             Doc.IsSharingEnabled && docShareMode ? (
@@ -771,8 +770,6 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         const centery = hideTitle ? 0 : this._titleHeight;
         const transformOrigin = `${50}% calc(50% + ${centery / 2}px)`;
         const freeformDoc = DocumentView.Selected().some(v => CollectionFreeFormDocumentView.from(v));
-      
-
         return (
             <div className="documentDecorations" style={{ display: this._showNothing && !freeformDoc ? 'none' : undefined }}>
                 <div
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 54698ab2d..13fef101a 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -9,6 +9,7 @@ import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs';
 import { FaFilter } from 'react-icons/fa';
 import { ClientUtils, DashColor, lightOrDark } from '../../ClientUtils';
 import { Doc, Opt, StrListCast } from '../../fields/Doc';
+import { DocData } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { ScriptField } from '../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
@@ -19,14 +20,12 @@ import { SnappingManager } from '../util/SnappingManager';
 import { undoable, UndoManager } from '../util/UndoManager';
 import { TreeSort } from './collections/TreeSort';
 import { Colors } from './global/globalEnums';
-import { TagsView } from './TagsView';
-import { CollectionFreeFormDocumentView } from './nodes/CollectionFreeFormDocumentView';
 import { DocumentView, DocumentViewProps } from './nodes/DocumentView';
 import { FieldViewProps } from './nodes/FieldView';
+import { IconTagBox } from './nodes/IconTagBox';
 import { StyleProp } from './StyleProp';
 import './StyleProvider.scss';
-import { IconTagBox } from './nodes/IconTagBox';
-import { DocData } from '../../fields/DocSymbols';
+import { TagsView } from './TagsView';
 
 function toggleLockedPosition(doc: Doc) {
     UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground');
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index c029d17c6..97ff852b2 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -577,7 +577,8 @@ export class CollectionCardView extends CollectionSubView() {
     /**
      * Actually renders all the cards
      */
-    renderCards = (sortedDocs: Doc[]) => {
+    @computed get renderCards() {
+        const sortedDocs = this.sortedDocs;
         const anySelected = this.childDocs.some(doc => DocumentView.SelectedDocs().includes(doc));
         const isEmpty = this.childDocsWithoutLinks.length === 0;
         const isDesc = BoolCast(this.Document.cardSort_isDesc);
@@ -640,7 +641,7 @@ export class CollectionCardView extends CollectionSubView() {
                 </div>
             );
         });
-    };
+    }
 
     render() {
         const isEmpty = this.childDocsWithoutLinks.length === 0;
@@ -664,7 +665,7 @@ export class CollectionCardView extends CollectionSubView() {
                         gridAutoRows: `${100 / this.numRows}%`,
                     }}
                     onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
-                    {this.renderCards(this.sortedDocs)}
+                    {this.renderCards}
                 </div>
             </div>
         );
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 7feb17df1..f53a7d163 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -157,7 +157,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
 
         let noviceList: string[] = [];
         let text: string | undefined;
-        const getStyle: (val: string) => { [key: string]: string } = () => ({});
+        let getStyle: (val: string) => { [key: string]: string } = () => ({});
         let icon: IconProp = 'caret-down';
         const isViewDropdown = script?.script.originalScript.startsWith('{ return setView');
         if (isViewDropdown) {
@@ -192,7 +192,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
         } else {
             text = script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result as string;
             // text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
-            //   getStyle = (val: string) => ({ fontFamily: val });
+            if (this.Document.title === 'Font') getStyle = (val: string) => ({ fontFamily: val }); // bcz: major hack to style the font dropdown items --- needs to become part of the dropdown's metadata
         }
 
         // Get items to place into the list
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index f5f758ad2..03585a8b7 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -133,7 +133,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             _layout_autoHeight: true,
         });
 
-        this.addToCollection?.(newCol); //this._props.addDocument(newCol)
+        this.addToCollection?.(newCol);
     };
 
     pointerDown = (e: React.PointerEvent) => {
-- 
cgit v1.2.3-70-g09d2


From d95730d904612640184ca6fdc00864b0c81b0c0c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 18 Sep 2024 20:46:38 -0400
Subject: lots of changes to fix dragging cards, integrate iconTags with other
 tags, sizing docs when selected to fit window,

---
 eslint.config.mjs                                  |   1 +
 src/client/util/CurrentUserUtils.ts                |  14 +-
 src/client/util/DragManager.ts                     |   3 +-
 src/client/views/DocumentButtonBar.tsx             |  44 ++----
 src/client/views/DocumentDecorations.tsx           |   4 +-
 src/client/views/StyleProvider.tsx                 |   4 -
 src/client/views/TagsView.tsx                      |  20 +--
 .../views/collections/CollectionCardDeckView.scss  |   8 +-
 .../views/collections/CollectionCardDeckView.tsx   | 152 ++++++++++++---------
 .../views/collections/CollectionCarouselView.tsx   |   1 -
 .../views/collections/CollectionStackingView.tsx   |   3 -
 src/client/views/collections/CollectionSubView.tsx |   4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   5 +-
 src/client/views/nodes/DocumentView.tsx            |   5 +
 src/client/views/nodes/IconTagBox.scss             |  24 ++--
 src/client/views/nodes/IconTagBox.tsx              | 106 +++++++-------
 .../views/nodes/formattedText/FormattedTextBox.tsx |   1 +
 src/client/views/search/FaceRecognitionHandler.tsx |   3 +-
 18 files changed, 198 insertions(+), 204 deletions(-)

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

diff --git a/eslint.config.mjs b/eslint.config.mjs
index 8926afd7c..f7063caa5 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -36,6 +36,7 @@ export default [
             'no-multi-assign': 'off',
             'no-underscore-dangle': 'off',
             'no-nested-ternary': 'off',
+            'no-param-reassign': 'error',
             'lines-between-class-members': 'off',
             'no-shadow': 'off',
             '@typescript-eslint/no-shadow': 'warn',
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index eee6be937..262916312 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -2,7 +2,7 @@
 import { reaction, runInAction } from "mobx";
 import * as rp from 'request-promise';
 import { ClientUtils, OmitKeys } from "../../ClientUtils";
-import { Doc, DocListCast, DocListCastAsync, FieldType, Opt } from "../../fields/Doc";
+import { Doc, DocListCast, DocListCastAsync, FieldType, Opt, StrListCast } from "../../fields/Doc";
 import { DocData } from "../../fields/DocSymbols";
 import { InkTool } from "../../fields/InkField";
 import { List } from "../../fields/List";
@@ -688,18 +688,18 @@ pie title Minerals in my tap water
   
     static tagGroupTools(): Button[] {
         if (!Doc.UserDoc().activeDashboard) {
-        Doc.UserDoc().myFilterHotKeyTitles = new List<string>(['Star', 'Heart', 'Bolt', 'Cloud']);
+        Doc.UserDoc().myFilterHotKeyTitles = new List<string>(['star', 'heart', 'bolt', 'cloud']);
         
-        ['Star', 'Heart', 'Bolt', 'Cloud'].forEach(key => {
+        StrListCast(Doc.UserDoc().myFilterHotKeyTitles).forEach(key => {
             Doc.UserDoc()[key] = key.toLowerCase();
         });
 }
         // hack: if there's no dashboard, create default filters.  otherwise, just make sure that the Options button is preserved
         return (Doc.UserDoc().activeDashboard ? [] : [
-            { title: "Star", isSystem: false, icon: "star", toolTip:"Click to toggle the star group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
-            { title: "Heart", isSystem: false,icon: "heart", toolTip:"Click to toggle the heart group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
-            { title: "Bolt", isSystem: false,icon: "bolt", toolTip:"Click to toggle the bolt group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
-            { title: "Cloud", isSystem: false,icon: "cloud", toolTip:"Click to toggle the cloud group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "star",  isSystem: false,icon: "star", toolTip:"Click to toggle the star group's visibility",   btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "heart", isSystem: false,icon: "heart", toolTip:"Click to toggle the heart group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "bolt",  isSystem: false,icon: "bolt", toolTip:"Click to toggle the bolt group's visibility",    btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "cloud", isSystem: false,icon: "cloud", toolTip:"Click to toggle the cloud group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
         ]).concat([
             { title: "Options", isSystem: true,icon: "gear", toolTip:"Click to customize your filter panel", btnType: ButtonType.ClickButton,  expertMode: false, toolType:"opts", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}
         ])
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index d55d193cc..81ea840f1 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -29,7 +29,7 @@ import { dropActionType } from './DropActionTypes';
 import { SnappingManager } from './SnappingManager';
 import { UndoManager } from './UndoManager';
 
-// eslint-disable-next-line @typescript-eslint/no-var-requires
+// eslint-disable-next-line  @typescript-eslint/no-require-imports
 const { contextMenuZindex } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
 
 /**
@@ -101,7 +101,6 @@ export namespace DragManager {
 
     // event called when the drag operation results in a drop action
     export class DropEvent {
-        // eslint-disable-next-line no-useless-constructor
         constructor(
             readonly x: number,
             readonly y: number,
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 87dd5f45a..32bf67df1 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -9,7 +9,7 @@ import * as React from 'react';
 import { FaEdit } from 'react-icons/fa';
 import { returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../ClientUtils';
 import { emptyFunction } from '../../Utils';
-import { Doc } from '../../fields/Doc';
+import { Doc, DocListCast } from '../../fields/Doc';
 import { Cast, DocCast } from '../../fields/Types';
 import { DocUtils, IsFollowLinkScript } from '../documents/DocUtils';
 import { CalendarManager } from '../util/CalendarManager';
@@ -28,7 +28,6 @@ import { DocumentLinksButton } from './nodes/DocumentLinksButton';
 import { DocumentView } from './nodes/DocumentView';
 import { OpenWhere } from './nodes/OpenWhere';
 import { DashFieldView } from './nodes/formattedText/DashFieldView';
-import { DocData } from '../../fields/DocSymbols';
 
 @observer
 export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (DocumentView | undefined)[]; stack?: unknown }> {
@@ -287,44 +286,27 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
     get keywordButton() {
         const targetDoc = this.view0?.Document;
 
-        const metaBtn = (name: string, icon: IconProp) => {
-            const tooltip = `Toggle ${name}`;
-            return (
-                <Tooltip title={<div className="dash-tooltip">{tooltip}</div>}>
-                    <div className="documentButtonBar-pinIcon">
-                        <FontAwesomeIcon
-                            className="documentdecorations-icon"
-                            style={{ width: 20 }}
-                            key={icon.toString()}
-                            size="sm"
-                            icon={icon}
-                            onClick={() => {
-                                if (name === 'tags') {
-                                    targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags);
-                                } else {
-                                    targetDoc && (targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels);
-                                }
-                            }}
-                        />
-                    </div>
-                </Tooltip>
-            );
-        };
-
         return !targetDoc ? null : (
             <div className="documentButtonBar-icon">
-                <div className="documentButtonBar-pinTypes" style={{ width: '40px' }}>
+                {/* <div className="documentButtonBar-pinTypes" style={{ width: '40px' }}>
                     {metaBtn('tags', 'star')}
                     {metaBtn('keywords', 'id-card')}
-                </div>
+                </div> */}
 
                 <Tooltip title={<div className="dash-keyword-button">Open keyword menu</div>}>
                     <div
                         className="documentButtonBar-icon"
                         style={{ color: 'white' }}
-                        onClick={() => {
-                            // targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags;
-                        }}>
+                        onClick={undoable(e => {
+                            const showing = DocumentView.Selected().some(dv => dv.showTags);
+                            DocumentView.Selected().forEach(dv => {
+                                dv.layoutDoc._layout_showTags = !showing;
+                                if (e.shiftKey)
+                                    DocListCast(dv.Document[Doc.LayoutFieldKey(dv.Document) + '_annotations']).forEach(doc => {
+                                        if (doc.face) doc.hidden = showing;
+                                    });
+                            });
+                        }, 'show Doc tags')}>
                         <FontAwesomeIcon className="documentdecorations-icon" icon="tag" />
                     </div>
                 </Tooltip>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 5e7908725..2ba96c2a9 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -837,7 +837,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                 <div
                                     className="link-button-container"
                                     style={{
-                                        top: DocumentView.Selected().length > 1 ? 0 : `${seldocview.Document._layout_showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
+                                        top: DocumentView.Selected().length > 1 ? 0 : `${seldocview.showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
                                         transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
                                     }}>
                                     <DocumentButtonBar views={() => DocumentView.Selected()} />
@@ -846,7 +846,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                             <div
                                 className="documentDecorations-tagsView"
                                 style={{
-                                    top: `${seldocview.Document._layout_showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
+                                    top: `${seldocview.showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
                                     transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
                                 }}>
                                 {DocumentView.Selected().length > 1 ? <TagsView Views={DocumentView.Selected()} /> : null}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index eb434db40..3545afcee 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -9,7 +9,6 @@ import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs';
 import { FaFilter } from 'react-icons/fa';
 import { ClientUtils, DashColor, lightOrDark } from '../../ClientUtils';
 import { Doc, Opt, StrListCast } from '../../fields/Doc';
-import { DocData } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { ScriptField } from '../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
@@ -22,7 +21,6 @@ import { TreeSort } from './collections/TreeSort';
 import { Colors } from './global/globalEnums';
 import { DocumentView, DocumentViewProps } from './nodes/DocumentView';
 import { FieldViewProps } from './nodes/FieldView';
-import { IconTagBox } from './nodes/IconTagBox';
 import { StyleProp } from './StyleProp';
 import './StyleProvider.scss';
 import { TagsView } from './TagsView';
@@ -366,7 +364,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                 );
             };
             const tags = () => docView?.() ? <TagsView Views={[docView?.()]}/> :  null;
-            const iconTags = () => doc?.[DocData].showIconTags ? <IconTagBox doc= {doc}/> : null;
         
             return (
                 <>
@@ -375,7 +372,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                     {filter()}
                     {audio()}
                     {tags()}
-                    {iconTags()}
                 </>
             );
         }
diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx
index be2c28185..a678d5580 100644
--- a/src/client/views/TagsView.tsx
+++ b/src/client/views/TagsView.tsx
@@ -18,6 +18,7 @@ import { ObservableReactComponent } from './ObservableReactComponent';
 import './TagsView.scss';
 import { DocumentView } from './nodes/DocumentView';
 import { FaceRecognitionHandler } from './search/FaceRecognitionHandler';
+import { IconTagBox } from './nodes/IconTagBox';
 
 /**
  * The TagsView is a metadata input/display panel shown at the bottom of a DocumentView in a freeform collection.
@@ -59,7 +60,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
      * @param tag tag string
      * @returns tag collection Doc or undefined
      */
-    public static findTagCollectionDoc = (tag: String) => TagItem.AllTagCollectionDocs.find(doc => doc.title === tag);
+    public static findTagCollectionDoc = (tag: string) => TagItem.AllTagCollectionDocs.find(doc => doc.title === tag);
 
     /**
      * Creates a Doc that collects Docs with the specified tag / value
@@ -148,7 +149,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
 
     private _ref: React.RefObject<HTMLDivElement>;
 
-    constructor(props: any) {
+    constructor(props: TagItemProps) {
         super(props);
         makeObservable(this);
         this._ref = React.createRef();
@@ -225,7 +226,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
                                 type="checkbox"
                                 onClick={e => e.stopPropagation()}
                                 onPointerDown={e => e.stopPropagation()}
-                                onChange={undoable(e => (this.doc[metadata] = !this.doc[metadata]), 'metadata toggle')}
+                                onChange={undoable(() => (this.doc[metadata] = !this.doc[metadata]), 'metadata toggle')}
                                 checked={this.doc[metadata] as boolean}
                             />
                         ) : (
@@ -257,7 +258,7 @@ interface TagViewProps {
  */
 @observer
 export class TagsView extends ObservableReactComponent<TagViewProps> {
-    constructor(props: any) {
+    constructor(props: TagViewProps) {
         super(props);
         makeObservable(this);
     }
@@ -271,7 +272,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
     componentDidMount() {
         this._heightDisposer = reaction(
             () => this.View.screenToContentsTransform(),
-            xf => {
+            () => {
                 this._panelHeightDirty = this._panelHeightDirty + 1;
             }
         );
@@ -288,7 +289,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
         return this._props.Views.length > 1 ? 1 : Math.max(1, 1 / this.View.screenToLocalScale());
     }
     @computed get isEditing() {
-        return this._isEditing && (this._props.Views.length > 1 || DocumentView.SelectedDocs().includes(this.View.Document));
+        return this._isEditing && (this._props.Views.length > 1 || (DocumentView.Selected().length === 1 && DocumentView.Selected().includes(this.View)));
     }
 
     /**
@@ -336,7 +337,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
         );
         this._panelHeightDirty;
 
-        return this.View.ComponentView?.isUnstyledView?.() || (!this.View.Document._layout_showTags && this._props.Views.length === 1) ? null : (
+        return this.View.ComponentView?.isUnstyledView?.() || (!this.View.showTags && this._props.Views.length === 1) ? null : (
             <div
                 className="tagsView-container"
                 ref={r => r && new ResizeObserver(action(() => this._props.Views.length === 1 && (this.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)}
@@ -352,9 +353,10 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
                 }}>
                 <div className="tagsView-content" style={{ width: '100%' }}>
                     <div className="tagsView-list">
-                        {!tagsList.size && !facesList.size ? null : ( //
+                        {this._props.Views.length === 1 && !this.View.showTags ? null : ( //
                             <IconButton style={{ width: '8px' }} tooltip="Close Menu" onPointerDown={() => this.setToEditing(!this._isEditing)} icon={<FontAwesomeIcon icon={this._isEditing ? 'chevron-up' : 'chevron-down'} size="sm" />} />
                         )}
+                        <IconTagBox Views={this._props.Views} IsEditing={this._isEditing} />
                         {Array.from(tagsList).map((tag, i) => (
                             <TagItem
                                 key={i}
@@ -388,7 +390,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
                                 />
                             </div>
                             <div className="tagsView-suggestions-box">
-                                {TagItem.AllTagCollectionDocs.map((doc, i) => {
+                                {TagItem.AllTagCollectionDocs.map(doc => {
                                     const tag = StrCast(doc.title);
                                     return (
                                         <Button
diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
index 5ccc3d9a8..e5fb7aba6 100644
--- a/src/client/views/collections/CollectionCardDeckView.scss
+++ b/src/client/views/collections/CollectionCardDeckView.scss
@@ -29,18 +29,13 @@
     transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955);
 }
 
-.no-card-span{
+.no-card-span {
     position: relative;
     width: fit-content;
     text-align: center;
     font-size: 65px;
-    
-
-
 }
 
-
-
 .card-item-inactive,
 .card-item-active,
 .card-item {
@@ -50,7 +45,6 @@
     flex-direction: column;
 }
 
-
 .card-item-inactive {
     opacity: 0.5;
 }
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index fced9fd37..1952cc707 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -21,6 +21,7 @@ import { DocumentView } from '../nodes/DocumentView';
 import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
 import './CollectionCardDeckView.scss';
 import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
+import { List } from '../../../fields/List';
 
 enum cardSortings {
     Time = 'time',
@@ -42,7 +43,6 @@ enum cardSortings {
 @observer
 export class CollectionCardView extends CollectionSubView() {
     private _dropDisposer?: DragManager.DragDropDisposer;
-    private _childDocumentWidth = 600; // target width of a Doc...
     private _disposers: { [key: string]: IReactionDisposer } = {};
     private _textToDoc = new Map<string, Doc>();
 
@@ -77,6 +77,12 @@ export class CollectionCardView extends CollectionSubView() {
         this.setRegenerateCallback();
     }
 
+    protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
+        this._dropDisposer?.();
+        if (ele) {
+            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
+        }
+    };
     /**
      * Callback to ensure gpt's text versions of the child docs are updated
      */
@@ -94,10 +100,7 @@ export class CollectionCardView extends CollectionSubView() {
     };
 
     componentDidMount() {
-        this.Document.childFilters_boolean = 'OR';
-        this.childDocsWithoutLinks.forEach(c => {
-            c[DocData].showIconTags = true;
-        });
+        this.Document.childFilters_boolean = 'OR'; // bcz: really shouldn't be assigning to fields from within didMount -- this should be a default/override beahavior somehow
 
         // Reaction to cardSort changes
         this._disposers.sort = reaction(
@@ -117,28 +120,24 @@ export class CollectionCardView extends CollectionSubView() {
         this._dropDisposer?.();
     }
 
-    @computed get cardSort_customField() {
-        return StrCast(this.Document.cardSort_customField) as 'chat' | 'star' | 'idea' | 'like';
-    }
-
     @computed get cardSort() {
         return StrCast(this.Document.cardSort) as cardSortings;
     }
 
     /**
-     * how much to scale down the contents of the view so that everything will fit
+     * The child documents to be rendered-- either all of them except the Links or the docs in the currently active
+     * custom group
      */
-    @computed get fitContentScale() {
-        const length = Math.min(this.childDocsWithoutLinks.length, this._maxRowCount);
-        return (this._childDocumentWidth * length) / this._props.PanelWidth();
+    @computed get childDocsWithoutLinks() {
+        return (this.childDocList as Doc[]).filter(l => l.type !== DocumentType.LINK);
     }
 
     /**
-     * The child documents to be rendered-- either all of them except the Links or the docs in the currently active
-     * custom group
+     * how much to scale down the contents of the view so that everything will fit
      */
-    @computed get childDocsWithoutLinks() {
-        return this.childDocs.filter(l => l.type !== DocumentType.LINK);
+    @computed get fitContentScale() {
+        const length = Math.min(this.childDocsWithoutLinks.length, this._maxRowCount);
+        return (this.childPanelWidth() * length) / this._props.PanelWidth();
     }
 
     /**
@@ -154,7 +153,7 @@ export class CollectionCardView extends CollectionSubView() {
      * Number of rows of cards to be rendered
      */
     @computed get numRows() {
-        return Math.ceil(this.sortedDocs.length / 10);
+        return Math.ceil(this.sortedDocs.length / this._maxRowCount);
     }
 
     @action
@@ -177,8 +176,7 @@ export class CollectionCardView extends CollectionSubView() {
      */
     inactiveDocs = () => this.childDocsWithoutLinks.filter(d => !DocumentView.SelectedDocs().includes(d));
 
-    panelWidth = () => this._childDocumentWidth;
-    panelHeight = (layout: Doc) => () => (this.panelWidth() * NumCast(layout._height)) / NumCast(layout._width);
+    childPanelWidth = () => NumCast(this.layoutDoc.childPanelWidth, this._props.PanelWidth() / 2);
     onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
     isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
     isChildContentActive = () => !!this.isContentActive();
@@ -245,6 +243,7 @@ export class CollectionCardView extends CollectionSubView() {
         const currRow = Math.floor((mouseY - 100) / rowHeight); //rows start at 0
 
         if (adjustedX < 0) {
+            console.log('DROP INDEX NO ');
             return 0; // Before the first column
         }
 
@@ -259,6 +258,7 @@ export class CollectionCardView extends CollectionSubView() {
 
             index = Math.floor(adjustedX / cardWidth) + currRow * this._maxRowCount;
         }
+        console.log('DROP INDEX = ' + index);
         return index;
     };
 
@@ -280,21 +280,42 @@ export class CollectionCardView extends CollectionSubView() {
         }
     };
 
+    /**
+     * 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
      * @param de drop event
      * @returns true if a card has been dropped, falls if not
      */
-    onInternalDrop = undoable((e: Event, de: DragManager.DropEvent) => {
-        if (de.complete.docDragData) {
-            this._isACardBeingDragged = false;
-            this._docDraggedIndex = -1;
-            e.stopPropagation();
-            return true;
-        }
-        return false;
-    }, '');
+    onInternalDrop = undoable(
+        action((e: Event, de: DragManager.DropEvent) => {
+            if (de.complete.docDragData) {
+                this._isACardBeingDragged = false;
+                const dragIndex = this._docDraggedIndex;
+                if (dragIndex > -1) {
+                    this._docDraggedIndex = -1;
+                    const draggedDoc = DragManager.docsBeingDragged[0];
+                    const sorted = this.sortedDocs;
+                    const originalIndex = sorted.findIndex(doc => doc === draggedDoc);
+
+                    this.Document.cardSort = '';
+                    sorted.splice(originalIndex, 1);
+                    sorted.splice(dragIndex, 0, draggedDoc);
+                    this.dataDoc[this.fieldKey] = new List<Doc>(sorted);
+                }
+                e.stopPropagation();
+                return true;
+            }
+            return false;
+        }),
+        ''
+    );
 
     @computed get sortedDocs() {
         return this.sort(this.childDocsWithoutLinks, this.cardSort, BoolCast(this.Document.cardSort_isDesc), this._docDraggedIndex);
@@ -348,28 +369,29 @@ export class CollectionCardView extends CollectionSubView() {
      * @returns
      */
     sort = (docs: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => {
-        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()];
+        sortType &&
+            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)];
                     }
-                    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)];
-                }
-            })();
+                })();
 
-            const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0;
-            return isDesc ? out : -out;
-        });
+                const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0;
+                return isDesc ? out : -out;
+            });
         if (dragIndex != -1) {
             const draggedDoc = DragManager.docsBeingDragged[0];
             const originalIndex = docs.findIndex(doc => doc === draggedDoc);
@@ -396,9 +418,11 @@ export class CollectionCardView extends CollectionSubView() {
             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.panelWidth}
-            PanelHeight={this.panelHeight(doc)}
+            PanelWidth={this.childPanelWidth}
+            PanelHeight={() => this._props.PanelHeight() * this.fitContentScale}
+            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}
             dontHideOnDrag
         />
     );
@@ -415,18 +439,18 @@ export class CollectionCardView extends CollectionSubView() {
         // 13 - 3 = 10
         const totalCards = this.sortedDocs.length;
         // if 9 or less
-        if (index < totalCards - (totalCards % 10)) {
+        if (index < totalCards - (totalCards % this._maxRowCount)) {
             return this._maxRowCount;
         }
         // (3)
-        return totalCards % 10;
+        return totalCards % this._maxRowCount;
     };
     /**
      * Determines the index a card is in in a row
      * @param realIndex
      * @returns
      */
-    overflowIndexCalc = (realIndex: number) => realIndex % 10;
+    overflowIndexCalc = (realIndex: number) => realIndex % this._maxRowCount;
     /**
      * Translates the cards in the second rows and beyond over to the right
      * @param realIndex
@@ -434,7 +458,7 @@ export class CollectionCardView extends CollectionSubView() {
      * @param calcRowCards
      * @returns
      */
-    translateOverflowX = (realIndex: number, calcRowCards: number) => (realIndex < this._maxRowCount ? 0 : (10 - calcRowCards) * (this.panelWidth() / 2));
+    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
@@ -613,6 +637,9 @@ export class CollectionCardView extends CollectionSubView() {
                 const rowCenterIndex = Math.min(this._maxRowCount, sortedDocs.length - rowIndex * this._maxRowCount) / 2;
                 return (rowCenterIndex - indexInRow) * 100 - 50;
             };
+            const aspect = NumCast(doc.height) / NumCast(doc.width, 1);
+            const vscale = ((this._props.PanelHeight() * .95) * this.fitContentScale) / (aspect * this.childPanelWidth());
+            const hscale = 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]}
@@ -629,13 +656,13 @@ export class CollectionCardView extends CollectionSubView() {
                         );
                     }}
                     style={{
-                        width: this.panelWidth(),
+                        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'}))
+                                    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})`,
-                    }}
+                                    scale(${isSelected ? `${Math.min(hscale, vscale) * 100}%` : this._hoveredNodeIndex === index ? 1.05 : 1})`,
+                    }} // prettier-ignore
                     onMouseEnter={() => this.setHoveredNodeIndex(index)}>
                     {this.displayDoc(doc, childScreenToLocal)}
                 </div>
@@ -645,14 +672,13 @@ export class CollectionCardView extends CollectionSubView() {
 
     render() {
         const isEmpty = this.childDocsWithoutLinks.length === 0;
-        const transformValue = `scale(${1 / this.fitContentScale})`;
-        const heightValue = `${100 * this.fitContentScale}%`;
 
         return (
             <div
                 onPointerMove={e => this.onPointerMove(e)}
                 className="collectionCardView-outer"
                 ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)}
+                onDrop={this.onExternalDrop.bind(this)}
                 style={{
                     background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string,
                     color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string,
@@ -660,8 +686,8 @@ export class CollectionCardView extends CollectionSubView() {
                 <div
                     className="card-wrapper"
                     style={{
-                        ...(!isEmpty && { transform: transformValue }),
-                        ...(!isEmpty && { height: heightValue }),
+                        ...(!isEmpty && { transform: `scale(${1 / this.fitContentScale})` }),
+                        ...(!isEmpty && { height: `${100 * this.fitContentScale}%` }),
                         gridAutoRows: `${100 / this.numRows}%`,
                     }}
                     onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index ad868fd1e..4609be374 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/jsx-props-no-spreading */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index e97ee713e..1ac0b6d70 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/jsx-props-no-spreading */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import * as CSS from 'csstype';
 import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
@@ -540,7 +539,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         if (this.pivotField) {
             const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
             if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
-                // eslint-disable-next-line prefer-destructuring
                 type = types[0];
             }
         }
@@ -577,7 +575,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined;
         const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
         if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
-            // eslint-disable-next-line prefer-destructuring
             type = types[0];
         }
         const rows = () => (!this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, Math.floor((this._props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))));
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 6aca8f2ca..99373da04 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -122,6 +122,9 @@ export function CollectionSubView<X>() {
                 );
             return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types
         }
+        /**
+         * This is the raw, stored list of children on a collection.  If you modify this list, the database will be updated
+         */
         @computed get childDocList() {
             return Cast(this.dataField, listSpec(Doc));
         }
@@ -218,7 +221,6 @@ export function CollectionSubView<X>() {
                 if (!cursors) {
                     proto.cursors = cursors = new List<CursorField>();
                 }
-                // eslint-disable-next-line no-cond-assign
                 if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.data.metadata.id === id)) > -1) {
                     cursors[ind].setPosition(pos);
                 } else {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index dbf781e63..cbbf063b4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/jsx-props-no-spreading */
 import { Bezier } from 'bezier-js';
 import { Colors } from 'browndash-components';
 import { Property } from 'csstype';
@@ -1211,7 +1210,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 for (let j = 0; j < otherCtrlPts.length - 3; j += 4) {
                     const neighboringSegment = i === j || i === j - 4 || i === j + 4;
                     // Ensuring that the curve intersected by the eraser is not checked for further ink intersections.
-                    // eslint-disable-next-line no-continue
                     if (ink?.Document === otherInk.Document && neighboringSegment) continue;
 
                     const otherCurve = new Bezier(otherCtrlPts.slice(j, j + 4).map(p => ({ x: p.X, y: p.Y })));
@@ -1481,8 +1479,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const childData = entry.pair.data;
         return (
             <CollectionFreeFormDocumentView
-                // eslint-disable-next-line react/jsx-props-no-spreading, @typescript-eslint/no-explicit-any
-                {...(OmitKeys(entry, ['replica', 'pair']).omit as any)}
+                {...(OmitKeys(entry, ['replica', 'pair']).omit as { x: number; y: number; z: number; width: number; height: number })}
                 key={childLayout[Id] + (entry.replica || '')}
                 Document={childLayout}
                 reactParent={this}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4c357cf45..758e70508 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -67,6 +67,7 @@ export interface DocumentViewProps extends FieldViewSharedProps {
     hideCaptions?: boolean;
     contentPointerEvents?: Property.PointerEvents | undefined; // pointer events allowed for content of a document view.  eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
     dontCenter?: 'x' | 'y' | 'xy';
+    showTags?: boolean;
     childHideDecorationTitle?: boolean;
     childHideResizeHandles?: boolean;
     childDragAction?: dropActionType; // allows child documents to be dragged out of collection without holding the embedKey or dragging the doc decorations title bar.
@@ -1126,6 +1127,10 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     @observable public static CurrentlyPlaying: DocumentView[] = []; // audio or video media views that are currently playing
     @observable public TagPanelHeight = 0;
 
+    @computed get showTags() {
+        return this.Document._layout_showTags || this._props.showTags;
+    }
+
     @computed private get shouldNotScale() {
         return (this.layout_fitWidth && !this.nativeWidth) || this.ComponentView?.isUnstyledView?.();
     }
diff --git a/src/client/views/nodes/IconTagBox.scss b/src/client/views/nodes/IconTagBox.scss
index 211a961c1..90cc06092 100644
--- a/src/client/views/nodes/IconTagBox.scss
+++ b/src/client/views/nodes/IconTagBox.scss
@@ -2,23 +2,25 @@
 
 .card-button-container {
     display: flex;
-    padding: 3px;
-    position: absolute;
+    position: relative;
     pointer-events: none;
-    background-color: rgb(218, 218, 218); 
+    background-color: rgb(218, 218, 218);
     border-radius: 50px;
-    transform: translateY(25px);
-    align-items: center; 
-    justify-content: start;
+    align-items: center;
+    gap: 5px;
+    padding-left: 5px;
+    padding-right: 5px;
+    padding-top: 2px;
+    padding-bottom: 2px;
 
     button {
         pointer-events: auto;
-        transform: translateY(-7.5px);
-        width: 30px;
-        height: 30px;
+        width: 20px;
+        height: 20px;
+        margin: auto;
+        padding: 0;
         border-radius: 50%;
         background-color: $dark-gray;
-        margin: 5px; 
         background-color: transparent;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx
index f86372ec0..3d021fd73 100644
--- a/src/client/views/nodes/IconTagBox.tsx
+++ b/src/client/views/nodes/IconTagBox.tsx
@@ -6,18 +6,18 @@ import { observer } from 'mobx-react';
 import React from 'react';
 import { numberRange } from '../../../Utils';
 import { Doc, StrListCast } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
-import { BoolCast, DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { CollectionViewType } from '../../documents/DocumentTypes';
+import { StrCast } from '../../../fields/Types';
 import { SnappingManager } from '../../util/SnappingManager';
 import { undoable } from '../../util/UndoManager';
 import { MainView } from '../MainView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { PropertiesView } from '../PropertiesView';
+import { DocumentView } from './DocumentView';
 import './IconTagBox.scss';
 
 export interface IconTagProps {
-    doc: Doc;
+    Views: DocumentView[];
+    IsEditing: boolean;
 }
 
 /**
@@ -26,59 +26,16 @@ export interface IconTagProps {
  */
 @observer
 export class IconTagBox extends ObservableReactComponent<IconTagProps> {
-    @computed
-    get currentScale() {
-        return NumCast((this._props.doc.embedContainer as Doc)?._freeform_scale, 1);
-    }
-
     constructor(props: IconTagProps) {
         super(props);
     }
 
-    componentDidUpdate(): void {
-        this._props.doc[DocData].tagHeight = 36 * this.currentScale;
+    @computed get View() {
+        return this._props.Views.lastElement();
+    }
+    @computed get currentScale() {
+        return this.View?.screenToLocalScale();
     }
-
-    /**
-     * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups
-     * @param doc
-     * @param cardSort
-     * @returns
-     */
-    renderButtons = (doc: Doc): JSX.Element | null => {
-        const amButtons = StrListCast(Doc.UserDoc().myFilterHotKeyTitles).length + 1;
-
-        const keys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
-
-        const totalWidth = (amButtons - 1) * 35 + (amButtons - 1) * 2 * 5 + 6;
-
-        const iconMap = (buttonID: number) => {
-            return StrCast(Doc.UserDoc()[keys[buttonID]]) as IconProp;
-        };
-
-        const isCard = DocCast(this._props.doc.embedContainer).type_collection === CollectionViewType.Card;
-
-        return (
-            <div
-                className="card-button-container"
-                style={{
-                    transformOrigin: 'top left',
-                    transform: `scale(${isCard ? 2 : 0.6 / this.currentScale})
-                translateY(${doc[DocData].showLabels ? NumCast(doc[DocData].keywordHeight) * (1 - this.currentScale) : 0}px)
-                `,
-                    width: `${totalWidth}px`,
-                    fontSize: '50px',
-                }}>
-                {numberRange(amButtons - 1).map(i => (
-                    <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from the {iconMap(i).toString()} group</div>}>
-                        <button key={i} type="button" onClick={() => this.toggleButton(doc, iconMap(i))}>
-                            {this.getButtonIcon(doc, iconMap(i))}
-                        </button>
-                    </Tooltip>
-                ))}
-            </div>
-        );
-    };
 
     /**
      * Opens the filter panel in the properties menu
@@ -93,13 +50,13 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
     };
 
     /**
-     * Toggles the buttons between on and off when creating custom sort groupings/changing those created by gpt
-     * @param childPairIndex
      * @param buttonID
      * @param doc
      */
-    toggleButton = undoable((doc: Doc, icon: string) => {
-        BoolCast(doc[icon]) ? (doc[icon] = false) : (doc[icon] = true);
+    setIconTag = undoable((icon: string, state: boolean) => {
+        this._props.Views.forEach(view => {
+            view.dataDoc[icon] = state;
+        });
     }, 'toggle card tag');
 
     /**
@@ -112,10 +69,43 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
         const isActive = doc[icon.toString()];
         const color = isActive ? '#4476f7' : '#323232';
 
-        return <FontAwesomeIcon icon={icon} style={{ color, height: '30px', width: '30px' }} />;
+        return <FontAwesomeIcon icon={icon} style={{ color, height: '20px', width: '20px' }} />;
     };
 
+    /**
+     * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups
+     */
     render() {
-        return <>{this.renderButtons(this._props.doc)}</>;
+        const amButtons = StrListCast(Doc.UserDoc().myFilterHotKeyTitles).length + 1;
+
+        const keys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
+
+        const iconMap = (buttonID: number) => {
+            return StrCast(Doc.UserDoc()[keys[buttonID]]) as IconProp;
+        };
+        const buttons = numberRange(amButtons - 1)
+            .filter(i => this._props.IsEditing || this.View.Document[iconMap(i).toString()] || (DocumentView.Selected.length === 1 && this.View.IsSelected))
+            .map(i => (
+                <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from the {iconMap(i).toString()} group</div>}>
+                    <button
+                        key={i}
+                        type="button"
+                        onClick={() => {
+                            const state = this.View.Document[iconMap(i).toString()];
+                            this.setIconTag(iconMap(i), !state);
+                        }}>
+                        {this.getButtonIcon(this.View.Document, iconMap(i))}
+                    </button>
+                </Tooltip>
+            ));
+        return !buttons.length ? null : (
+            <div
+                className="card-button-container"
+                style={{
+                    fontSize: '50px',
+                }}>
+                {buttons}
+            </div>
+        );
     }
 }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 343e255dc..e0331a422 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -275,6 +275,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 ele.append(contents);
             }
             this._selectionHTML = ele?.innerHTML;
+            // eslint-disable-next-line @typescript-eslint/no-unused-vars
         } catch (e) {
             /* empty */
         }
diff --git a/src/client/views/search/FaceRecognitionHandler.tsx b/src/client/views/search/FaceRecognitionHandler.tsx
index c507e54b6..6f70e96ab 100644
--- a/src/client/views/search/FaceRecognitionHandler.tsx
+++ b/src/client/views/search/FaceRecognitionHandler.tsx
@@ -30,6 +30,7 @@ import { DocumentManager } from '../../util/DocumentManager';
  *     face_annos - a list of face annotations, where each anno has
  */
 export class FaceRecognitionHandler {
+    // eslint-disable-next-line no-use-before-define
     static _instance: FaceRecognitionHandler;
     private _apiModelReady = false;
     private _pendingAPIModelReadyDocs: Doc[] = [];
@@ -221,7 +222,7 @@ export class FaceRecognitionHandler {
                                 const annos = [] as Doc[];
                                 const scale = NumCast(imgDoc.data_nativeWidth) / img.width;
                                 const showTags= imgDocFaceDescriptions.length > 1;
-                                imgDocFaceDescriptions.forEach((fd, i) => {
+                                imgDocFaceDescriptions.forEach(fd => {
                                     const faceDescriptor = new List<number>(Array.from(fd.descriptor));
                                     const matchedUniqueFace = this.findMatchingFaceDoc(fd.descriptor) ?? this.createUniqueFaceDoc(activeDashboard);
                                     const faceAnno = Docs.Create.FreeformDocument([], {
-- 
cgit v1.2.3-70-g09d2


From cc09a9e9015bbcbeec68ccdc574b5250d35a4e8f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 18 Sep 2024 21:16:03 -0400
Subject: carddeck tweak to heuristic to keep Docs selected docs inset within
 window.  tweak to layout of Docs to make arc apex based on grid Doc widths.

---
 src/client/views/collections/CollectionCardDeckView.tsx | 12 ++++++------
 1 file changed, 6 insertions(+), 6 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 1952cc707..e95a6fb97 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -208,11 +208,12 @@ export class CollectionCardView extends CollectionSubView() {
     translateY = (amCards: number, index: number, realIndex: number) => {
         const evenOdd = amCards % 2;
         const apex = (amCards - evenOdd) / 2;
-        const stepMag = 200 / ((amCards - evenOdd) / 2) + Math.abs((apex - index) * 25);
+        const Magnitude = this.childPanelWidth() / 2; // 400
+        const stepMag = Magnitude / 2 / ((amCards - evenOdd) / 2) + Math.abs((apex - index) * 25);
 
         let rowOffset = 0;
         if (realIndex > this._maxRowCount - 1) {
-            rowOffset = 400 * ((realIndex - (realIndex % this._maxRowCount)) / this._maxRowCount);
+            rowOffset = Magnitude * ((realIndex - (realIndex % this._maxRowCount)) / this._maxRowCount);
         }
         if (evenOdd === 1 || index < apex - 1) {
             return Math.abs(stepMag * (apex - index)) - rowOffset;
@@ -475,9 +476,7 @@ export class CollectionCardView extends CollectionSubView() {
         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);
+        return this.translateY(amCards, calcRowIndex, realIndex);
     };
 
     /**
@@ -638,7 +637,8 @@ export class CollectionCardView extends CollectionSubView() {
                 return (rowCenterIndex - indexInRow) * 100 - 50;
             };
             const aspect = NumCast(doc.height) / NumCast(doc.width, 1);
-            const vscale = ((this._props.PanelHeight() * .95) * this.fitContentScale) / (aspect * this.childPanelWidth());
+            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 hscale = 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
-- 
cgit v1.2.3-70-g09d2


From cacd3a933fe874b798b422847c963b25458c3f60 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 18 Sep 2024 23:56:52 -0400
Subject: fixed selecting card scaling when fewer than 10 items

---
 src/client/views/collections/CollectionCardDeckView.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(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 e95a6fb97..5a00fb673 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -639,7 +639,7 @@ export class CollectionCardView extends CollectionSubView() {
             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 hscale = 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
+            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]}
-- 
cgit v1.2.3-70-g09d2


From 18056735bdeddca55f530d41259874a4ed3ae200 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 19 Sep 2024 00:05:28 -0400
Subject: fixed hiding decorations when animating card transitions

---
 src/client/views/TagsView.tsx                           | 2 ++
 src/client/views/collections/CollectionCardDeckView.tsx | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx
index 5a47be705..a9224e437 100644
--- a/src/client/views/TagsView.tsx
+++ b/src/client/views/TagsView.tsx
@@ -19,6 +19,7 @@ import './TagsView.scss';
 import { DocumentView } from './nodes/DocumentView';
 import { FaceRecognitionHandler } from './search/FaceRecognitionHandler';
 import { IconTagBox } from './nodes/IconTagBox';
+import { Id } from '../../fields/FieldSymbols';
 
 /**
  * The TagsView is a metadata input/display panel shown at the bottom of a DocumentView in a freeform collection.
@@ -350,6 +351,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
                 className="tagsView-container"
                 ref={r => r && new ResizeObserver(action(() => this._props.Views.length === 1 && (this.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)}
                 style={{
+                    display: SnappingManager.IsResizing === this.View.Document[Id] ? 'none' : undefined,
                     transformOrigin: 'top left',
                     maxWidth: `${100 * this.currentScale}%`,
                     width: 'max-content',
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 5a00fb673..3dfb280f4 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -646,7 +646,7 @@ export class CollectionCardView extends CollectionSubView() {
                     className={`card-item${isSelected ? '-active' : anySelected ? '-inactive' : ''}`}
                     onPointerUp={() => {
                         // this turns off documentDecorations during a transition, then turns them back on afterward.
-                        SnappingManager.SetIsResizing(this.Document[Id]);
+                        SnappingManager.SetIsResizing(doc[Id]);
                         setTimeout(
                             action(() => {
                                 SnappingManager.SetIsResizing(undefined);
-- 
cgit v1.2.3-70-g09d2


From 3df19b61e8ae06940822a01801a4aa6e7716ac9a Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Thu, 19 Sep 2024 01:04:08 -0400
Subject: fixed filtering bug in card view

---
 src/client/views/collections/CollectionCardDeckView.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(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 3dfb280f4..8fea3dbd6 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -129,7 +129,7 @@ export class CollectionCardView extends CollectionSubView() {
      * custom group
      */
     @computed get childDocsWithoutLinks() {
-        return (this.childDocList as Doc[]).filter(l => l.type !== DocumentType.LINK);
+        return this.childDocs.filter(l => l.type !== DocumentType.LINK);
     }
 
     /**
-- 
cgit v1.2.3-70-g09d2


From 65a1a272b15fb9c08ef75a12946d7f3751a500c7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 19 Sep 2024 02:54:28 -0400
Subject: added stop propagation for clicking on tags and icon tags.  fixed
 dragging crash from doc nested in card view to card view.  still needs more
 work for card view to honor canEmbed

---
 src/client/views/TagsView.tsx                      | 25 +++++++++++++++--
 .../views/collections/CollectionCardDeckView.tsx   | 32 +++++++++++-----------
 .../views/collections/CollectionNoteTakingView.tsx |  1 -
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  1 -
 src/client/views/nodes/IconTagBox.tsx              | 14 ++++++----
 5 files changed, 47 insertions(+), 26 deletions(-)

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

diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx
index a9224e437..cae30218c 100644
--- a/src/client/views/TagsView.tsx
+++ b/src/client/views/TagsView.tsx
@@ -207,7 +207,10 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
                 return false;
             },
             returnFalse,
-            emptyFunction
+            clickEv => {
+                clickEv.stopPropagation();
+                this._props.setToEditing();
+            }
         );
         e.preventDefault();
     };
@@ -220,7 +223,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
         this._props.tagDoc && setTimeout(() => this._props.docs.forEach(doc => TagItem.addTagToDoc(doc, this._props.tag))); // bcz: hack to make sure that Docs are added to their tag Doc collection since metadata can get set anywhere without a guard triggering an add to the collection
         const metadata = this._props.tag.startsWith('@') ? this._props.tag.replace(/^@/, '') : '';
         return (
-            <div className={'tagItem' + (!this._props.tagDoc ? ' faceItem' : '')} onClick={this._props.setToEditing} onPointerDown={this.handleDragStart} ref={this._ref}>
+            <div className={'tagItem' + (!this._props.tagDoc ? ' faceItem' : '')} onPointerDown={this.handleDragStart} ref={this._ref}>
                 {metadata ? (
                     <span>
                         <b style={{ fontSize: 'smaller' }}>{'@' + metadata}&nbsp;</b>
@@ -364,7 +367,23 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
                 <div className="tagsView-content" style={{ width: '100%' }}>
                     <div className="tagsView-list">
                         {this._props.Views.length === 1 && !this.View.showTags ? null : ( //
-                            <IconButton style={{ width: '8px' }} tooltip="Close Menu" onPointerDown={() => this.setToEditing(!this._isEditing)} icon={<FontAwesomeIcon icon={this._isEditing ? 'chevron-up' : 'chevron-down'} size="sm" />} />
+                            <IconButton
+                                style={{ width: '8px' }}
+                                tooltip="Close Menu"
+                                onPointerDown={e =>
+                                    setupMoveUpEvents(
+                                        this,
+                                        e,
+                                        returnFalse,
+                                        emptyFunction,
+                                        upEv => {
+                                            this.setToEditing(!this._isEditing);
+                                            upEv.stopPropagation();
+                                        }
+                                    )
+                                }
+                                icon={<FontAwesomeIcon icon={this._isEditing ? 'chevron-up' : 'chevron-down'} size="sm" />}
+                            />
                         )}
                         <IconTagBox Views={this._props.Views} IsEditing={this._isEditing} />
                         {Array.from(tagsList)
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 8fea3dbd6..cab7d51e4 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -6,6 +6,7 @@ import { emptyFunction } from '../../../Utils';
 import { Doc, StrListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
+import { List } from '../../../fields/List';
 import { BoolCast, DateCast, DocCast, NumCast, RTFCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { URLField } from '../../../fields/URLField';
 import { gptImageLabel } from '../../apis/gpt/GPT';
@@ -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 { List } from '../../../fields/List';
 
 enum cardSortings {
     Time = 'time',
@@ -51,7 +51,6 @@ export class CollectionCardView extends CollectionSubView() {
     @observable _docRefs = new ObservableMap<Doc, DocumentView>();
     @observable _maxRowCount = 10;
     @observable _docDraggedIndex: number = -1;
-    @observable _isACardBeingDragged: boolean = false;
     @observable overIndex: number = -1;
 
     static imageUrlToBase64 = async (imageUrl: string): Promise<string> => {
@@ -76,7 +75,6 @@ export class CollectionCardView extends CollectionSubView() {
         makeObservable(this);
         this.setRegenerateCallback();
     }
-
     protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
         this._dropDisposer?.();
         if (ele) {
@@ -269,11 +267,10 @@ export class CollectionCardView extends CollectionSubView() {
      */
 
     @action
-    onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
-        if (DragManager.docsBeingDragged.length != 0) {
-            this._isACardBeingDragged = true;
-
-            const newIndex = this.findCardDropIndex(e.clientX, e.clientY);
+    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;
@@ -297,18 +294,19 @@ export class CollectionCardView extends CollectionSubView() {
     onInternalDrop = undoable(
         action((e: Event, de: DragManager.DropEvent) => {
             if (de.complete.docDragData) {
-                this._isACardBeingDragged = false;
                 const dragIndex = this._docDraggedIndex;
-                if (dragIndex > -1) {
+                const draggedDoc = DragManager.docsBeingDragged[0];
+                if (dragIndex > -1 && draggedDoc) {
                     this._docDraggedIndex = -1;
-                    const draggedDoc = DragManager.docsBeingDragged[0];
                     const sorted = this.sortedDocs;
                     const originalIndex = sorted.findIndex(doc => doc === draggedDoc);
 
                     this.Document.cardSort = '';
-                    sorted.splice(originalIndex, 1);
+                    originalIndex !== -1 && sorted.splice(originalIndex, 1);
                     sorted.splice(dragIndex, 0, draggedDoc);
-                    this.dataDoc[this.fieldKey] = new List<Doc>(sorted);
+                    if (de.complete.docDragData.removeDocument?.(draggedDoc)) {
+                        this.dataDoc[this.fieldKey] = new List<Doc>(sorted);
+                    }
                 }
                 e.stopPropagation();
                 return true;
@@ -397,7 +395,7 @@ export class CollectionCardView extends CollectionSubView() {
             const draggedDoc = DragManager.docsBeingDragged[0];
             const originalIndex = docs.findIndex(doc => doc === draggedDoc);
 
-            docs.splice(originalIndex, 1);
+            originalIndex !== -1 && docs.splice(originalIndex, 1);
             docs.splice(dragIndex, 0, draggedDoc);
         }
 
@@ -619,7 +617,8 @@ export class CollectionCardView extends CollectionSubView() {
             const realIndex = sortedDocs.indexOf(doc);
             const calcRowIndex = this.overflowIndexCalc(realIndex);
             const amCards = this.overflowAmCardsCalc(realIndex);
-            const isSelected = DocumentView.SelectedDocs().includes(doc);
+            const view = DocumentView.getDocumentView(doc, this.DocumentView?.());
+            const isSelected = view?.ComponentView?.isAnyChildContentActive?.() || view?.IsSelected ? true : false;
 
             const childScreenToLocal = () => {
                 this._forceChildXf;
@@ -645,6 +644,7 @@ export class CollectionCardView extends CollectionSubView() {
                     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(
@@ -675,9 +675,9 @@ export class CollectionCardView extends CollectionSubView() {
 
         return (
             <div
-                onPointerMove={e => this.onPointerMove(e)}
                 className="collectionCardView-outer"
                 ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)}
+                onPointerMove={e => this.onPointerMove(e.clientX, e.clientY)}
                 onDrop={this.onExternalDrop.bind(this)}
                 style={{
                     background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string,
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index e1f0a3e41..ac8e37358 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -653,7 +653,6 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         const sections = Array.from(this.Sections.entries());
         return sections.reduce((list, sec, i) => {
             list.push(this.sectionNoteTaking(sec[0], sec[1]));
-            // eslint-disable-next-line react/no-array-index-key
             i !== sections.length - 1 && list.push(<CollectionNoteTakingViewDivider key={`divider${i}`} isContentActive={this.isContentActive} index={i} setColumnStartXCoords={this.setColumnStartXCoords} xMargin={this.xMargin} />);
             return list;
         }, [] as JSX.Element[]);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index cbbf063b4..f106eba26 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1783,7 +1783,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 if (!code.includes('dashDiv')) {
                     const script = CompileScript(code, { params: { docView: 'any' }, typecheck: false, editable: true });
                     if (script.compiled) script.run({ this: this.DocumentView?.() });
-                    // eslint-disable-next-line no-eval
                 } else code && !first && eval?.(code);
             },
             { fireImmediately: true }
diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx
index ea1c3591a..70992e28a 100644
--- a/src/client/views/nodes/IconTagBox.tsx
+++ b/src/client/views/nodes/IconTagBox.tsx
@@ -4,7 +4,7 @@ import { Tooltip } from '@mui/material';
 import { computed } from 'mobx';
 import { observer } from 'mobx-react';
 import React from 'react';
-import { numberRange } from '../../../Utils';
+import { emptyFunction, numberRange } from '../../../Utils';
 import { Doc, StrListCast } from '../../../fields/Doc';
 import { StrCast } from '../../../fields/Types';
 import { SnappingManager } from '../../util/SnappingManager';
@@ -15,6 +15,7 @@ import { PropertiesView } from '../PropertiesView';
 import { DocumentView } from './DocumentView';
 import './IconTagBox.scss';
 import { TagItem } from '../TagsView';
+import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
 
 export interface IconTagProps {
     Views: DocumentView[];
@@ -93,10 +94,13 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
                     <button
                         key={i}
                         type="button"
-                        onClick={() => {
-                            const state = TagItem.docHasTag(this.View.Document, iconMap(i).toString()); // this.View.Document[iconMap(i).toString()];
-                            this.setIconTag(iconMap(i), !state);
-                        }}>
+                        onPointerDown={e =>
+                            setupMoveUpEvents(this, e, returnFalse, emptyFunction, clickEv => {
+                                const state = TagItem.docHasTag(this.View.Document, iconMap(i).toString()); // this.View.Document[iconMap(i).toString()];
+                                this.setIconTag(iconMap(i), !state);
+                                clickEv.stopPropagation();
+                            })
+                        }>
                         {this.getButtonIcon(this.View.Document, iconMap(i))}
                     </button>
                 </Tooltip>
-- 
cgit v1.2.3-70-g09d2


From 3c8af89e3d5370b748fea27c411b3e62758b9a45 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 19 Sep 2024 17:50:48 -0400
Subject: changed backend for filtering buttons to store only one list - the
 list of icon buttons in Doc.MyFilterHotKeys.

---
 src/client/util/CurrentUserUtils.ts                | 85 +++++++++-----------
 src/client/views/FilterPanel.tsx                   | 86 ++++++++++----------
 src/client/views/PropertiesView.tsx                |  2 +-
 src/client/views/TagsView.tsx                      | 55 ++++++-------
 .../views/collections/CollectionCardDeckView.tsx   | 27 +++----
 src/client/views/global/globalScripts.ts           | 18 +++--
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |  4 +-
 src/client/views/nodes/IconTagBox.tsx              | 91 ++++++++--------------
 src/fields/Doc.ts                                  |  8 +-
 9 files changed, 168 insertions(+), 208 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 262916312..21df0fd3b 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -671,38 +671,33 @@ pie title Minerals in my tap water
     }
     static cardTools(): Button[] {
         return [
-            { title: "Time",      icon:"hourglass-half", toolTip:"Sort by most recent document creation",      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"time",   funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Type",      icon:"eye",            toolTip:"Sort by document type",                      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"docType",funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Color",     icon:"palette",        toolTip:"Sort by document color",                     btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Tags",     icon:"bolt",        toolTip:"Sort by document's tags",                     btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"tag",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Pile",     icon:"layer-group",        toolTip:"View the cards as a pile in the free form view !",   btnType: ButtonType.ClickButton,  expertMode: false, toolType:"pile",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Chat Popup", icon:"lightbulb",     toolTip:"Toggle the chat popup's visibility!", width: 45 ,btnType: ButtonType.ToggleButton, expertMode: false, toolType:"toggle-chat",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
+            { title: "Time",      icon:"hourglass-half", toolTip:"Sort by most recent document creation",          btnType: ButtonType.ToggleButton, expertMode: false, toolType:"time",       funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Type",      icon:"eye",            toolTip:"Sort by document type",                          btnType: ButtonType.ToggleButton, expertMode: false, toolType:"docType",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Color",     icon:"palette",        toolTip:"Sort by document color",                         btnType: ButtonType.ToggleButton, expertMode: false, toolType:"color",      funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Tags",      icon:"bolt",           toolTip:"Sort by document's tags",                        btnType: ButtonType.ToggleButton, expertMode: false, toolType:"tag",        funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Pile",      icon:"layer-group",    toolTip:"View the cards as a pile in the free form view!",btnType: ButtonType.ClickButton,  expertMode: false, toolType:"pile",       funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Chat Popup",icon:"lightbulb",      toolTip:"Toggle the chat popup's visibility!", width: 45, btnType: ButtonType.ToggleButton, expertMode: false, toolType:"toggle-chat",funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
 
-            { title: "Sort",    toolTip: "Manage sort order / lock status",   icon: "sort" ,        btnType: ButtonType.MultiToggleButton,                 toolType:"alignment", ignoreClick: true,
+            { title: "Sort",      icon: "sort" ,         toolTip: "Manage sort order / lock status",               btnType: ButtonType.MultiToggleButton,               toolType:"alignment", ignoreClick: true,
               subMenu: [
-                { title: "Ascending",     toolTip: "Sort the cards in ascending order",   btnType: ButtonType.ToggleButton, icon: "sort-up",  toolType:"up",  ignoreClick: true,  scripts: {onClick: '{ return showFreeform(this.toolType, _readOnly_);}' }},
-                { title: "Descending",   toolTip: "Sort the cards in descending order",btnType: ButtonType.ToggleButton, icon: "sort-down",toolType:"down",ignoreClick: true,  scripts: {onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
+                { title: "Ascending", toolTip: "Sort the cards in ascending order", btnType: ButtonType.ToggleButton, icon: "sort-up",  toolType:"up",  ignoreClick: true,  scripts: {onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
+                { title: "Descending",toolTip: "Sort the cards in descending order",btnType: ButtonType.ToggleButton, icon: "sort-down",toolType:"down",ignoreClick: true,  scripts: {onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} },
             ]},
          ]
     }
   
     static tagGroupTools(): Button[] {
-        if (!Doc.UserDoc().activeDashboard) {
-        Doc.UserDoc().myFilterHotKeyTitles = new List<string>(['star', 'heart', 'bolt', 'cloud']);
-        
-        StrListCast(Doc.UserDoc().myFilterHotKeyTitles).forEach(key => {
-            Doc.UserDoc()[key] = key.toLowerCase();
-        });
-}
+        const defaultTagButtonDescs = [
+            { title: "Star", isSystem: false,icon: "star",  toolTip:"Click to toggle visibility of Star tagged Docs",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"#star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "Like", isSystem: false,icon: "heart", toolTip:"Click to toggle visibility of Like tagged Docs",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"#like", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "Todo", isSystem: false,icon: "bolt",  toolTip:"Click to toggle visibility of Todo tagged Docs",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"#todo", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "Idea", isSystem: false,icon: "cloud", toolTip:"Click to toggle visibility of Idea tagged Docs",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"#idea", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+        ];
         // hack: if there's no dashboard, create default filters.  otherwise, just make sure that the Options button is preserved
-        return (Doc.UserDoc().activeDashboard ? [] : [
-            { title: "star",  isSystem: false,icon: "star", toolTip:"Click to toggle the star group's visibility",   btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
-            { title: "heart", isSystem: false,icon: "heart", toolTip:"Click to toggle the heart group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
-            { title: "bolt",  isSystem: false,icon: "bolt", toolTip:"Click to toggle the bolt group's visibility",    btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
-            { title: "cloud", isSystem: false,icon: "cloud", toolTip:"Click to toggle the cloud group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
-        ]).concat([
-            { title: "Options", isSystem: true,icon: "gear", toolTip:"Click to customize your filter panel", btnType: ButtonType.ClickButton,  expertMode: false, toolType:"opts", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}
-        ])
+        return [
+            { title:"Options",isSystem: true,icon: "gear",  toolTip:"Click to customize list of filter buttons",       btnType: ButtonType.ClickButton,  expertMode: false, toolType:"-opts-",funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            ...(Doc.UserDoc().activeDashboard ? [] : defaultTagButtonDescs)
+        ]
     }
     static viewTools(): Button[] {
         return [
@@ -720,8 +715,8 @@ pie title Minerals in my tap water
             { title: "Font",     toolTip: "Font", width: 100,     btnType: ButtonType.DropdownList,                      toolType:"font",     ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'},
               btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
             { title: "Font Size",toolTip: "Font size (%size)",    btnType: ButtonType.NumberDropdownButton,              toolType:"fontSize", ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 6 },
-            { title: "Color",    toolTip: "Font color (%color)",  btnType: ButtonType.ColorButton,  icon: "font",        toolType:"fontColor",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'}},
-            { title: "Highlight",toolTip: "Font highlight",       btnType: ButtonType.ColorButton,  icon: "highlighter", toolType:"highlight",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'}},
+            { title: "Color",    toolTip: "Font color (%color)",  btnType: ButtonType.ColorButton,  icon: "font",        toolType:"fontColor",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'} },
+            { title: "Highlight",toolTip: "Font highlight",       btnType: ButtonType.ColorButton,  icon: "highlighter", toolType:"highlight",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'} },
             { title: "Bold",     toolTip: "Bold (Ctrl+B)",        btnType: ButtonType.ToggleButton, icon: "bold",        toolType:"bold",     ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
             { title: "Italic",   toolTip: "Italic (Ctrl+I)",      btnType: ButtonType.ToggleButton, icon: "italic",      toolType:"italics",  ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
             { title: "Under",    toolTip: "Underline (Ctrl+U)",   btnType: ButtonType.ToggleButton, icon: "underline",   toolType:"underline",ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
@@ -730,12 +725,12 @@ pie title Minerals in my tap water
             { title: "Vcenter",  toolTip: "Vertical center",      btnType: ButtonType.ToggleButton, icon: "pallet",      toolType:"vcent",    ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
             { title: "Align",    toolTip: "Alignment",            btnType: ButtonType.MultiToggleButton,                 toolType:"alignment", ignoreClick: true,
               subMenu: [
-                { title: "Left",     toolTip: "Left align (Cmd-[)",   btnType: ButtonType.ToggleButton, icon: "align-left",  toolType:"left",  ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}' }},
+                { title: "Left",     toolTip: "Left align (Cmd-[)",   btnType: ButtonType.ToggleButton, icon: "align-left",  toolType:"left",  ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
                 { title: "Center",   toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
                 { title: "Right",    toolTip: "Right align (Cmd-])",  btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
             ]},
-            { title: "Elide",    toolTip: "Elide selection",      btnType: ButtonType.ToggleButton, icon: "eye",         toolType:"elide",     ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'}},
-            { title: "Dictate",  toolTip: "Dictate",              btnType: ButtonType.ToggleButton, icon: "microphone",  toolType:"dictation", ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'}},
+            { title: "Elide",    toolTip: "Elide selection",      btnType: ButtonType.ToggleButton, icon: "eye",         toolType:"elide",     ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
+            { title: "Dictate",  toolTip: "Dictate",              btnType: ButtonType.ToggleButton, icon: "microphone",  toolType:"dictation", ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
             { title: "NoLink",   toolTip: "Auto Link",            btnType: ButtonType.ToggleButton, icon: "link",        toolType:"noAutoLink", expertMode:true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}},
             // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}},
             // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", scripts: {onClick:: 'toggleSuperscript()'}},
@@ -806,26 +801,20 @@ pie title Minerals in my tap water
             { title: "Num",     icon:"", toolTip: "Frame # (click to toggle edit mode)",btnType: ButtonType.TextButton,          expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
             { title: "Fwd",     icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30,  scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
             
+            { title: "Text",    icon: "Text",   toolTip: "Text functions",       subMenu: CurrentUserUtils.textTools(),   expertMode: false, toolType:DocumentType.RTF,            funcs: {                                                                             linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+            { title: "Ink",     icon: "Ink",    toolTip: "Ink functions",        subMenu: CurrentUserUtils.inkTools(),    expertMode: false, toolType:DocumentType.INK,            funcs: {hidden: `IsExploreMode()`,                                                   linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
+            { title: "Doc",     icon: "Doc",    toolTip: "Freeform Doc tools",   subMenu: CurrentUserUtils.freeTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode, true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+            { title: "View",    icon: "View",   toolTip: "View tools",           subMenu: CurrentUserUtils.viewTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+            { title: "Stack",   icon: "View",   toolTip: "Stacking tools",       subMenu: CurrentUserUtils.stackTools(),  expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
             
-            
-            { title: "Text",    icon: "Text",          toolTip: "Text functions",       subMenu: CurrentUserUtils.textTools(),   expertMode: false, toolType:DocumentType.RTF,            funcs: {                                                                             linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
-            { title: "Ink",     icon: "Ink",           toolTip: "Ink functions",        subMenu: CurrentUserUtils.inkTools(),    expertMode: false, toolType:DocumentType.INK,            funcs: {hidden: `IsExploreMode()`,                                                   linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
-            { title: "Doc",     icon: "Doc",           toolTip: "Freeform Doc tools",   subMenu: CurrentUserUtils.freeTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode, true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
-            { title: "View",    icon: "View",          toolTip: "View tools",           subMenu: CurrentUserUtils.viewTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
-            { title: "Stack",   icon: "View",          toolTip: "Stacking tools",       subMenu: CurrentUserUtils.stackTools(),  expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
-            
-            { title: "Card",    icon: "Card",          toolTip: "Card View Tools",            subMenu: CurrentUserUtils.cardTools(),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
-            // { title: "Create",   icon: "Create",         toolTip: "Assign card labels",   subMenu: CurrentUserUtils.labelTools(),  expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
-            { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
-            { title: "Video",   icon: "Video",         toolTip: "Video functions",      subMenu: CurrentUserUtils.videoTools(),  expertMode: false, toolType:DocumentType.VID,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected
-            { title: "Image",   icon: "Image",         toolTip: "Image functions",      subMenu: CurrentUserUtils.imageTools(),  expertMode: false, toolType:DocumentType.IMG,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected
-            { title: "Schema",  icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Schema is selected
-            { title: "Filter", icon:"=",     toolTip:"Filter cards by tags", btnType: ButtonType.MultiToggleButton, width: 150, ignoreClick: true,toolType:DocumentType.COL, 
-            subMenu: this.tagGroupTools(), funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, 
-            
-        },
-           ];
-           
+            { title: "Card",    icon: "Card",   toolTip: "Card View Tools",      subMenu: CurrentUserUtils.cardTools(),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+         // { title: "Create",  icon: "Create", toolTip: "Assign card labels",   subMenu: CurrentUserUtils.labelTools(),  expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+            { title: "Web",     icon: "Web",    toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
+            { title: "Video",   icon: "Video",  toolTip: "Video functions",      subMenu: CurrentUserUtils.videoTools(),  expertMode: false, toolType:DocumentType.VID,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected
+            { title: "Image",   icon: "Image",  toolTip: "Image functions",      subMenu: CurrentUserUtils.imageTools(),  expertMode: false, toolType:DocumentType.IMG,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected
+            { title: "Schema",  icon: "Schema", toolTip: "Schema functions",     subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema,   funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`}, linearBtnWidth:58 }, // Only when Schema is selected
+            { title: "Filter",  icon: "=",      toolTip: "Filter cards by tags", subMenu: CurrentUserUtils.tagGroupTools(),ignoreClick:true, toolType:DocumentType.COL,            funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, btnType: ButtonType.MultiToggleButton, width: 150},
+        ];
     }
 
     /// initializes a context menu button for the top bar context menu
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index b6bea1d4b..a3c8fc383 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -26,7 +26,7 @@ import { ObservableReactComponent } from './ObservableReactComponent';
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 
 interface HotKeyButtonProps {
-    hotKey: string;
+    hotKey: Doc;
     selected?: Doc;
 }
 
@@ -53,7 +53,7 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey /*, sel
             this.isEditing = false;
         },
         setHotKey(newHotKey: string) {
-            this.myHotKey = newHotKey;
+            this.myHotKey.title = newHotKey;
         },
     }));
 
@@ -64,24 +64,13 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey /*, sel
         state.toggleActive();
     };
 
-    const hotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
     const buttons = DocCast(Doc.UserDoc().myContextMenuBtns);
-    const filter = DocCast(buttons.Filter);
-
-    /**
-     * The doc of the button in the context menu that corresponds to the current hotkey
-     * @returns
-     */
-    const myHotKeyDoc = () => {
-        const hotKeyDocs = DocListCast(filter.data);
-        return hotKeyDocs.filter(k => StrCast(k.title) === hotKey)[0];
-    };
 
     /**
      * Removes a hotkey from list
      */
     const removeHotKey = () => {
-        Doc.RemoveDocFromList(filter, 'data', myHotKeyDoc());
+        Doc.RemFromFilterHotKeys(hotKey);
     };
 
     /**
@@ -89,11 +78,8 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey /*, sel
      * hotkey with the current icon
      */
     const updateFromInput = undoable(() => {
-        const myDoc = myHotKeyDoc();
-        Doc.UserDoc().myFilterHotKeyTitles = new List<string>(hotKeys.map(k => (k === hotKey ? state.myHotKey : k)));
-        Doc.UserDoc()[state.myHotKey] = StrCast(Doc.UserDoc()[hotKey]);
-        myDoc.title = state.myHotKey;
-        myDoc.toolTip = `Click to toggle the ${state.myHotKey}'s group's visibility`;
+        hotKey.title = StrCast(state.myHotKey.title);
+        hotKey.toolTip = `Click to toggle the ${StrCast(hotKey.title)}'s group's visibility`;
     }, '');
 
     /**
@@ -123,13 +109,12 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey /*, sel
     /**
      * Panel of icons the user can choose from to represent their tag
      */
-    const iconPanel = iconOpts.map((icon, i) => (
+    const iconPanel = iconOpts.map(icon => (
         <button
-            key={i}
-            onClick={undoable((e: React.MouseEvent) => {
+            key={icon.toString()}
+            onClick={undoable(e => {
                 e.stopPropagation;
-                Doc.UserDoc()[hotKey] = icon.toString();
-                myHotKeyDoc().icon = icon.toString();
+                hotKey[DocData].icon = icon.toString();
             }, '')}
             className="icon-panel-button">
             <FontAwesomeIcon icon={icon} color={SnappingManager.userColor} />
@@ -157,7 +142,7 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey /*, sel
                             e.stopPropagation();
                             handleClick();
                         }}>
-                        <FontAwesomeIcon icon={Doc.UserDoc()[hotKey] as IconProp} size="2xl" color={SnappingManager.userColor} />
+                        <FontAwesomeIcon icon={hotKey.icon as IconProp} size="2xl" color={SnappingManager.userColor} />
                     </button>
                 </Tooltip>
                 {state.isActive && <div className="icon-panel">{iconPanel}</div>}
@@ -166,7 +151,7 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey /*, sel
                 <input
                     ref={inputRef}
                     type="text"
-                    value={state.myHotKey.toUpperCase()}
+                    value={StrCast(state.myHotKey.title).toUpperCase()}
                     onChange={e => state.setHotKey(e.target.value)}
                     onBlur={() => {
                         state.stopEditing();
@@ -181,13 +166,12 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey /*, sel
                     className="hotkey-title-input"
                 />
             ) : (
-                <p className="hotkey-title">{hotKey.toUpperCase()}</p>
+                <p className="hotkey-title">{StrCast(hotKey.title).toUpperCase()}</p>
             )}
             <button
                 className="hotKey-close"
                 onClick={(e: React.MouseEvent) => {
                     e.stopPropagation();
-                    Doc.UserDoc().myFilterHotKeyTitles = new List<string>(hotKeys.filter(k => k !== hotKey));
                     removeHotKey();
                 }}>
                 <FontAwesomeIcon icon={'x' as IconProp} color={SnappingManager.userColor} />
@@ -310,7 +294,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
     @computed get activeRenderedFacetInfos() {
         return new Set(
             Array.from(new Set(Array.from(this._selectedFacetHeaders).concat(this.activeFacetHeaders))).map(facetHeader => {
-                const facetValues = FilterPanel.gatherFieldValues(this.targetDocChildren, facetHeader, StrListCast(this.Document.childFilters));
+                const facetValues = facetHeader.startsWith('#') ? { strings: [] } : FilterPanel.gatherFieldValues(this.targetDocChildren, facetHeader, StrListCast(this.Document.childFilters));
 
                 let nonNumbers = 0;
                 let minVal = Number.MAX_VALUE;
@@ -328,7 +312,10 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
                 if (facetHeader === 'text') {
                     return { facetHeader, renderType: 'text' };
                 }
-                if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) {
+                if (facetHeader.startsWith('#')) {
+                    return { facetHeader, renderType: 'togglebox' };
+                }
+                if (facetHeader !== 'tags' && !facetHeader.startsWith('#') && nonNumbers / facetValues.strings.length < 0.1) {
                     const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1));
                     const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05)));
                     const ranged: number[] | undefined = Doc.readDocRangeFilter(this.Document, facetHeader); // not the filter range, but the zooomed in range on the filter
@@ -400,30 +387,23 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
     addHotkey = (hotKey: string) => {
         const buttons = DocCast(Doc.UserDoc().myContextMenuBtns);
         const filter = DocCast(buttons.Filter);
+        const title = hotKey.startsWith('#') ? hotKey.substring(1) : hotKey;
 
         const newKey: Button = {
-            title: hotKey,
-            icon: 'bolt',
-            toolTip: `Click to toggle the ${hotKey}'s group's visibility`,
+            title,
+            icon: 'question',
+            toolTip: `Click to toggle the ${title}'s group's visibility`,
             btnType: ButtonType.ToggleButton,
             expertMode: false,
-            toolType: 'bolt',
+            toolType: '#' + title,
             funcs: {},
             scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}' },
         };
 
-        const currHotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
-
-        Doc.UserDoc().myFilterHotKeyTitles = new List<string>(currHotKeys.concat(hotKey));
-
-        Doc.UserDoc()[hotKey] = 'bolt';
-
         const newBtn = CurrentUserUtils.setupContextMenuBtn(newKey, filter);
         newBtn.isSystem = newBtn[DocData].isSystem = undefined;
 
-        const subDocs = DocListCast(filter.data);
-        const opts = subDocs[subDocs.length - 1];
-        Doc.AddDocToList(filter, 'data', newBtn, opts, true);
+        Doc.AddToFilterHotKeys(newBtn);
     };
 
     /**
@@ -432,11 +412,11 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
      */
     hotKeyButtons = () => {
         const selected = DocumentView.SelectedDocs().lastElement();
-        const hotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
+        const hotKeys = Doc.MyFilterHotKeys;
 
         // Selecting a button should make it so that the icon on the top filter panel becomes said icon
         const buttons = hotKeys.map(hotKey => (
-            <Tooltip key={hotKey} title={<div className="dash-tooltip">Click to customize this hotkey&apos;s icon</div>}>
+            <Tooltip key={StrCast(hotKey.title)} title={<div className="dash-tooltip">Click to customize this hotkey&apos;s icon</div>}>
                 <HotKeyIconButton hotKey={hotKey} selected={selected} />
             </Tooltip>
         ));
@@ -561,6 +541,22 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
                         </div>
                     );
                 });
+            case 'togglebox':
+                return (
+                    <div>
+                        <input
+                            style={{ width: 20, marginLeft: 20 }}
+                            checked={['check', 'exists'].includes(
+                                StrListCast(this.Document._childFilters)
+                                    .find(filter => filter.split(Doc.FilterSep)[0] === 'tags' && filter.split(Doc.FilterSep)[1] === facetHeader)
+                                    ?.split(Doc.FilterSep)[2] ?? ''
+                            )}
+                            type={'checkbox'}
+                            onChange={undoable(e => Doc.setDocFilter(this.Document, 'tags', facetHeader, e.target.checked ? 'check' : 'remove'), 'set filter')}
+                        />
+                        -set-
+                    </div>
+                );
 
             case 'range':
                 {
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index e940ba6f9..f3221d005 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -1176,7 +1176,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     @computed get filtersSubMenu() {
         return (
             // prettier-ignore
-            <PropertiesSection title="Filters" isOpen={this.openFilters} setIsOpen={bool => { this.openFilters = bool; }} onDoubleClick={this.CloseAll}>
+            <PropertiesSection title="Filters" isOpen={this.openFilters} setIsOpen={action(bool => { this.openFilters = bool; })} onDoubleClick={this.CloseAll}>
                 <div className="propertiesView-content filters" style={{ position: 'relative', height: 'auto' }}>
                     <FilterPanel Document={this.selectedDoc ?? Doc.ActiveDashboard!} />
                 </div>
diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx
index cae30218c..2f30edb80 100644
--- a/src/client/views/TagsView.tsx
+++ b/src/client/views/TagsView.tsx
@@ -371,26 +371,20 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
                                 style={{ width: '8px' }}
                                 tooltip="Close Menu"
                                 onPointerDown={e =>
-                                    setupMoveUpEvents(
-                                        this,
-                                        e,
-                                        returnFalse,
-                                        emptyFunction,
-                                        upEv => {
-                                            this.setToEditing(!this._isEditing);
-                                            upEv.stopPropagation();
-                                        }
-                                    )
+                                    setupMoveUpEvents(this, e, returnFalse, emptyFunction, upEv => {
+                                        this.setToEditing(!this._isEditing);
+                                        upEv.stopPropagation();
+                                    })
                                 }
                                 icon={<FontAwesomeIcon icon={this._isEditing ? 'chevron-up' : 'chevron-down'} size="sm" />}
                             />
                         )}
                         <IconTagBox Views={this._props.Views} IsEditing={this._isEditing} />
                         {Array.from(tagsList)
-                            .filter(tag => tag.startsWith('#') || tag.startsWith('@'))
-                            .map((tag, i) => (
+                            .filter(tag => (tag.startsWith('#') || tag.startsWith('@')) && !Doc.MyFilterHotKeys.some(key => key.toolType === tag))
+                            .map(tag => (
                                 <TagItem
-                                    key={i}
+                                    key={tag}
                                     docs={this._props.Views.map(view => view.Document)}
                                     tag={tag}
                                     tagDoc={TagItem.findTagCollectionDoc(tag) ?? TagItem.createTagCollectionDoc(tag)}
@@ -398,8 +392,8 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
                                     showRemoveUI={this.isEditing}
                                 />
                             ))}
-                        {Array.from(facesList).map((tag, i) => (
-                            <TagItem key={i} docs={this._props.Views.map(view => view.Document)} tag={tag} tagDoc={undefined} setToEditing={this.setToEditing} showRemoveUI={this.isEditing} />
+                        {Array.from(facesList).map(tag => (
+                            <TagItem key={tag} docs={this._props.Views.map(view => view.Document)} tag={tag} tagDoc={undefined} setToEditing={this.setToEditing} showRemoveUI={this.isEditing} />
                         ))}
                     </div>
                     {this.isEditing ? (
@@ -421,9 +415,9 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
                                 />
                             </div>
                             <div className="tagsView-suggestions-box">
-                                {TagItem.AllTagCollectionDocs.filter(doc => StrCast(doc.title).startsWith('#') || StrCast(doc.title).startsWith('@')).map(doc => {
-                                    const tag = StrCast(doc.title);
-                                    return (
+                                {TagItem.AllTagCollectionDocs.map(doc => StrCast(doc.title))
+                                    .filter(tag => (tag.startsWith('#') || tag.startsWith('@')) && !Doc.MyFilterHotKeys.some(key => key.toolType === tag))
+                                    .map(tag => (
                                         <Button
                                             style={{ margin: '2px 2px', border: '1px solid black', backgroundColor: 'lightblue', color: 'black' }}
                                             text={tag}
@@ -432,20 +426,17 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
                                             onClick={() => this.submitTag(tag)}
                                             key={tag}
                                         />
-                                    );
-                                })}
-                                {Array.from(chatTagsList).map(tag => {
-                                    return (
-                                        <Button
-                                            style={{ margin: '2px 2px', border: '1px solid black', backgroundColor: 'lightpink', color: 'black' }}
-                                            text={tag}
-                                            color={SnappingManager.userVariantColor}
-                                            tooltip="Add existing tag"
-                                            onClick={() => this.submitTag(tag)}
-                                            key={tag}
-                                        />
-                                    );
-                                })}
+                                    ))}
+                                {Array.from(chatTagsList).map(tag => (
+                                    <Button
+                                        style={{ margin: '2px 2px', border: '1px solid black', backgroundColor: 'lightpink', color: 'black' }}
+                                        text={tag}
+                                        color={SnappingManager.userVariantColor}
+                                        tooltip="Add existing tag"
+                                        onClick={() => this.submitTag(tag)}
+                                        key={tag}
+                                    />
+                                ))}
                             </div>
                         </div>
                     ) : null}
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index cab7d51e4..bcb42111f 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -328,24 +328,22 @@ export class CollectionCardView extends CollectionSubView() {
      */
 
     tagValue = (doc: Doc) => {
-        const keys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
+        const keys = Doc.MyFilterHotKeys;
 
-        const isTagActive = (buttonID: number) => {
-            return BoolCast(doc[StrCast(Doc.UserDoc()[keys[buttonID]])]);
-        };
+        const isTagActive = (key: Doc) => BoolCast(key.active);
 
         let base = '';
         let fraction = '';
 
-        for (let i = 0; i < keys.length; i++) {
-            if (isTagActive(i)) {
+        keys.forEach(key => {
+            if (isTagActive(key)) {
                 if (base === '') {
-                    base = i.toString(); // First active tag becomes the base
+                    base = StrCast(key.toolType); // First active tag becomes the base
                 } else {
-                    fraction += i.toString(); // Subsequent active tags become part of the fraction
+                    fraction += StrCast(key.toolType); // Subsequent active tags become part of the fraction
                 }
             }
-        }
+        });
 
         // If no tag was active, return 0 by default
         if (base === '') {
@@ -553,17 +551,16 @@ export class CollectionCardView extends CollectionSubView() {
                         doc.chatIndex = index;
                         break;
                     case '1': {
-                        const allHotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
+                        const allHotKeys = Doc.MyFilterHotKeys;
 
                         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]]);
+                                // bcz: CHECK THIS CODE OUT -- SOMETHING CHANGED
+                                const keyTag = StrCast(allHotKeys[i].toolType);
+                                if (tag.includes(keyTag)) {
+                                    myTag = keyTag;
                                     break;
                                 }
                             }
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index d2cee39e2..f7a9689c9 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -270,27 +270,29 @@ ScriptingGlobals.add(function showFreeform(attr: 'hcenter' | 'vcenter' | 'grid'
 });
 
 /**
- * Applies a filter to the selected document (or, if the settins button is pressed, opens the filter panel)
+ * Applies a filter to the selected document (or, if the settings button is pressed, opens the filter panel)
  */
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function handleTags(value: string, checkResult?: boolean) {
     const selected = DocumentView.SelectedDocs().lastElement();
+    const isOptions = value === '-opts-';
 
-    function isAttrFiltered(attr: string) {
-        return StrListCast(selected._childFilters).some(filter => filter.includes(attr));
-    }
+    const isAttrFiltered = (attr: string) =>
+        StrListCast(selected._childFilters)
+            .map(filter => filter.split(Doc.FilterSep))
+            .some(([key, val]) => key === 'tags' && val === attr);
 
     if (checkResult) {
-        return value === 'opts' ? PropertiesView.Instance?.openFilters : isAttrFiltered(value);
+        return isOptions ? false : isAttrFiltered(value);
     }
 
-    if (value != 'opts') {
-        isAttrFiltered(value) ? Doc.setDocFilter(selected, value, true, 'remove') : Doc.setDocFilter(selected, value, true, 'match');
+    if (!isOptions) {
+        isAttrFiltered(value) ? Doc.setDocFilter(selected, 'tags', value, 'remove') : Doc.setDocFilter(selected, 'tags', value, 'check');
     } else {
         SnappingManager.PropertiesWidth < 5 && SnappingManager.SetPropertiesWidth(0);
         SnappingManager.SetPropertiesWidth(MainView.Instance.propertiesWidth() < 15 ? 250 : 0);
         PropertiesView.Instance?.CloseAll();
-        PropertiesView.Instance.openFilters = true;
+        runInAction(() => (PropertiesView.Instance.openFilters = SnappingManager.PropertiesWidth > 5));
     }
 
     return undefined;
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index f53a7d163..0e4288574 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -267,6 +267,8 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
         const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string;
         const items = DocListCast(this.dataDoc.data);
         const multiDoc = this.Document;
+        const selectedItem = StrCast(items.find(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, value: undefined, _readOnly_: true }).result)?.toolType ?? StrCast(multiDoc.toolType));
+        console.log(selectedItem);
         return (
             <MultiToggle
                 tooltip={`Toggle ${tooltip}`}
@@ -282,7 +284,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
                     tooltip: StrCast(item.toolTip),
                     val: StrCast(item.toolType),
                 }))}
-                selectedVal={StrCast(items.find(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, value: undefined, _readOnly_: true }).result)?.toolType ?? StrCast(multiDoc.toolType))}
+                selectedVal={selectedItem}
                 setSelectedVal={(val: string | number) => {
                     const itemDoc = items.find(item => item.toolType === val);
                     itemDoc && ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, value: val, _readOnly_: false });
diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx
index 70992e28a..01dca0fbb 100644
--- a/src/client/views/nodes/IconTagBox.tsx
+++ b/src/client/views/nodes/IconTagBox.tsx
@@ -1,21 +1,21 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { computed } from 'mobx';
+import { action, computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import React from 'react';
-import { emptyFunction, numberRange } from '../../../Utils';
-import { Doc, StrListCast } from '../../../fields/Doc';
+import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
+import { Doc } from '../../../fields/Doc';
 import { StrCast } from '../../../fields/Types';
 import { SnappingManager } from '../../util/SnappingManager';
 import { undoable } from '../../util/UndoManager';
 import { MainView } from '../MainView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { PropertiesView } from '../PropertiesView';
+import { TagItem } from '../TagsView';
 import { DocumentView } from './DocumentView';
 import './IconTagBox.scss';
-import { TagItem } from '../TagsView';
-import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
 
 export interface IconTagProps {
     Views: DocumentView[];
@@ -30,48 +30,36 @@ export interface IconTagProps {
 export class IconTagBox extends ObservableReactComponent<IconTagProps> {
     constructor(props: IconTagProps) {
         super(props);
+        makeObservable(this);
     }
 
-    @computed get View() {
-        return this._props.Views.lastElement();
-    }
-    @computed get currentScale() {
-        return this.View?.screenToLocalScale();
-    }
+    @computed get View() { return this._props.Views.lastElement(); } // prettier-ignore
+    @computed get currentScale() { return this.View?.screenToLocalScale(); } // prettier-ignore
 
     /**
-     * Opens the filter panel in the properties menu
+     * Sets or removes the specified tag
+     * @param tag tag name (should begin with '#')
+     * @param state flag to add or remove the metadata
      */
-
-    openHotKeyMenu = () => {
-        SnappingManager.PropertiesWidth < 5 && SnappingManager.SetPropertiesWidth(0);
-        SnappingManager.SetPropertiesWidth(MainView.Instance.propertiesWidth() < 15 ? 250 : 0);
-
-        PropertiesView.Instance.CloseAll();
-        PropertiesView.Instance.openFilters = true;
-    };
-
-    /**
-     * @param buttonID
-     * @param doc
-     */
-    setIconTag = undoable((icon: string, state: boolean) => {
+    setIconTag = undoable((tag: string, state: boolean) => {
         this._props.Views.forEach(view => {
-            state && TagItem.addTagToDoc(view.dataDoc, icon);
-            !state && TagItem.removeTagFromDoc(view.dataDoc, icon);
-            view.dataDoc[icon] = state;
+            state && TagItem.addTagToDoc(view.dataDoc, tag);
+            !state && TagItem.removeTagFromDoc(view.dataDoc, tag);
         });
     }, 'toggle card tag');
 
     /**
-     * Determines whether or not the given icon is active depending on the doc's data
-     * @param doc
-     * @param icon
-     * @returns
+     * Returns a renderable version of the button Doc that is colorized to indicate
+     * whether the doc has the associated tag set on it or not.
+     * @param doc doc to test
+     * @param key metadata icon button
+     * @returns an icon for the metdata button
      */
-    getButtonIcon = (doc: Doc, icon: IconProp): JSX.Element => {
-        const isActive = TagItem.docHasTag(doc, icon.toString()); // doc[icon.toString()];
-        const color = isActive ? '#4476f7' : '#323232';
+    getButtonIcon = (doc: Doc, key: Doc): JSX.Element => {
+        const icon = StrCast(key.icon) as IconProp;
+        const tag = StrCast(key.toolType);
+        const isActive = TagItem.docHasTag(doc, tag);
+        const color = isActive ? '#4476f7' : '#323232'; // TODO should use theme colors
 
         return <FontAwesomeIcon icon={icon} style={{ color, height: '20px', width: '20px' }} />;
     };
@@ -80,37 +68,26 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
      * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups
      */
     render() {
-        const amButtons = StrListCast(Doc.UserDoc().myFilterHotKeyTitles).length + 1;
-
-        const keys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
-
-        const iconMap = (buttonID: number) => {
-            return StrCast(Doc.UserDoc()[keys[buttonID]]) as IconProp;
-        };
-        const buttons = numberRange(amButtons - 1)
-            .filter(i => this._props.IsEditing || this.View.Document[iconMap(i).toString()] || (DocumentView.Selected.length === 1 && this.View.IsSelected))
-            .map(i => (
-                <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from the {iconMap(i).toString()} group</div>}>
+        const buttons = Doc.MyFilterHotKeys 
+            .map(key => ({ key, tag: StrCast(key.toolType) }))
+            .filter(({ tag }) => this._props.IsEditing || this.View.Document[tag] || (DocumentView.Selected.length === 1 && this.View.IsSelected))
+            .map(({ key, tag }) => (
+                <Tooltip key={tag} title={<div className="dash-tooltip">Click to add/remove this card from the {tag} group</div>}>
                     <button
-                        key={i}
                         type="button"
                         onPointerDown={e =>
                             setupMoveUpEvents(this, e, returnFalse, emptyFunction, clickEv => {
-                                const state = TagItem.docHasTag(this.View.Document, iconMap(i).toString()); // this.View.Document[iconMap(i).toString()];
-                                this.setIconTag(iconMap(i), !state);
+                                this.setIconTag(tag, !TagItem.docHasTag(this.View.Document, tag));
                                 clickEv.stopPropagation();
                             })
                         }>
-                        {this.getButtonIcon(this.View.Document, iconMap(i))}
+                        {this.getButtonIcon(this.View.Document, key)}
                     </button>
                 </Tooltip>
-            ));
+            )); // prettier-ignore
+
         return !buttons.length ? null : (
-            <div
-                className="card-button-container"
-                style={{
-                    fontSize: '50px',
-                }}>
+            <div className="card-button-container" style={{ fontSize: '50px' }}>
                 {buttons}
             </div>
         );
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 7abba7679..f6b7708b3 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -256,7 +256,13 @@ export class Doc extends RefField {
     public static set ActivePresentation(val) { Doc.ActiveDashboard && (Doc.ActiveDashboard.activePresentation = val) } // prettier-ignore
     public static get ActiveDashboard()       { return DocCast(Doc.UserDoc().activeDashboard); } // prettier-ignore
     public static set ActiveDashboard(val: Opt<Doc>) {  Doc.UserDoc().activeDashboard = val; } // prettier-ignore
-
+    public static get MyFilterHotKeys()       { return DocListCast(DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter)?.data).filter(key => key.toolType !== "-opts-"); } // prettier-ignore
+    public static RemFromFilterHotKeys(doc: Doc) {
+        return Doc.RemoveDocFromList(DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter), 'data', doc);
+    }
+    public static AddToFilterHotKeys(doc: Doc) {
+        return Doc.AddDocToList(DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter), 'data', doc);
+    }
     public static IsInMyOverlay(doc: Doc)     { return Doc.MyOverlayDocs.includes(doc); } // prettier-ignore
     public static AddToMyOverlay(doc: Doc)    { return Doc.ActiveDashboard ? Doc.AddDocToList(Doc.ActiveDashboard, 'myOverlayDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
     public static RemFromMyOverlay(doc: Doc)  { return Doc.ActiveDashboard ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myOverlayDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
-- 
cgit v1.2.3-70-g09d2


From a64b1f1949390b1c787fedeb0696256047bf459a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 20 Sep 2024 14:38:29 -0400
Subject: fixed sorting by tags in card view

---
 .../views/collections/CollectionCardDeckView.tsx   | 45 +++++-----------------
 src/client/views/nodes/IconTagBox.tsx              |  5 +--
 2 files changed, 11 insertions(+), 39 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 bcb42111f..d9c27254a 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -3,7 +3,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { ClientUtils, DashColor, returnFalse, returnZero } from '../../../ClientUtils';
 import { emptyFunction } from '../../../Utils';
-import { Doc, StrListCast } from '../../../fields/Doc';
+import { Doc } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
@@ -18,6 +18,7 @@ import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoable } from '../../util/UndoManager';
 import { StyleProp } from '../StyleProp';
+import { TagItem } from '../TagsView';
 import { DocumentView } from '../nodes/DocumentView';
 import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
 import './CollectionCardDeckView.scss';
@@ -242,7 +243,6 @@ export class CollectionCardView extends CollectionSubView() {
         const currRow = Math.floor((mouseY - 100) / rowHeight); //rows start at 0
 
         if (adjustedX < 0) {
-            console.log('DROP INDEX NO ');
             return 0; // Before the first column
         }
 
@@ -257,7 +257,6 @@ export class CollectionCardView extends CollectionSubView() {
 
             index = Math.floor(adjustedX / cardWidth) + currRow * this._maxRowCount;
         }
-        console.log('DROP INDEX = ' + index);
         return index;
     };
 
@@ -327,35 +326,11 @@ export class CollectionCardView extends CollectionSubView() {
      * @returns its value based on its tags
      */
 
-    tagValue = (doc: Doc) => {
-        const keys = Doc.MyFilterHotKeys;
-
-        const isTagActive = (key: Doc) => BoolCast(key.active);
-
-        let base = '';
-        let fraction = '';
-
-        keys.forEach(key => {
-            if (isTagActive(key)) {
-                if (base === '') {
-                    base = StrCast(key.toolType); // First active tag becomes the base
-                } else {
-                    fraction += StrCast(key.toolType); // Subsequent active tags become part of the fraction
-                }
-            }
-        });
-
-        // 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);
-    };
+    tagValue = (doc: Doc) =>
+        Doc.MyFilterHotKeys.map((key, i) => ({ has: TagItem.docHasTag(doc, StrCast(key.toolType)), i }))
+            .filter(({ has }) => has)
+            .map(({ i }) => i)
+            .join('.');
 
     /**
      * Called in the sortedDocsType method. Compares the cards' value in regards to the desired sort type-- earlier cards are move to the
@@ -492,7 +467,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()}======`;
         });
@@ -532,7 +507,7 @@ export class CollectionCardView extends CollectionSubView() {
 
         if (questionType === '2' || questionType === '4') {
             this.childDocs.forEach(d => {
-                d['chatFilter'] = false;
+                d.chatFilter = false;
             });
         }
 
@@ -573,7 +548,7 @@ export class CollectionCardView extends CollectionSubView() {
                     }
                     case '2':
                     case '4':
-                        doc['chatFilter'] = true;
+                        doc.chatFilter = true;
                         Doc.setDocFilter(DocCast(this.Document.embedContainer), 'chatFilter', true, 'match');
                         break;
                 }
diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx
index 292f9b523..8faf8ffa5 100644
--- a/src/client/views/nodes/IconTagBox.tsx
+++ b/src/client/views/nodes/IconTagBox.tsx
@@ -1,18 +1,15 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { action, computed, makeObservable } from 'mobx';
+import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import React from 'react';
 import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils';
 import { emptyFunction } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
 import { StrCast } from '../../../fields/Types';
-import { SnappingManager } from '../../util/SnappingManager';
 import { undoable } from '../../util/UndoManager';
-import { MainView } from '../MainView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
-import { PropertiesView } from '../PropertiesView';
 import { TagItem } from '../TagsView';
 import { DocumentView } from './DocumentView';
 import './IconTagBox.scss';
-- 
cgit v1.2.3-70-g09d2


From 03374621c3739768850e4e183fbb5d1c278b2290 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 20 Sep 2024 15:01:28 -0400
Subject: removed assignment of OR filter mode from card view. not sure it's
 needed, but shouldn't be set automatically anyway.

---
 src/client/documents/Documents.ts                       | 4 ++--
 src/client/views/collections/CollectionCardDeckView.tsx | 2 --
 2 files changed, 2 insertions(+), 4 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index aac15059e..00edaddcc 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -319,11 +319,11 @@ export class DocumentOptions {
     contextMenuLabels?: List<string>;
     contextMenuIcons?: List<string>;
     childContentPointerEvents?: string; // pointer events allowed for content of a document view.  eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
-    childFilters_boolean?: string;
+    childFilters_boolean?: STRt = new StrInfo('boolean operator to apply to filters on different metadata fields. Value should be AND or OR. Default is AND');
     childFilters?: List<string>;
     childLimitHeight?: NUMt = new NumInfo('whether to limit the height of collection children.  0 - means  height can be no bigger than width', false);
     childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox layout in tree view)
-    childLayoutString?: string; // template string for collection to use to render its children
+    childLayoutString?: STRt = new StrInfo('JSX layout string for rendering children of a (collection) Doc'); // template string for collection to use to render its children
     childDocumentsActive?: BOOLt = new BoolInfo('whether child documents are active when parent is document active');
     childLayoutFitWidth?: BOOLt = new BoolInfo("whether a child doc's fitWith should be overriden by collection");
     childDontRegisterViews?: BOOLt = new BoolInfo('whether child document views should be registered so that they can be found when following links, etc');
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index d9c27254a..70b751f4c 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -99,8 +99,6 @@ export class CollectionCardView extends CollectionSubView() {
     };
 
     componentDidMount() {
-        this.Document.childFilters_boolean = 'OR'; // bcz: really shouldn't be assigning to fields from within didMount -- this should be a default/override beahavior somehow
-
         // Reaction to cardSort changes
         this._disposers.sort = reaction(
             () => GPTPopup.Instance.visible,
-- 
cgit v1.2.3-70-g09d2


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.

---
 src/client/views/DocumentDecorations.tsx           | 11 ++-
 .../views/collections/CollectionCardDeckView.tsx   | 83 +++++++++++++---------
 src/client/views/linking/LinkMenuItem.tsx          |  2 +-
 3 files changed, 62 insertions(+), 34 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index c3bcfdcca..fc4b1f0af 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { IconButton } from 'browndash-components';
-import { action, computed, makeObservable, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { FaUndo } from 'react-icons/fa';
@@ -36,6 +36,7 @@ import { ImageBox } from './nodes/ImageBox';
 import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { TagsView } from './TagsView';
+import { setTime } from 'react-datepicker/dist/date_utils';
 
 interface DocumentDecorationsProps {
     PanelWidth: number;
@@ -643,7 +644,9 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         return this._rotCenter;
     }
 
+    @observable forceRender = 0;
     render() {
+        this.forceRender;
         const { b, r, x, y } = this.Bounds;
         const seldocview = DocumentView.Selected().lastElement();
         if (SnappingManager.IsDragging || r - x < 1 || x === Number.MAX_VALUE || !seldocview || this._hidden || isNaN(r) || isNaN(b) || isNaN(x) || isNaN(y)) {
@@ -656,6 +659,12 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             return null;
         }
 
+        if (seldocview && !seldocview?.ContentDiv?.getBoundingClientRect().width) {
+            setTimeout(action(() => this.forceRender++));
+            return null;
+        }
+        console.log('Renderedr');
+
         // sharing
         const acl = GetEffectiveAcl(!this._showLayoutAcl ? Doc.GetProto(seldocview.Document) : seldocview.Document);
         const docShareMode = HierarchyMapping.get(acl)!.name;
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,
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index f54d8311d..b24fca8e2 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -90,7 +90,7 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
             moveEv => {
                 const dragData = new DragManager.DocumentDragData([this._props.linkDoc], dropActionType.embed);
                 dragData.dropPropertiesToRemove = ['hidden'];
-                DragManager.StartDocumentDrag([this._editRef.current!], dragData, moveEv.x, moveEv.y);
+                DragManager.StartDocumentDrag([this._editRef.current!], dragData, moveEv.x, moveEv.y, undefined, e => (this._props.linkDoc._layout_isSvg = true));
                 return true;
             },
             emptyFunction,
-- 
cgit v1.2.3-70-g09d2

