From c5b806c3b449cd81aee673a01238c4dae9402cf3 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Thu, 6 Jun 2024 14:59:04 -0400
Subject: lol

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

(limited to 'src/client/views/nodes/FontIconBox/FontIconBox.tsx')

diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index ffb668b03..8317d62a6 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -192,7 +192,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
         } else {
             text = script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result;
             // text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
-            getStyle = (val: string) => ({ fontFamily: val });
+            //   getStyle = (val: string) => ({ fontFamily: val });
         }
 
         // Get items to place into the list
-- 
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/nodes/FontIconBox/FontIconBox.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/nodes/FontIconBox/FontIconBox.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 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/nodes/FontIconBox/FontIconBox.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 11bf7c3c04b875ab8c0003001df600675470dca7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 20 Sep 2024 14:03:34 -0400
Subject: fixed filter dropdown to work properly with multiselect.

---
 package-lock.json                                  |  8 ++++----
 package.json                                       |  2 +-
 src/client/documents/DocUtils.ts                   |  3 +++
 src/client/util/CurrentUserUtils.ts                | 10 +++++-----
 src/client/views/global/globalScripts.ts           | 20 ++++++++++----------
 src/client/views/nodes/FontIconBox/FontIconBox.tsx | 21 ++++++++++++---------
 6 files changed, 35 insertions(+), 29 deletions(-)

(limited to 'src/client/views/nodes/FontIconBox/FontIconBox.tsx')

diff --git a/package-lock.json b/package-lock.json
index 5e2e67839..fac1cc20b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -70,7 +70,7 @@
                 "body-parser": "^1.20.2",
                 "bootstrap": "^5.3.2",
                 "brotli": "^1.3.3",
-                "browndash-components": "^0.1.45",
+                "browndash-components": "^0.1.47",
                 "browser-assert": "^1.2.1",
                 "bson": "^6.2.0",
                 "canvas": "^2.11.2",
