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

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

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index dc40562e8..b5d819b97 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -832,7 +832,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                 <div
                                     className="link-button-container"
                                     style={{
-                                        top: `${doc[DocData].showLabels ? 4 + (doc._keywordHeight as number) : 4}px`,
+                                        top: `${(doc[DocData].showLabels || doc[DocData].showIconTags) ? 4 + (doc._keywordHeight as number) : 4}px`,
                                         transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
                                     }}>
                                     <DocumentButtonBar views={() => DocumentView.Selected()} />
-- 
cgit v1.2.3-70-g09d2


From a882dc6899a6b11e2e972f3e2a2ae991caeea094 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Tue, 6 Aug 2024 14:48:49 -0400
Subject: locking into hotkey buttons

---
 src/client/util/CurrentUserUtils.ts      |  24 +--
 src/client/views/DocumentButtonBar.scss  |   5 +
 src/client/views/DocumentButtonBar.tsx   |  43 ++---
 src/client/views/DocumentDecorations.tsx |   9 +-
 src/client/views/FilterPanel.scss        |  64 +++++++
 src/client/views/FilterPanel.tsx         | 285 ++++++++++++++++++++++++++-----
 src/client/views/MainView.tsx            |   3 +-
 src/client/views/nodes/IconTagBox.tsx    |  38 +++--
 8 files changed, 380 insertions(+), 91 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 67f8dafc4..0d8955ced 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -44,6 +44,7 @@ export interface Button {
     title?: string;
     toolTip?: string;
     icon?: string;
+    isSystem?: boolean;
     btnType?: ButtonType;
     numBtnMin?: number;
     numBtnMax?: number;
@@ -713,14 +714,14 @@ pie title Minerals in my tap water
     //     ]
     // }
     static tagGroupTools(): Button[] {
-        return [
-            { title: "Star", icon: StrCast(Doc.UserDoc().myFilterHotKeyIcons) ?? "Star", toolTip:"Click to toggle the star group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
-            { title: "Heart", icon: "heart", toolTip:"Click to toggle the heart group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
-            { title: "Bolt", icon: "bolt", toolTip:"Click to toggle the bolt group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
-            { title: "Cloud", icon: "cloud", toolTip:"Click to toggle the cloud group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
-            { title: "Options", icon: "gear", toolTip:"Click to customize your filter panel",  btnType: ButtonType.ClickButton,  expertMode: false, toolType:"opts", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
-
-
+        // hack: if there's no dashboard, create default filters.  otherwise, just make sure that the Options button is preserved
+        return (Doc.UserDoc().activeDashboard ? [] : [
+            { title: "Star", isSystem: false, icon: "star", toolTip:"Click to toggle the star group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "Heart", isSystem: false,icon: "heart", toolTip:"Click to toggle the heart group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "Bolt", isSystem: false,icon: "bolt", toolTip:"Click to toggle the bolt group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "Cloud", isSystem: false,icon: "cloud", toolTip:"Click to toggle the cloud group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            
+            
             // { title: "Group 1", icon, toolTip:"Click to toggle group 1's visibility",  btnType: ButtonType.ToggleButton, width: 40,  expertMode: false, toolType:"1", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             // { title: "Group 2", icon, toolTip:"Click to toggle group 2's visibility",  btnType: ButtonType.ToggleButton, width: 40, expertMode: false, toolType:"2", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             // { title: "Group 3", icon, toolTip:"Click to toggle group 3's visibility",  btnType: ButtonType.ToggleButton, width: 40, expertMode: false, toolType:"3", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
@@ -728,7 +729,9 @@ pie title Minerals in my tap water
             // { title: "", icon, toolTip:"Click to toggle group 5's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"5", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             // { title: "", icon, toolTip:"Click to toggle group 6's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"6", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             // { title: "", icon, toolTip:"Click to toggle group 7's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"7", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-        ]
+        ]).concat([
+            { title: "Options", isSystem: true,icon: "gear", toolTip:"Click to customize your filter panel",  btnType: ButtonType.ClickButton,  expertMode: false, toolType:"opts", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}
+        ])
     }
     static viewTools(): Button[] {
         return [
@@ -871,8 +874,9 @@ pie title Minerals in my tap water
     /// initializes a context menu button for the top bar context menu
     static setupContextMenuButton(params:Button, btnDoc?:Doc, btnContainer?:Doc) {
         const reqdOpts:DocumentOptions = {
+            isSystem: true,
             ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit,
-            color: Colors.WHITE, isSystem: true,
+            color: Colors.WHITE, 
             _nativeWidth: params.width ?? 30, _width: params.width ?? 30,
             _height: 30, _nativeHeight: 30, linearBtnWidth: params.linearBtnWidth,
             toolType: params.toolType, expertMode: params.expertMode,
diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss
index 374d6ecd3..ede277aae 100644
--- a/src/client/views/DocumentButtonBar.scss
+++ b/src/client/views/DocumentButtonBar.scss
@@ -29,6 +29,11 @@ $linkGap: 3px;
     background: black;
     height: 20px;
     align-items: center;
+
+    .tags {
+        width: 40px;
+
+    }
 }
 .documentButtonBar-followTypes {
     width: 20px;
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index c8df66535..c61ebfb4c 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -288,7 +288,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
         const targetDoc = this.view0?.Document;
 
         const metaBtn = (name: string, icon: IconProp) => {
-            const tooltip = `Show ${name}`;
+            const tooltip = `Toggle ${name}`;
             return (
                 <Tooltip title={<div className="dash-tooltip">{tooltip}</div>}>
                     <div className="documentButtonBar-pinIcon">
@@ -298,18 +298,16 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                             key={icon.toString()}
                             size="sm"
                             icon={icon}
-                            // onPointerEnter={action(() => {
-                            //     this.subPin =
-                            //         (pinLayoutView ? 'Layout' : '') +
-                            //         (pinLayoutView && pinContentView ? ' &' : '') +
-                            //         (pinContentView ? ' Content View' : '') +
-                            //         (pinLayoutView && pinContentView ? '(shift+alt)' : pinLayoutView ? '(shift)' : pinContentView ? '(alt)' : '');
-                            // })}
-                            // onPointerLeave={action(() => {
-                            //     this.subPin = '';
-                            // })}
                             onClick={e => {
-                                name === 'tags' ? targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags) : 'hi'
+                                // console.log('wtfff')
+                                // name === 'tags' ?? 
+                                if (name === 'tags'){
+                                    (targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags))
+                                } else {
+                                    (targetDoc && (targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels))
+                                }
+
+
 
                                
                             }}
@@ -323,24 +321,27 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
 
 
         return !targetDoc ? null : (
+            <div className='documentButtonBar-icon'>
+            <div className="documentButtonBar-pinTypes" style = {{width: '40px'}}>
+                {metaBtn('tags', 'star')}
+                {metaBtn("keywords", 'id-card')}
+             </div>
+
             <Tooltip title={<div className="dash-keyword-button">Open keyword menu</div>}>
                 <div
                     className="documentButtonBar-icon"
                     style={{ color: 'white' }}
                     onClick={() => {
-                        targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels;
-                    }}>
+                        // targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags;
+                    }}
+                    >
 
-                    <div className="documentButtonBar-pinTypes">
-                        {
-                            metaBtn('tags', "tag")
-                        /* {pinBtn(true, false, 'window-maximize')}
-                        {pinBtn(false, true, 'address-card')}
-                        {pinBtn(true, true, 'id-card')} */}
-                    </div>
+                   
                     <FontAwesomeIcon className="documentdecorations-icon" icon="tag" />
                 </div>
             </Tooltip>
+            </div>
+
         );
     }
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index b5d819b97..f3b4013f7 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -703,6 +703,7 @@ 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 ? (
@@ -765,6 +766,12 @@ 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));
+      
+
+        const keyWordTrans = doc[DocData].showLabels ? NumCast(doc[DocData].keywordHeight) : 0
+        const tagTrans = doc[DocData].showIconTags ? NumCast(doc[DocData].tagHeight) : 0
+
+
         return (
             <div className="documentDecorations" style={{ display: this._showNothing && !freeformDoc ? 'none' : undefined }}>
                 <div
@@ -832,7 +839,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                 <div
                                     className="link-button-container"
                                     style={{
-                                        top: `${(doc[DocData].showLabels || doc[DocData].showIconTags) ? 4 + (doc._keywordHeight as number) : 4}px`,
+                                        top: `${ keyWordTrans + tagTrans + 4}px`,
                                         transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
                                     }}>
                                     <DocumentButtonBar views={() => DocumentView.Selected()} />
diff --git a/src/client/views/FilterPanel.scss b/src/client/views/FilterPanel.scss
index d6d2956aa..1877cc78b 100644
--- a/src/client/views/FilterPanel.scss
+++ b/src/client/views/FilterPanel.scss
@@ -1,3 +1,4 @@
+
 .filterBox-flyout {
     display: block;
     text-align: left;
@@ -228,6 +229,69 @@
     vertical-align: middle;
 }
 
+.filterHotKey-button {
+    pointer-events: auto;
+    // padding-right: 8px; //5px;
+    width: 100%; //width: 25px;
+    border-radius: 5px;
+    // margin-right: 20px;
+    margin-bottom: 8px;
+    border-color: #d3d3d3;
+    border-style: solid;
+    border-width: thin;
+    transition: all 0.3s ease-out;
+
+    &:hover{
+        border-color: #e9e9e9;
+        background-color: #878484
+    }
+
+
+
+    &.active {
+        background-color: #878484;
+
+
+
+        .icon-panel{
+            display: flex
+        }
+    }
+}
+
+.hotKeyButtons {
+    position: relative;
+    width: 100%;
+    //     margin-top: 3px;
+    //    // grid-column: 1/4;
+    //     width: 100%;
+    //     height: auto;
+    //     display: flex;
+    //     // flex-direction: row;
+    //     // flex-wrap: wrap;
+    //     padding-bottom: 5.5px;
+}
+
+.hotKey-icon-button {
+    // pointer-events: auto; /* Re-enable pointer events for the buttons */
+
+    //     width: 30px;
+    //     height: 30px;
+        // border-color: $medium-blue;
+        background-color: transparent;
+      &.active{
+
+
+      }
+
+}
+
+.icon-panel {
+    bottom: -14px;
+
+
+
+}
 // .sliderBox-outerDiv {
 //     width: 30%;// width: calc(100% - 14px);   // 14px accounts for handles that are at the max value of the slider that would extend outside the box
 //     height: 40; // height: 100%;
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index 332a89a96..e5f9fdb51 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -28,6 +28,19 @@ import { CurrentUserUtils } from '../util/CurrentUserUtils';
 import { DocumentOptions } from '../documents/Documents';
 import { DocUtils } from '../documents/DocUtils';
 import { dropActionType } from '../util/DropActionTypes';
+import { Toggle } from 'browndash-components';
+import { SettingsManager } from '../util/SettingsManager';
+import { StrCast } from '../../fields/Types';
+import { ToggleType } from 'browndash-components';
+import { MultiToggle } from 'browndash-components';
+import { Type } from 'browndash-components';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { DocData } from '../../fields/DocSymbols';
+import { DocumentType } from '../documents/DocumentTypes';
+import { Tooltip } from '@mui/material';
+import { useLocalObservable } from 'mobx-react';
+import { useRef } from 'react';
+import { useEffect } from 'react';
 
 
 interface filterProps {
@@ -228,11 +241,14 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
     };
 
     addHotkey() {
-
         const buttons = DocCast(Doc.UserDoc().myContextMenuBtns);
-        const filter = buttons['Filter']
-        const filter2 = DocCast(filter)
-        const but2 = Doc.UserDoc().myContextMenuBtns
+        const filter = DocCast(buttons.Filter);
+        const filter2 = DocCast(filter);
+        const but2 = Doc.UserDoc().myContextMenuBtns;
+
+        console.log(DocCast(filter).data);
+
+        //[idToDoc("0bec0963-95ac-4978-8606-8af4c7b9badd"),idToDoc("f740e910-bf83-4ee3-b13a-1e63c49dd254"),idToDoc("61db82b0-025a-4cb9-937b-97b1290e8f1a"),idToDoc("1046859f-34ac-4421-bc07-ee2f2cf812e2"),idToDoc("badaff47-3c3a-4b78-a7d6-f2cbc8e45ba0")]
 
         // const newCol = Docs.Create.CarouselDocument(DocListCast(doc[Doc.LayoutFieldKey(doc)]), {
         //     _width: 250,
@@ -251,59 +267,135 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
 
         // DocumentView.getDocumentView(DocCast(buttons['Card']))?.ComponentView?.addDocument?.(hm)
 
-        const hi = CurrentUserUtils.contextMenuTools()
-
-        const newKey: Button = { title: "Cloud", icon: "cloud", toolTip:"Click to toggle the cloud group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}
-
-
-        const heyy = [...hi, newKey]
-
-        Doc.UserDoc().myFilterHotKeyIcons = "palette"
-        Doc.UserDoc().myFilterHotKeyTitles = "hi"
-
-        CurrentUserUtils.setupContextMenuButtons(Doc.UserDoc());
-
-
-
-        Doc.UserDoc().hi = Docs.Create.FontIconDocument(
-           { title: '',
-                icon: 'map-pin',
-            
-                backgroundColor: '#ACCEF7',
-                layout_hideAllLinks: true,
-                _width: 15,
-                _height: 15,
-                _xPadding: 0,
-   }
-        )
-
-
-        // buttons['Filter']
+        //         const hi = CurrentUserUtils.contextMenuTools()
+
+        const newKey: Button = {
+            title: 'Bob',
+            icon: 'cloud',
+            toolTip: "Click to toggle the cloud group's visibility",
+            btnType: ButtonType.ToggleButton,
+            expertMode: false,
+            toolType: 'cloud',
+            funcs: {},
+            scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}' },
+        };
+
+        //         const heyy = [...hi, newKey]
+
+        Doc.UserDoc().myFilterHotKeyTitles = new List<string>(['hi hi', 'supppp'])
+
+        Doc.UserDoc()['hi hi'] = 'heart'
+        Doc.UserDoc()['supppp'] = 'star'
+
+        //         CurrentUserUtils.setupContextMenuButtons(Doc.UserDoc());
+
+        //         Doc.UserDoc().hi = Docs.Create.FontIconDocument(
+        //            { title: '',
+        //                 icon: 'map-pin',
+
+        //                 backgroundColor: '#ACCEF7',
+        //                 layout_hideAllLinks: true,
+        //                 _width: 15,
+        //                 _height: 15,
+        //                 _xPadding: 0,
+        //    }
+        //         )
+
+        //         // buttons['Filter']
+
+        // const reqdCtxtOpts: DocumentOptions = {
+        //     title: 'Filter',
+        //     undoIgnoreFields: new List<string>(['width', 'linearView_IsOpen']),
+        //     flexGap: 0,
+        //     childDragAction: dropActionType.embed,
+        //     childDontRegisterViews: true,
+        //     linearView_IsOpen: true,
+        //     ignoreClick: true,
+        //     linearView_Expandable: false,
+        //     _height: 35,
+        // };
+        const ctxtMenuBtn = CurrentUserUtils.setupContextMenuBtn(newKey, filter);
+        ctxtMenuBtn.isSystem = ctxtMenuBtn[DocData].isSystem = undefined;
+        Doc.AddDocToList(filter, 'data', ctxtMenuBtn);
+        // // // DocUtils.AssignOpts(buttons, reqdCtxtOpts, ctxtMenuBtns);
+
+        //  DocUtils.AssignOpts(DocCast(buttons['Filter']), reqdCtxtOpts, [ctxtMenuBtns]);
 
+        // newCol && dv.ComponentView?.addDocument?.(newCol);
 
+        // console.log(but2 + "omgg")
 
+        // doc.cardSort_activeIcons = new List<string>(list.includes(icon) ? list.filter(d => d !== icon) : [...list, icon]);
 
-        const reqdCtxtOpts:DocumentOptions = { title: "hi", undoIgnoreFields:new List<string>(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: dropActionType.embed, childDontRegisterViews: true,  linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: false, _height: 35 };
-        const ctxtMenuBtns =  CurrentUserUtils.setupContextMenuBtn(newKey, buttons);
-        // DocUtils.AssignOpts(buttons, reqdCtxtOpts, ctxtMenuBtns);
+        // Doc.UserDoc().myContextMenuBtns = new List<Doc>([...buttons, newKey as Doc])
+    }
 
+    hotKeyButtons() {
+        const selected = DocumentView.SelectedDocs().lastElement();
 
-        DocUtils.AssignOpts(DocCast(buttons['Filter']), reqdCtxtOpts, [ctxtMenuBtns]);
+        function isAttrFiltered(attr: string) {
+            if (selected && selected._childFilters != undefined && selected.type === DocumentType.COL) {
+                return StrListCast(selected._childFilters).some(filter => filter.includes(attr));
+            } else {
+                return false;
+            }
+            // return selected._childFilters ? StrListCast(selected._childFilters).some(filter => filter.includes(attr)) : false;
+        }
 
+        const hotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
 
-        // newCol && dv.ComponentView?.addDocument?.(newCol);
+        console.log(hotKeys + 'hiihihi');
 
-        // console.log(but2 + "omgg")
+        const hi = ['star', 'heart', 'bolt'];
 
+        
 
 
-        // doc.cardSort_activeIcons = new List<string>(list.includes(icon) ? list.filter(d => d !== icon) : [...list, icon]);
+        //selecting a button should make t so that the the icon on the top filter panel becomes said icon
 
+        const buttons = hotKeys.map((hotKey, i) => (
+            // <div key={i} className={`filterHotKey-button ${isAttrFiltered(hotKey) ? 'active' : ''} ` }
+            
+            // onClick={undoable((e: React.MouseEvent) => {
 
+            //     // if (e.target === e.currentTarget){
+            //         isAttrFiltered(hotKey) ? Doc.setDocFilter(selected, hotKey, true, 'remove') : Doc.setDocFilter(selected, hotKey, true, 'match');
+            //     // }
+            // }, hotKey)}
 
-        // Doc.UserDoc().myContextMenuBtns = new List<Doc>([...buttons, newKey as Doc])
+            // // style = {{backgroundColor: `${isAttrFiltered(hotKey) ? '#878484' : SettingsManager.userBackgroundColor}`}}
+            
+            
+            // >
+                {/* <Toggle
+                    toggleStatus={isAttrFiltered(hotKey)}
+                    tooltip={'toggle filter'}
+                    text={hotKey}
+                    color={SettingsManager.userColor}
+                    icon={<FontAwesomeIcon icon={Doc.UserDoc()[hotKey] as any}/>}
+                    iconPlacement="left"
+                    align="flex-start"
+                    fillWidth
+                    toggleType={ToggleType.BUTTON}
+                    onClick={undoable(() => {
+                        isAttrFiltered(hotKey) ? Doc.setDocFilter(selected, hotKey, true, 'remove') : Doc.setDocFilter(selected, hotKey, true, 'match');
+                    }, hotKey)}
+                /> */}
+                <>
+
+                    <Tooltip key={i} title={<div className="dash-tooltip">Click to customize this hotkey's icon</div>}>
+                        <HotKeyIconButton hotKey = {hotKey} />
+
+                    </Tooltip>
+
+                   </> 
+        ));
+
+        return buttons;
     }
 
+    // @observable iconPanelMap: Map<string, number> = new Map();
+
     render() {
         return (
             <div className="filterBox-treeView">
@@ -376,8 +468,9 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
                             <FieldsDropdown Document={this.Document} selectFunc={this.addHotkey} showPlaceholder placeholder="add a hotkey" addedFields={['acl_Guest', LinkedTo, 'Star', 'Heart', 'Bolt', 'Cloud']} />
                         </div>
                     </div>
-
                 </div>
+
+                <div>{this.hotKeyButtons()}</div>
             </div>
         );
     }
@@ -486,3 +579,113 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
         return undefined;
     }
 }
+
+
+
+interface HotKeyButtonProps {
+    hotKey: string;
+    selected?: Doc
+}
+
+const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey, selected}) => {
+    const state = useLocalObservable(() => ({
+        isActive: false,
+        toggleActive() {
+            this.isActive = !this.isActive;
+        },
+        deactivate() {
+            this.isActive = false;
+        }
+    }));
+
+    const panelRef = useRef<HTMLDivElement>(null);
+
+    const handleClick = () => {
+        state.toggleActive();
+
+        console.log(state.isActive + "hmmm")
+    };
+
+    const handleClickOutside = (event: MouseEvent) => {
+        if (panelRef.current && !panelRef.current.contains(event.target as Node)) {
+            state.deactivate();
+        }
+    };
+
+    useEffect(() => {
+        document.addEventListener('mousedown', handleClickOutside);
+        return () => {
+            document.removeEventListener('mousedown', handleClickOutside);
+        };
+    }, []);
+
+    const iconOpts = ['star', 'heart', 'bolt'];
+
+    const iconPanel = iconOpts.map((icon, i) => (
+        <button key={i} onClick={() => Doc.UserDoc()[hotKey] = icon}>
+            <FontAwesomeIcon icon={icon as any} />
+        </button>
+    ));
+
+    const hotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles)
+
+    return (
+        <>
+
+        <div className={`filterHotKey-button ${isAttrFiltered(hotKey) ? 'active' : ''} ` }
+                    
+                    onClick={undoable((e: React.MouseEvent) => {
+
+                        // if (e.target === e.currentTarget){
+                            isAttrFiltered(hotKey) ? Doc.setDocFilter(selected, hotKey, true, 'remove') : Doc.setDocFilter(selected, hotKey, true, 'match');
+                        // }
+                    }, hotKey)}
+
+                    // style = {{backgroundColor: `${isAttrFiltered(hotKey) ? '#878484' : SettingsManager.userBackgroundColor}`}}
+                    
+                    
+                    >
+        <div className={`hotKey-icon-button ${state.isActive ? 'active' : ''}`} ref={panelRef}>
+            <Tooltip title={<div className="dash-tooltip">Click to customize this hotkey's icon</div>}>
+                <button
+                    type="button"
+                    onClick={(e: React.MouseEvent) => {
+                        console.log('hiii')
+                        e.stopPropagation(); 
+                        handleClick();
+
+                    }}
+
+                >
+                    <FontAwesomeIcon icon={Doc.UserDoc()[hotKey] as any} size="2xl"/>
+                </button>
+            </Tooltip>
+            {state.isActive && (
+                <div className="icon-panel" >
+                    {iconPanel}
+                </div>
+            )}
+
+            
+        </div>
+
+        <p>     {hotKey.toUpperCase()}    </p>
+
+
+        <button onClick={(e: React.MouseEvent) => { 
+            e.stopPropagation(); 
+            Doc.UserDoc().myFilterHotKeyTitles = new List<string>(hotKeys.filter(k => k !== hotKey)); 
+        }}>
+            <FontAwesomeIcon icon={'x' as any} />
+        </button>
+
+        </div>
+
+
+        </>
+
+        
+    );
+});
+
+
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f552540b8..9da359e1b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -551,7 +551,8 @@ export class MainView extends ObservableReactComponent<{}> {
                 fa.faStar,
                 fa.faCloud,
                 fa.faBolt,
-                fa.faLightbulb
+                fa.faLightbulb,
+                fa.faX
             ]
         );
     }
diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx
index 9a2273c3a..73360b684 100644
--- a/src/client/views/nodes/IconTagBox.tsx
+++ b/src/client/views/nodes/IconTagBox.tsx
@@ -41,24 +41,24 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
         this.ref = React.createRef();
     }
 
-    componentDidMount(): void {
-        this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
-        this._props.doc._keywordHeight = this.height;
-
-        reaction(
-            () => this.currentScale,
-            () => {
-                if (this.currentScale < 1) {
-                    this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
-                    this._props.doc._keywordHeight = this.height;
-                }
-            }
-        );
-    }
+    // componentDidMount(): void {
+    //     this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
+    //     this._props.doc._keywordHeight = this.height;
+
+    //     reaction(
+    //         () => this.currentScale,
+    //         () => {
+    //             if (this.currentScale < 1) {
+    //                 this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
+    //                 this._props.doc._keywordHeight = this.height;
+    //             }
+    //         }
+    //     );
+    // }
 
     componentDidUpdate(prevProps: Readonly<IconTagProps>): void {
-        this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
-        this._props.doc._keywordHeight = this.height;
+        // this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
+        this._props.doc[DocData].tagHeight = 36*this.currentScale;
     }
 
     /**
@@ -93,12 +93,16 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
             3: 'bolt'
         };
 
+
+
         return (
             <div
             className="card-button-container"
             style={{
                 transformOrigin: 'top left',
-                transform: `scale(${1 / this.currentScale}) translateY(${doc[DocData].showLabels ? doc._keywordHeight as number : 0}px)`,
+                transform: `scale(${1 / this.currentScale})
+                translateY(${doc[DocData].showLabels ? ((NumCast(doc[DocData].keywordHeight)*(1-this.currentScale))) : 0}px)
+                `,
                 width: `${totalWidth}px`,
                 fontSize: '50px'
             }}
-- 
cgit v1.2.3-70-g09d2


From 623da0b4eec34fbd238cc26a5e0c105426a6711e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 12 Sep 2024 09:50:31 -0400
Subject: added tags for multi-selections.

---
 src/client/views/DocumentButtonBar.tsx             | 10 +++-
 src/client/views/DocumentDecorations.scss          |  7 +++
 src/client/views/DocumentDecorations.tsx           | 11 +++-
 src/client/views/StyleProvider.tsx                 |  2 +-
 src/client/views/TagsView.tsx                      | 65 ++++++++++++++--------
 .../collectionFreeForm/FaceCollectionBox.tsx       |  2 +-
 6 files changed, 69 insertions(+), 28 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 437ef045f..785e69f84 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -267,7 +267,15 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
     get keywordButton() {
         return !DocumentView.Selected().length ? null : (
             <Tooltip title={<div className="dash-keyword-button">Open keyword menu</div>}>
-                <div className="documentButtonBar-icon" style={{ color: 'white' }} onClick={() => DocumentView.Selected().map(dv => (dv.dataDoc.showTags = !dv.dataDoc.showTags))}>
+                <div
+                    className="documentButtonBar-icon"
+                    style={{ color: 'white' }}
+                    onClick={() => {
+                        const showing = DocumentView.Selected().some(dv => dv.dataDoc.showTags);
+                        DocumentView.Selected().forEach(dv => {
+                            dv.dataDoc.showTags = !showing;
+                        });
+                    }}>
                     <FontAwesomeIcon className="documentdecorations-icon" icon="tag" />
                 </div>
             </Tooltip>
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 67e1054c3..346df10d5 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -38,6 +38,13 @@ $resizeHandler: 8px;
         background: green;
         border-radius: 50%;
     }
+    .documentDecorations-tagsView {
+        position: absolute;
+        height: 100%;
+        pointer-events: all;
+        border-radius: 50%;
+        color: black;
+    }
 }
 
 .documentDecorations-container {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index da35459bb..118a5bd3d 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -35,6 +35,7 @@ import { DocumentView } from './nodes/DocumentView';
 import { ImageBox } from './nodes/ImageBox';
 import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { TagsView } from './TagsView';
 
 interface DocumentDecorationsProps {
     PanelWidth: number;
@@ -835,12 +836,20 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                 <div
                                     className="link-button-container"
                                     style={{
-                                        top: `${doc[DocData].showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
+                                        top: DocumentView.Selected().length > 1 ? 0 : `${doc[DocData].showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
                                         transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
                                     }}>
                                     <DocumentButtonBar views={() => DocumentView.Selected()} />
                                 </div>
                             )}
+                            <div
+                                className="documentDecorations-tagsView"
+                                style={{
+                                    top: `${doc[DocData].showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
+                                    transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
+                                }}>
+                                {DocumentView.Selected().length > 1 ? <TagsView Views={DocumentView.Selected()} /> : null}
+                            </div>
                         </div>
 
                         {useRotation && (
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 513953d17..262f888fb 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -365,7 +365,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                     </Tooltip>
                 );
             };
-            const tags = () => props?.DocumentView?.() ? <TagsView View={props.DocumentView()}/> :  null;
+            const tags = () => props?.DocumentView?.() ? <TagsView Views={[props.DocumentView()]}/> :  null;
             return (
                 <>
                     {paint()}
diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx
index 8047363d9..ae9d42749 100644
--- a/src/client/views/TagsView.tsx
+++ b/src/client/views/TagsView.tsx
@@ -33,7 +33,7 @@ import { FaceRecognitionHandler } from './search/FaceRecognitionHandler';
  */
 
 interface TagItemProps {
-    doc: Doc;
+    docs: Doc[];
     tag: string;
     tagDoc: Opt<Doc>;
     showRemoveUI: boolean;
@@ -207,8 +207,12 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
         e.preventDefault();
     };
 
+    @computed get doc() {
+        return this._props.docs.lastElement();
+    }
+
     render() {
-        this._props.tagDoc && setTimeout(() => TagItem.addTagToDoc(this._props.doc, this._props.tag)); // bcz: hack to make sure that Docs are added to their tag Doc collection since metadata can get set anywhere without a guard triggering an add to the collection
+        this._props.tagDoc && setTimeout(() => this._props.docs.forEach(doc => TagItem.addTagToDoc(doc, this._props.tag))); // bcz: hack to make sure that Docs are added to their tag Doc collection since metadata can get set anywhere without a guard triggering an add to the collection
         const tag = this._props.tag.replace(/^#/, '');
         const metadata = tag.startsWith('@') ? tag.replace(/^@/, '') : '';
         return (
@@ -216,16 +220,16 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
                 {metadata ? (
                     <span>
                         <b style={{ fontSize: 'smaller' }}>{tag}&nbsp;</b>
-                        {typeof this._props.doc[metadata] === 'boolean' ? (
+                        {typeof this.doc[metadata] === 'boolean' ? (
                             <input
                                 type="checkbox"
                                 onClick={e => e.stopPropagation()}
                                 onPointerDown={e => e.stopPropagation()}
-                                onChange={undoable(e => (this._props.doc[metadata] = !this._props.doc[metadata]), 'metadata toggle')}
-                                checked={this._props.doc[metadata] as boolean}
+                                onChange={undoable(e => (this.doc[metadata] = !this.doc[metadata]), 'metadata toggle')}
+                                checked={this.doc[metadata] as boolean}
                             />
                         ) : (
-                            Field.toString(this._props.doc[metadata])
+                            Field.toString(this.doc[metadata])
                         )}
                     </span>
                 ) : (
@@ -234,7 +238,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
                 {this.props.showRemoveUI && this._props.tagDoc && (
                     <IconButton
                         tooltip="Remove tag"
-                        onPointerDown={undoable(() => TagItem.removeTagFromDoc(this._props.doc, this._props.tag, this._props.tagDoc), `remove tag ${this._props.tag}`)}
+                        onPointerDown={undoable(() => this._props.docs.forEach(doc => TagItem.removeTagFromDoc(doc, this._props.tag, this._props.tagDoc)), `remove tag ${this._props.tag}`)}
                         icon={<FontAwesomeIcon icon="times" size="sm" />}
                         style={{ width: '8px', height: '8px', marginLeft: '10px' }}
                     />
@@ -245,7 +249,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
 }
 
 interface TagViewProps {
-    View: DocumentView;
+    Views: DocumentView[];
 }
 
 /**
@@ -261,12 +265,12 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
 
     @observable _panelHeightDirty = 0;
     @observable _currentInput = '';
-    @observable _isEditing = !StrListCast(this._props.View.dataDoc.tags).length;
+    @observable _isEditing = !StrListCast(this.View.dataDoc.tags).length;
     _heightDisposer: IReactionDisposer | undefined;
 
     componentDidMount() {
         this._heightDisposer = reaction(
-            () => this._props.View.screenToContentsTransform(),
+            () => this.View.screenToContentsTransform(),
             xf => {
                 this._panelHeightDirty = this._panelHeightDirty + 1;
             }
@@ -276,11 +280,15 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
         this._heightDisposer?.();
     }
 
+    @computed get View() {
+        return this._props.Views.lastElement();
+    }
+
     @computed get currentScale() {
-        return Math.max(1, 1 / this._props.View.screenToLocalScale());
+        return this._props.Views.length > 1 ? 1 : Math.max(1, 1 / this.View.screenToLocalScale());
     }
     @computed get isEditing() {
-        return this._isEditing && DocumentView.SelectedDocs().includes(this._props.View.Document);
+        return this._isEditing && (this._props.Views.length > 1 || DocumentView.SelectedDocs().includes(this.View.Document));
     }
 
     /**
@@ -290,7 +298,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
     @action
     setToEditing = (editing = true) => {
         this._isEditing = editing;
-        editing && this._props.View.select(false);
+        editing && this._props.Views.length === 1 && this.View.select(false);
     };
 
     /**
@@ -303,8 +311,10 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
         action((tag: string) => {
             const submittedLabel = tag.trim().replace(/^#/, '').split(':');
             if (submittedLabel[0]) {
-                TagItem.addTagToDoc(this._props.View.Document, '#' + submittedLabel[0]);
-                if (submittedLabel.length > 1) Doc.SetField(this._props.View.Document, submittedLabel[0].replace(/^@/, ''), ':' + submittedLabel[1]);
+                this._props.Views.forEach(view => {
+                    TagItem.addTagToDoc(view.Document, '#' + submittedLabel[0]);
+                    if (submittedLabel.length > 1) Doc.SetField(view.Document, submittedLabel[0].replace(/^@/, ''), ':' + submittedLabel[1]);
+                });
             }
             this._currentInput = ''; // Clear the input box
         }),
@@ -316,20 +326,20 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
      * When the dropdown is clicked, this will toggle an extended UI that allows additional tags to be added/removed.
      */
     render() {
-        const tagsList = new Set<string>(StrListCast(this._props.View.dataDoc.tags));
-        const chatTagsList = new Set<string>(StrListCast(this._props.View.dataDoc.tags_chat));
+        const tagsList = new Set<string>(StrListCast(this.View.dataDoc.tags));
+        const chatTagsList = new Set<string>(StrListCast(this.View.dataDoc.tags_chat));
         const facesList = new Set<string>(
-            DocListCast(this._props.View.dataDoc[Doc.LayoutFieldKey(this._props.View.Document) + '_annotations'])
-                .concat(this._props.View.Document)
+            DocListCast(this.View.dataDoc[Doc.LayoutFieldKey(this.View.Document) + '_annotations'])
+                .concat(this.View.Document)
                 .filter(d => d.face)
                 .map(doc => StrCast(DocCast(doc.face)?.title))
         );
         this._panelHeightDirty;
 
-        return !this._props.View.Document.showTags ? null : (
+        return !this.View.Document.showTags && this._props.Views.length === 1 ? null : (
             <div
                 className="tagsView-container"
-                ref={r => r && new ResizeObserver(action(() => (this._props.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)}
+                ref={r => r && new ResizeObserver(action(() => this._props.Views.length === 1 && (this.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)}
                 style={{
                     transformOrigin: 'top left',
                     maxWidth: `${100 * this.currentScale}%`,
@@ -338,7 +348,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
                     backgroundColor: this.isEditing ? Colors.LIGHT_GRAY : Colors.TRANSPARENT,
                     borderColor: this.isEditing ? Colors.BLACK : Colors.TRANSPARENT,
                     position: 'relative',
-                    top: `calc(-${this.InsetDist} * ${1 / this.currentScale}px)`,
+                    top: this._props.Views.length > 1 ? 25 : `calc(-${this.InsetDist} * ${1 / this.currentScale}px)`,
                 }}>
                 <div className="tagsView-content" style={{ width: '100%' }}>
                     <div className="tagsView-list">
@@ -346,10 +356,17 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
                             <IconButton style={{ width: '8px' }} tooltip="Close Menu" onPointerDown={() => this.setToEditing(!this._isEditing)} icon={<FontAwesomeIcon icon={this._isEditing ? 'chevron-up' : 'chevron-down'} size="sm" />} />
                         )}
                         {Array.from(tagsList).map((tag, i) => (
-                            <TagItem key={i} doc={this._props.View.Document} tag={tag} tagDoc={TagItem.findTagCollectionDoc(tag) ?? TagItem.createTagCollectionDoc(tag)} setToEditing={this.setToEditing} showRemoveUI={this.isEditing} />
+                            <TagItem
+                                key={i}
+                                docs={this._props.Views.map(view => view.Document)}
+                                tag={tag}
+                                tagDoc={TagItem.findTagCollectionDoc(tag) ?? TagItem.createTagCollectionDoc(tag)}
+                                setToEditing={this.setToEditing}
+                                showRemoveUI={this.isEditing}
+                            />
                         ))}
                         {Array.from(facesList).map((tag, i) => (
-                            <TagItem key={i} doc={this._props.View.Document} tag={tag} tagDoc={undefined} setToEditing={this.setToEditing} showRemoveUI={this.isEditing} />
+                            <TagItem key={i} docs={this._props.Views.map(view => view.Document)} tag={tag} tagDoc={undefined} setToEditing={this.setToEditing} showRemoveUI={this.isEditing} />
                         ))}
                     </div>
                     {this.isEditing ? (
diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx
index f9f6c81ab..534f67927 100644
--- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx
+++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx
@@ -179,7 +179,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
                         })}>
                         {FaceRecognitionHandler.UniqueFaceImages(this.Document).map((doc, i) => {
-                            const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)]).url.href.split('.');
+                            const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)])?.url.href.split('.') ?? ['-missing-', '.png'];
                             return (
                                 <div
                                     className="image-wrapper"
-- 
cgit v1.2.3-70-g09d2


From bb1f4c62071987cc59461a8ce53522fa7a9036cc Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 12 Sep 2024 14:42:10 -0400
Subject: changed closing of face rectangles to hide them.  added Shift+click
 of tags button to show face rectangles.

---
 src/client/documents/Documents.ts                          |  1 +
 src/client/views/DocumentButtonBar.tsx                     | 14 +++++++++-----
 src/client/views/DocumentDecorations.tsx                   |  7 ++++---
 src/client/views/TagsView.tsx                              |  6 +++---
 .../views/collections/collectionFreeForm/ImageLabelBox.tsx |  4 ++--
 src/client/views/nodes/formattedText/RichTextRules.ts      |  2 +-
 src/client/views/search/FaceRecognitionHandler.tsx         |  3 +++
 7 files changed, 23 insertions(+), 14 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index fbdf361dd..89356072a 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -273,6 +273,7 @@ export class DocumentOptions {
     _layout_showTitle?: string; // field name to display in header (:hover is an optional suffix)
     _layout_showSidebar?: BOOLt = new BoolInfo('whether an annotationsidebar should be displayed for text docuemnts');
     _layout_showCaption?: string; // which field to display in the caption area.  leave empty to have no caption
+    _layout_showTags?: BOOLt = new BoolInfo('whether to show the list of document tags at the bottom of a DocView');
 
     _chromeHidden?: BOOLt = new BoolInfo('whether the editing chrome for a document is hidden');
     hideClickBehaviors?: BOOLt = new BoolInfo('whether to hide click behaviors in context menu');
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 785e69f84..f14fd033b 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -9,7 +9,7 @@ import * as React from 'react';
 import { FaEdit } from 'react-icons/fa';
 import { returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../ClientUtils';
 import { emptyFunction } from '../../Utils';
-import { Doc } from '../../fields/Doc';
+import { Doc, DocListCast } from '../../fields/Doc';
 import { Cast, DocCast } from '../../fields/Types';
 import { DocUtils, IsFollowLinkScript } from '../documents/DocUtils';
 import { CalendarManager } from '../util/CalendarManager';
@@ -270,12 +270,16 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                 <div
                     className="documentButtonBar-icon"
                     style={{ color: 'white' }}
-                    onClick={() => {
-                        const showing = DocumentView.Selected().some(dv => dv.dataDoc.showTags);
+                    onClick={undoable(e => {
+                        const showing = DocumentView.Selected().some(dv => dv.layoutDoc._layout_showTags);
                         DocumentView.Selected().forEach(dv => {
-                            dv.dataDoc.showTags = !showing;
+                            dv.layoutDoc._layout_showTags = !showing;
+                            if (e.shiftKey)
+                                DocListCast(dv.Document[Doc.LayoutFieldKey(dv.Document) + '_annotations']).forEach(doc => {
+                                    if (doc.face) doc.hidden = showing;
+                                });
                         });
-                    }}>
+                    }, 'show Doc tags')}>
                     <FontAwesomeIcon className="documentdecorations-icon" icon="tag" />
                 </div>
             </Tooltip>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 118a5bd3d..907bb2614 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -232,7 +232,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                         if (iconViewDoc.activeFrame) {
                             iconViewDoc.opacity = 0; // bcz: hacky ... allows inkMasks and other documents to be "turned off" without removing them from the animated collection which allows them to function properly in a presenation.
                         } else {
-                            iconView._props.removeDocument?.(iconView.Document);
+                            if (iconView.Document.annotationOn && iconView.Document.face) iconView.Document.hidden = true;
+                            else iconView._props.removeDocument?.(iconView.Document);
                         }
                     });
                     views.forEach(DocumentView.DeselectView);
@@ -836,7 +837,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                 <div
                                     className="link-button-container"
                                     style={{
-                                        top: DocumentView.Selected().length > 1 ? 0 : `${doc[DocData].showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
+                                        top: DocumentView.Selected().length > 1 ? 0 : `${doc._layout_showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
                                         transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
                                     }}>
                                     <DocumentButtonBar views={() => DocumentView.Selected()} />
@@ -845,7 +846,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                             <div
                                 className="documentDecorations-tagsView"
                                 style={{
-                                    top: `${doc[DocData].showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
+                                    top: `${doc._layout_showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
                                     transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
                                 }}>
                                 {DocumentView.Selected().length > 1 ? <TagsView Views={DocumentView.Selected()} /> : null}
diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx
index ae9d42749..0b303f6f5 100644
--- a/src/client/views/TagsView.tsx
+++ b/src/client/views/TagsView.tsx
@@ -172,12 +172,12 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
             docData.data = new List<Doc>(newEmbeddings);
             docData.title = this._props.tag;
             docData.tags = new List<string>([this._props.tag]);
-            docData.showTags = true;
             docData.freeform_fitContentsToBox = true;
             doc._freeform_panX = doc._freeform_panY = 0;
             doc._width = 900;
             doc._height = 900;
             doc.layout_fitWidth = true;
+            doc._layout_showTags = true;
             return doc;
         })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
         newEmbeddings.forEach(embed => Doc.SetContainer(embed, newCollection));
@@ -322,7 +322,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
     );
 
     /**
-     * When 'showTags' is set on a Doc, this displays a wrapping panel of tagItemViews corresponding to all the tags set on the Doc).
+     * When 'layout_showTags' is set on a Doc, this displays a wrapping panel of tagItemViews corresponding to all the tags set on the Doc).
      * When the dropdown is clicked, this will toggle an extended UI that allows additional tags to be added/removed.
      */
     render() {
@@ -336,7 +336,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
         );
         this._panelHeightDirty;
 
-        return !this.View.Document.showTags && this._props.Views.length === 1 ? null : (
+        return !this.View.Document._layout_showTags && this._props.Views.length === 1 ? null : (
             <div
                 className="tagsView-container"
                 ref={r => r && new ResizeObserver(action(() => this._props.Views.length === 1 && (this.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)}
diff --git a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
index e419e522c..033d1590d 100644
--- a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
+++ b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
@@ -139,9 +139,9 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
     toggleDisplayInformation = () => {
         this._displayImageInformation = !this._displayImageInformation;
         if (this._displayImageInformation) {
-            this._selectedImages.forEach(doc => (doc[DocData].showTags = true));
+            this._selectedImages.forEach(doc => (doc._layout_showTags = true));
         } else {
-            this._selectedImages.forEach(doc => (doc[DocData].showTags = false));
+            this._selectedImages.forEach(doc => (doc._layout_showTags = false));
         }
     };
 
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index e0d6c7c05..0ef67b4be 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -404,7 +404,7 @@ export class RichTextRules {
                 if (!tags.includes(tag)) {
                     tags.push(tag);
                     this.Document[DocData].tags = new List<string>(tags);
-                    this.Document[DocData].showTags = true;
+                    this.Document._layout_showTags = true;
                 }
                 const fieldView = state.schema.nodes.dashField.create({ fieldKey: '#' + tag });
                 return state.tr
diff --git a/src/client/views/search/FaceRecognitionHandler.tsx b/src/client/views/search/FaceRecognitionHandler.tsx
index 636a44984..c507e54b6 100644
--- a/src/client/views/search/FaceRecognitionHandler.tsx
+++ b/src/client/views/search/FaceRecognitionHandler.tsx
@@ -220,6 +220,7 @@ export class FaceRecognitionHandler {
                             .then(imgDocFaceDescriptions => {  // For each face detected, find a match.
                                 const annos = [] as Doc[];
                                 const scale = NumCast(imgDoc.data_nativeWidth) / img.width;
+                                const showTags= imgDocFaceDescriptions.length > 1;
                                 imgDocFaceDescriptions.forEach((fd, i) => {
                                     const faceDescriptor = new List<number>(Array.from(fd.descriptor));
                                     const matchedUniqueFace = this.findMatchingFaceDoc(fd.descriptor) ?? this.createUniqueFaceDoc(activeDashboard);
@@ -233,12 +234,14 @@ export class FaceRecognitionHandler {
                                         y: fd.alignedRect.box.top * scale,
                                         _width: fd.alignedRect.box.width * scale,
                                         _height: fd.alignedRect.box.height * scale,
+                                        _layout_showTags: showTags
                                     })
                                     FaceRecognitionHandler.UniqueFaceAddFaceImage(faceAnno, matchedUniqueFace); // add image/faceDescriptor to matched unique face
                                     annos.push(faceAnno);
                                 });
 
                                 imgDoc[DocData].data_annotations = new List<Doc>(annos);
+                                imgDoc._layout_showTags = annos.length > 0;
                                 return imgDocFaceDescriptions;
                             })
                 ); // prettier-ignore
-- 
cgit v1.2.3-70-g09d2


From cded2f6473018f15711142517ba30382682cbec0 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 16 Sep 2024 15:48:08 -0400
Subject: fixed title dragging pick correlation and conversion to tab dragging.

---
 src/client/util/DragManager.ts           | 2 +-
 src/client/views/DocumentDecorations.tsx | 5 +++--
 2 files changed, 4 insertions(+), 3 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 7db13689d..d55d193cc 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -569,7 +569,7 @@ export namespace DragManager {
                             AbortDrag();
                             await finishDrag?.(new DragCompleteEvent(true, docDragData));
                             DragManager.StartWindowDrag?.(e, docDragData.droppedDocuments, aborted => {
-                                if (!aborted && (docDragData?.dropAction === 'move' || docDragData?.dropAction === 'same')) {
+                                if (!aborted && (docDragData?.dropAction === dropActionType.move || docDragData?.dropAction === dropActionType.same)) {
                                     docDragData.removeDocument?.(docDragData?.draggedDocuments[0]);
                                 }
                             });
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 907bb2614..4b0198021 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -173,7 +173,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                     !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('$') ? (selected && Field.toKeyValueString(selected, this._titleControlString.substring(1))) || '-unset-' : this._titleControlString);
                     this._editingTitle = true;
                     this._keyinput.current && setTimeout(this._keyinput.current.focus);
-                })
+                }),
+                false // can't preventDefault since that will mess up goldenlayout if you drag over the tab bar.  so just stop propagation below.
             );
             e.stopPropagation();
         }
@@ -195,7 +196,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         if (containers.size > 1) return false;
         const { left, top } = dragDocView.getBounds || { left: 0, top: 0 };
         const dragData = new DragManager.DocumentDragData(DocumentView.SelectedDocs(), dragDocView._props.dropAction);
-        dragData.offset = dragDocView.screenToContentsTransform().transformDirection(e.x - left, e.y - top);
+        dragData.offset = dragDocView.screenToViewTransform().transformDirection(e.x - left, e.y - top);
         dragData.moveDocument = dragDocView._props.moveDocument;
         dragData.removeDocument = dragDocView._props.removeDocument;
         dragData.isDocDecorationMove = true;
-- 
cgit v1.2.3-70-g09d2


From 62eb66ca7d3404f9977acdf73f815f4920fb964d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 16 Sep 2024 18:14:56 -0400
Subject: fixed doc decorations from crashing when dockingView is selected.
 cleaned up tab doc view.

---
 src/client/views/DocumentDecorations.tsx           |   5 +-
 src/client/views/TagsView.tsx                      |   2 +-
 .../views/collections/CollectionDockingView.tsx    |   3 +-
 src/client/views/collections/TabDocView.tsx        | 175 +++++++++------------
 4 files changed, 77 insertions(+), 108 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 4b0198021..5e7908725 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -646,7 +646,6 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     render() {
         const { b, r, x, y } = this.Bounds;
         const seldocview = DocumentView.Selected().lastElement();
-        const doc = DocumentView.SelectedDocs().lastElement();
         if (SnappingManager.IsDragging || r - x < 1 || x === Number.MAX_VALUE || !seldocview || this._hidden || isNaN(r) || isNaN(b) || isNaN(x) || isNaN(y)) {
             setTimeout(
                 action(() => {
@@ -838,7 +837,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                 <div
                                     className="link-button-container"
                                     style={{
-                                        top: DocumentView.Selected().length > 1 ? 0 : `${doc._layout_showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
+                                        top: DocumentView.Selected().length > 1 ? 0 : `${seldocview.Document._layout_showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
                                         transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
                                     }}>
                                     <DocumentButtonBar views={() => DocumentView.Selected()} />
@@ -847,7 +846,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                             <div
                                 className="documentDecorations-tagsView"
                                 style={{
-                                    top: `${doc._layout_showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
+                                    top: `${seldocview.Document._layout_showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
                                     transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
                                 }}>
                                 {DocumentView.Selected().length > 1 ? <TagsView Views={DocumentView.Selected()} /> : null}
diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx
index 0b303f6f5..be2c28185 100644
--- a/src/client/views/TagsView.tsx
+++ b/src/client/views/TagsView.tsx
@@ -336,7 +336,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
         );
         this._panelHeightDirty;
 
-        return !this.View.Document._layout_showTags && this._props.Views.length === 1 ? null : (
+        return this.View.ComponentView?.isUnstyledView?.() || (!this.View.Document._layout_showTags && this._props.Views.length === 1) ? null : (
             <div
                 className="tagsView-container"
                 ref={r => r && new ResizeObserver(action(() => this._props.Views.length === 1 && (this.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index e0aa79c7b..028133a6e 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -31,6 +31,7 @@ import { ScriptingRepl } from '../ScriptingRepl';
 import { UndoStack } from '../UndoStack';
 import './CollectionDockingView.scss';
 import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
+import { TabHTMLElement } from './TabDocView';
 
 @observer
 export class CollectionDockingView extends CollectionSubView() {
@@ -544,7 +545,7 @@ export class CollectionDockingView extends CollectionSubView() {
     tabCreated = (tab: { contentItem: { element: HTMLElement[] } }) => {
         this.tabMap.add(tab);
         // InitTab is added to the tab's HTMLElement in TabDocView
-        const tabdocviewContent = tab.contentItem.element[0]?.firstChild?.firstChild as unknown as { InitTab?: (tab: object) => void };
+        const tabdocviewContent = tab.contentItem.element[0]?.firstChild?.firstChild as unknown as TabHTMLElement;
         tabdocviewContent?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content)
     };
 
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 31b6be927..def1ea731 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -57,6 +57,7 @@ interface TabMiniThumbProps {
     miniLeft: () => number;
 }
 
+export type TabHTMLElement = HTMLDivElement & { InitTab?: (tab: object) => void };
 @observer
 class TabMiniThumb extends React.Component<TabMiniThumbProps> {
     render() {
@@ -193,8 +194,9 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
             .filter(tv => tv._document)
             .map(tv => tv._document!);
     }
-    _mainCont: HTMLDivElement | null = null;
+    _mainCont: TabHTMLElement | null = null;
     _tabReaction: IReactionDisposer | undefined;
+    _lastSelection = 0; // time when view was last selected - used to re-select views that get invalidated when selected
 
     /**
      * Adds a document to the presentation view
@@ -273,19 +275,24 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
         setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs
     }
 
+    // Flag indicating that when a tab is activated, it should not select it's document.
+    // this is used by the link properties menu when it wants to display the link target without selecting the target (which would make the link property window go away since it would no longer be selected)
+    public static DontSelectOnActivate = 'dontSelectOnActivate';
+
+    public static IsSelected = (doc?: Doc) => {
+        return DocumentView.getViews(doc).some(dv => dv?.IsSelected);
+    };
+
     static Activate = (tabDoc: Doc) => {
         const tab = Array.from(CollectionDockingView.Instance?.tabMap ?? []).find(findTab => findTab.DashDoc === tabDoc && !findTab.contentItem.config.props.keyValue);
         tab?.header.parent.setActiveContentItem(tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
         return tab !== undefined;
     };
-    // static ActivateTabView(doc: Doc) {
-    //     const tabView = Array.from(TabDocView._allTabs).find(view => view._document === doc);
-    //     if (!tabView?._activated && tabView?._document) {
-    //         TabDocView.Activate(tabView?._document);
-    //         return tabView;
-    //     }
-    //     return undefined;
-    // }
+
+    get stack()           { return this._props.glContainer.parent.parent; } // prettier-ignore
+    get tab()             { return this._props.glContainer.tab;  } // prettier-ignore
+    get view()            { return this._view;  } // prettier-ignore
+
     constructor(props: TabDocViewProps) {
         super(props);
         makeObservable(this);
@@ -299,37 +306,13 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     @observable _hovering = false;
     @observable _isActive: boolean = false;
     @observable _isAnyChildContentActive = false;
-    public static IsSelected = (doc?: Doc) => {
-        if (DocumentView.getViews(doc).some(dv => dv?.IsSelected)) {
-            return true;
-        }
-        return false;
-    };
-    @computed get _isUserActivated() {
-        return TabDocView.IsSelected(this._document) || this._isAnyChildContentActive;
-    }
-    get _isContentActive() {
-        return this._isUserActivated || this._hovering;
-    }
     @observable _document: Doc | undefined = undefined;
     @observable _view: DocumentView | undefined = undefined;
+    @observable _forceInvalidateScreenToLocal = 0; // screentolocal is computed outside of react using a dom resize ovbserver.  this hack allows the resize observer to trigger a react update
 
-    @computed get layoutDoc() {
-        return this._document && Doc.Layout(this._document);
-    }
-
-    get stack() {
-        return this._props.glContainer.parent.parent;
-    }
-    get tab() {
-        return this._props.glContainer.tab;
-    }
-    get view() {
-        return this._view;
-    }
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    _lastTab: any;
-    _lastView: DocumentView | undefined;
+    @computed get layoutDoc() { return this._document && Doc.Layout(this._document); } // prettier-ignore
+    @computed get isUserActivated() {  return TabDocView.IsSelected(this._document) || this._isAnyChildContentActive; } // prettier-ignore
+    @computed get isContentActive() { return this.isUserActivated || this._hovering; } // prettier-ignore
 
     @action
     // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -413,7 +396,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                             color === variant
                                 ? DashColor(color)
                                       .fade(
-                                          this._isUserActivated
+                                          this.isUserActivated
                                               ? 0
                                               : this._hovering
                                                 ? 0.25
@@ -522,19 +505,14 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
         this._props.glContainer.layoutManager.off('activeContentItemChanged', this.onActiveContentItemChanged);
     }
 
-    // Flag indicating that when a tab is activated, it should not select it's document.
-    // this is used by the link properties menu when it wants to display the link target without selecting the target (which would make the link property window go away since it would no longer be selected)
-    public static DontSelectOnActivate = 'dontSelectOnActivate';
-
-    @action.bound
     // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    private onActiveContentItemChanged(contentItem: any) {
+    onActiveContentItemChanged = (contentItem: any) => {
         if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) {
             this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab;
             if (!this._view && this.tab?.contentItem?.config?.props?.panelName !== TabDocView.DontSelectOnActivate) setTimeout(() => DocumentView.SelectView(this._view, false));
             !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one.
         }
-    }
+    };
 
     // adds a tab to the layout based on the locaiton parameter which can be:
     //  close[:{left,right,top,bottom}]  - e.g., "close" will close the tab, "close:left" will close the left tab,
@@ -551,7 +529,6 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
         const whereMods = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none;
         const panelName = whereFields.length > 1 ? whereFields.lastElement() : '';
         if (docs[0]?.dockingConfig && !keyValue) return DashboardView.openDashboard(docs[0]);
-        // prettier-ignore
         switch (whereFields[0]) {
             case undefined:
             case OpenWhere.lightbox:   return LightboxView.Instance.AddDocTab(docs[0], location);
@@ -559,7 +536,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
             case OpenWhere.replace:    return CollectionDockingView.ReplaceTab(docs[0], whereMods, this.stack, panelName, undefined, keyValue);
             case OpenWhere.toggle:     return CollectionDockingView.ToggleSplit(docs[0], whereMods, this.stack, TabDocView.DontSelectOnActivate, keyValue);
             case OpenWhere.add:default:return CollectionDockingView.AddSplit(docs[0], whereMods, this.stack, undefined, keyValue);
-        }
+        } // prettier-ignore
     };
     remDocTab = (doc: Doc | Doc[]) => {
         if (doc === this._document) {
@@ -571,8 +548,6 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     };
 
     getCurrentFrame = () => NumCast(Cast(PresBox.Instance.activeItem.presentation_targetDoc, Doc, null)._currentFrame);
-
-    @action
     focusFunc = () => {
         if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) {
             this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
@@ -580,7 +555,6 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
         return undefined;
     };
     active = () => this._isActive;
-    @observable _forceInvalidateScreenToLocal = 0;
     ScreenToLocalTransform = () => {
         this._forceInvalidateScreenToLocal;
         const { translateX, translateY } = ClientUtils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement);
@@ -594,47 +568,44 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     whenChildContentActiveChanges = (isActive: boolean) => {
         this._isAnyChildContentActive = isActive;
     };
-    isContentActive = () => this._isContentActive;
+    isContentActiveFunc = () => this.isContentActive;
     waitForDoubleClick = () => (SnappingManager.ExploreMode ? 'never' : undefined);
-    @computed get docView() {
-        return !this._activated || !this._document ? null : (
-            <>
-                <DocumentView
-                    key={this._document[Id]}
-                    ref={action((r: DocumentView) => {
-                        this._lastView && DocumentView.removeView(this._lastView);
-                        this._view = r;
-                        this._lastView = this._view;
-                    })}
-                    renderDepth={0}
-                    LayoutTemplateString={this._props.keyValue ? KeyValueBox.LayoutString() : undefined}
-                    hideTitle={this._props.keyValue}
-                    Document={this._document}
-                    TemplateDataDocument={!Doc.AreProtosEqual(this._document[DocData], this._document) ? this._document[DocData] : undefined}
-                    waitForDoubleClickToClick={this.waitForDoubleClick}
-                    isContentActive={this.isContentActive}
-                    isDocumentActive={returnFalse}
-                    PanelWidth={this.PanelWidth}
-                    PanelHeight={this.PanelHeight}
-                    styleProvider={DefaultStyleProvider}
-                    childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyFilter}
-                    childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyFilter}
-                    searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
-                    addDocument={undefined}
-                    removeDocument={this.remDocTab}
-                    addDocTab={this.addDocTab}
-                    suppressSetHeight={!!this._document._layout_fitWidth}
-                    ScreenToLocalTransform={this.ScreenToLocalTransform}
-                    dontCenter="y"
-                    whenChildContentsActiveChanged={this.whenChildContentActiveChanges}
-                    focus={this.focusFunc}
-                    containerViewPath={returnEmptyDocViewList}
-                    pinToPres={TabDocView.PinDoc}
-                />
-                {this.disableMinimap() ? null : <TabMinimapView key="minimap" addDocTab={this.addDocTab} PanelHeight={this.PanelHeight} PanelWidth={this.PanelWidth} background={this.miniMapColor} document={this._document} tabView={this.tabView} />}
-            </>
-        );
-    }
+    renderDocView = (doc: Doc) => (
+        <DocumentView
+            key={doc[Id]}
+            ref={action((r: DocumentView) => {
+                const now = Date.now();
+                this._lastSelection = this._view?.IsSelected ? now : this._lastSelection;
+                if (this._view) DocumentView.removeView(this._view);
+                this._view = r;
+                if (this._view && now - this._lastSelection < 1000) this._view.select(false);
+            })}
+            renderDepth={0}
+            LayoutTemplateString={this._props.keyValue ? KeyValueBox.LayoutString() : undefined}
+            hideTitle={this._props.keyValue}
+            Document={doc}
+            TemplateDataDocument={!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined}
+            waitForDoubleClickToClick={this.waitForDoubleClick}
+            isContentActive={this.isContentActiveFunc}
+            isDocumentActive={returnFalse}
+            PanelWidth={this.PanelWidth}
+            PanelHeight={this.PanelHeight}
+            styleProvider={DefaultStyleProvider}
+            childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyFilter}
+            childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyFilter}
+            searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
+            addDocument={undefined}
+            removeDocument={this.remDocTab}
+            addDocTab={this.addDocTab}
+            suppressSetHeight={!!doc._layout_fitWidth}
+            ScreenToLocalTransform={this.ScreenToLocalTransform}
+            dontCenter="y"
+            whenChildContentsActiveChanged={this.whenChildContentActiveChanges}
+            focus={this.focusFunc}
+            containerViewPath={returnEmptyDocViewList}
+            pinToPres={TabDocView.PinDoc}
+        />
+    );
 
     render() {
         return (
@@ -647,23 +618,21 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
                 onPointerLeave={action(() => { this._hovering = false; })} // prettier-ignore
                 onDragOver={action(() =>     { this._hovering = true; })} // prettier-ignore
                 onDragLeave={action(() =>    { this._hovering = false; })} // prettier-ignore
-                ref={ref => {
+                ref={(ref: TabHTMLElement) => {
+                    // "add" an InitTab function to this div to call from tabCreated in CollectionDockingView when div is reused
                     this._mainCont = ref;
                     if (this._mainCont) {
-                        if (this._lastTab) {
-                            this._view && DocumentView.removeView(this._view);
-                        }
-                        this._lastTab = this.tab;
-                        (this._mainCont as { InitTab?: (tab: object) => void }).InitTab = (tab: object) => this.init(tab, this._document);
-                        DocServer.GetRefField(this._props.documentId).then(
-                            action(doc => {
-                                doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document);
-                            })
-                        );
-                        ref && new ResizeObserver(action(() => this._forceInvalidateScreenToLocal++)).observe(ref);
+                        this._mainCont.InitTab = (tab: object) => this.init(tab, this._document);
+                        DocServer.GetRefField(this._props.documentId).then(action(doc => {
+                            doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document);
+                        })); // prettier-ignore
+                        new ResizeObserver(action(() => this._forceInvalidateScreenToLocal++)).observe(this._mainCont);
                     }
                 }}>
-                {this.docView}
+                {!this._activated || !this._document ? null : this.renderDocView(this._document)}
+                {this.disableMinimap() || !this._document ? null : (
+                    <TabMinimapView key="minimap" addDocTab={this.addDocTab} PanelHeight={this.PanelHeight} PanelWidth={this.PanelWidth} background={this.miniMapColor} document={this._document} tabView={this.tabView} />
+                )}
             </div>
         );
     }
-- 
cgit v1.2.3-70-g09d2


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

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

(limited to 'src/client/views/DocumentDecorations.tsx')

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


From 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/DocumentDecorations.tsx')

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


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

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

(limited to 'src/client/views/DocumentDecorations.tsx')

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


From e9167b125a59ff08b5d75691500d80359ebb4d41 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 19 Sep 2024 10:42:41 -0400
Subject: fixed setting rotation center.  fixed not dropping docs onto
 themselves.

---
 eslint.config.mjs                                         | 2 +-
 src/client/views/DocumentDecorations.tsx                  | 4 ++--
 src/client/views/collections/CollectionSubView.tsx        | 2 +-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 +-
 4 files changed, 5 insertions(+), 5 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/eslint.config.mjs b/eslint.config.mjs
index 8926afd7c..706a5ac09 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -47,7 +47,7 @@ export default [
             'no-return-assign': 'error',
             'no-await-in-loop': 'error',
             'no-loop-func': 'error',
-            'no-cond-assign': 'error',
+            '@typescript-eslint/no-cond-assign': 'error',
             'no-use-before-define': 'error',
             '@typescript-eslint/no-explicit-any': 'error',
             'no-restricted-globals': ['error', 'event'],
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 5e7908725..33022fa81 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -340,7 +340,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
 
     setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => {
         const selDoc = seldocview.Document;
-        const newloccentern = seldocview.screenToContentsTransform().transformPoint(rotCenter[0], rotCenter[1]);
+        const newloccentern = seldocview.screenToViewTransform().transformPoint(rotCenter[0], rotCenter[1]);
         const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2];
         const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.Document._rotation) / 180) * Math.PI);
         selDoc._rotation_centerX = final.x / NumCast(seldocview.layoutDoc._width);
@@ -635,7 +635,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     @computed get rotCenter() {
         const lastView = DocumentView.Selected().lastElement();
         if (lastView) {
-            const invXf = lastView.screenToContentsTransform().inverse();
+            const invXf = lastView.screenToViewTransform().inverse();
             const seldoc = lastView.layoutDoc;
             const loccenter = Utils.rotPt(NumCast(seldoc._rotation_centerX) * NumCast(seldoc._width), NumCast(seldoc._rotation_centerY) * NumCast(seldoc._height), invXf.Rotate);
             return invXf.transformPoint(loccenter.x + NumCast(seldoc._width) / 2, loccenter.y + NumCast(seldoc._height) / 2);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 6aca8f2ca..99d6ed28a 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -251,7 +251,7 @@ export function CollectionSubView<X>() {
 
         protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
             const { docDragData } = de.complete;
-            if (docDragData) {
+            if (docDragData && !docDragData.draggedDocuments.includes(this.Document)) {
                 let added;
                 const dropAction = docDragData.dropAction || docDragData.userDropAction;
                 const targetDocments = DocListCast(this.dataDoc[this._props.fieldKey]);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 343e255dc..7287e2b9f 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -561,7 +561,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             const draggedDoc = dragData.droppedDocuments.lastElement();
             let added: Opt<boolean>;
             const dropAction = dragData.dropAction || dragData.userDropAction;
-            if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl)) {
+            if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl) && !dragData.draggedDocuments.includes(this.Document)) {
                 // replace text contents when dragging with Alt
                 if (de.altKey) {
                     const fieldKey = Doc.LayoutFieldKey(draggedDoc);
-- 
cgit v1.2.3-70-g09d2