@@ -11553,9 +11553,9 @@
             }
         },
         "node_modules/browndash-components": {
-            "version": "0.1.45",
-            "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.1.45.tgz",
-            "integrity": "sha512-A2/3SikcA1kntY8jajwWJjnRGVRP9+DheaOMh9fAdgiQrdMZVMty+nUasbXk9HjyjkMfAUAkGvCR+SS28QL/zA==",
+            "version": "0.1.47",
+            "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.1.47.tgz",
+            "integrity": "sha512-562nWku81I1wccgEQbMU/30+RP/81sxsIGoPGLH7Otm9TdrHXWgW8yJS4tzBPXBF03Q2AX8DyQusi6T0LSc7Mw==",
             "dependencies": {
                 "@emotion/react": "^11.11.1",
                 "@emotion/styled": "^11.11.0",
diff --git a/package.json b/package.json
index 967549197..7e521eb37 100644
--- a/package.json
+++ b/package.json
@@ -149,7 +149,7 @@
         "body-parser": "^1.20.2",
         "bootstrap": "^5.3.2",
         "brotli": "^1.3.3",
-        "browndash-components": "^0.1.45",
+        "browndash-components": "^0.1.47",
         "browser-assert": "^1.2.1",
         "bson": "^6.2.0",
         "canvas": "^2.11.2",
diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts
index 30b71a09b..4d105e372 100644
--- a/src/client/documents/DocUtils.ts
+++ b/src/client/documents/DocUtils.ts
@@ -241,7 +241,10 @@ export namespace DocUtils {
             Object.keys(scripts).forEach(key => {
                 const script = scripts[key] as string;
                 if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && script) {
+                    const additionalItems: { [key: string]: unknown } = {};
+                    script.match(/_[a-zA-Z]*_/)?.forEach(match => (additionalItems[match] = 'any'));
                     (key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = ScriptField.MakeScript(script, {
+                        ...additionalItems,
                         this: Doc.name,
                         dragData: Doc.DocDragDataName,
                         value: 'any',
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 21df0fd3b..e72acf93f 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -688,14 +688,14 @@ pie title Minerals in my tap water
   
     static tagGroupTools(): Button[] {
         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_);}'}},
+            { 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 setTagFilter(this.toolType, _added_, _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 setTagFilter(this.toolType, _added_, _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 setTagFilter(this.toolType, _added_, _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 setTagFilter(this.toolType, _added_, _readOnly_);}'}},
         ];
         // hack: if there's no dashboard, create default filters.  otherwise, just make sure that the Options button is preserved
         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_);}'}},
+            { 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 setTagFilter(this.toolType, false,_readOnly_);}'}},
             ...(Doc.UserDoc().activeDashboard ? [] : defaultTagButtonDescs)
         ]
     }
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index f7a9689c9..588073568 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -270,24 +270,24 @@ ScriptingGlobals.add(function showFreeform(attr: 'hcenter' | 'vcenter' | 'grid'
 });
 
 /**
- * Applies a filter to the selected document (or, if the settings button is pressed, opens the filter panel)
+ * Applies (or removes) a filter to the selected document for the specified tag
+ * NOTE: this also opens the filter panel if the settings button is clicked (probably should be a different function)
  */
 // eslint-disable-next-line prefer-arrow-callback
-ScriptingGlobals.add(function handleTags(value: string, checkResult?: boolean) {
+ScriptingGlobals.add(function setTagFilter(tag: string, added: boolean, checkResult?: boolean) {
     const selected = DocumentView.SelectedDocs().lastElement();
-    const isOptions = value === '-opts-';
-
-    const isAttrFiltered = (attr: string) =>
-        StrListCast(selected._childFilters)
-            .map(filter => filter.split(Doc.FilterSep))
-            .some(([key, val]) => key === 'tags' && val === attr);
+    const isOptions = tag === '-opts-';
 
     if (checkResult) {
-        return isOptions ? false : isAttrFiltered(value);
+        return isOptions
+            ? false
+            : StrListCast(selected._childFilters) // check all filters for one that filters tags:value where value is the tag's name
+                  .map(filter => filter.split(Doc.FilterSep))
+                  .some(([key, val]) => key === 'tags' && val === tag);
     }
 
     if (!isOptions) {
-        isAttrFiltered(value) ? Doc.setDocFilter(selected, 'tags', value, 'remove') : Doc.setDocFilter(selected, 'tags', value, 'check');
+        added ? Doc.setDocFilter(selected, 'tags', tag, 'check') : Doc.setDocFilter(selected, 'tags', tag, 'remove');
     } else {
         SnappingManager.PropertiesWidth < 5 && SnappingManager.SetPropertiesWidth(0);
         SnappingManager.SetPropertiesWidth(MainView.Instance.propertiesWidth() < 15 ? 250 : 0);
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 0e4288574..cb0c4d188 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -266,28 +266,31 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
         // Colors
         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);
+        const selectedItems = items.filter(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, value: undefined, _readOnly_: true }).result).map(item => StrCast(item.toolType));
         return (
             <MultiToggle
                 tooltip={`Toggle ${tooltip}`}
                 type={Type.PRIM}
                 color={color}
-                onPointerDown={e => script && !toggleStatus && setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => script.run({ this: multiDoc, value: undefined, _readOnly_: false }))}
+                multiSelect={true}
+                onPointerDown={e => script && !toggleStatus && setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => script.run({ this: this.Document, value: undefined, _readOnly_: false }))}
                 isToggle={script ? true : false}
                 toggleStatus={toggleStatus}
                 //background={SnappingManager.userBackgroundColor}
                 label={this.label}
-                items={DocListCast(this.dataDoc.data).map(item => ({
+                items={items.map(item => ({
                     icon: <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={StrCast(item.icon) as IconProp} color={color} />,
                     tooltip: StrCast(item.toolTip),
                     val: StrCast(item.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 });
+                selectedItems={selectedItems}
+                onSelectionChange={(val: (string | number) | (string | number)[], added: boolean) => {
+                    // note: the multitoggle is telling us whether the selection was toggled on or off, but we ignore this since we know the state of all the buttons
+                    //   and control it through the selectedItems prop.  Therefore, the callback script will have to re-determine the toggle information.
+                    //   it would be better to pas the 'added' flag to the callback script, but our script generator from currentUserUtils makes it hard to define
+                    //   arbitrary parameter variables (but it could be done as a special case or with additional effort when creating the sript)
+                    const itemsChanged = items.filter(item => (val instanceof Array ? val.includes(item.toolType as string | number) : item.toolType === val));
+                    itemsChanged.forEach(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, _added_: added, itemDoc, _readOnly_: false }));
                 }}
             />
         );
-- 
cgit v1.2.3-70-g09d2

