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

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

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

diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index c97edd7f0..5db627d23 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -222,7 +222,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
             <div className="filterBox-treeView">
                 <div className="filterBox-select">
                     <div style={{ width: '100%' }}>
-                        <FieldsDropdown Document={this.Document} selectFunc={this.facetClick} showPlaceholder placeholder="add a filter" addedFields={['acl_Guest', LinkedTo]} />
+                        <FieldsDropdown Document={this.Document} selectFunc={this.facetClick} showPlaceholder placeholder="add a filter" addedFields={['acl_Guest', LinkedTo, 'Star', 'Heart', 'Bolt', 'Cloud']} />
                     </div>
                     {/* THE FOLLOWING CODE SHOULD BE DEVELOPER FOR BOOLEAN EXPRESSION (AND / OR) */}
                     {/* <div className="filterBox-select-bool"> 
-- 
cgit v1.2.3-70-g09d2


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

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

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

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


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

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

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

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


From 8e2cc917ab7cb913755aabe9aecce6d4bb157a0a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 4 Sep 2024 09:03:13 -0400
Subject: updated filter panel to allow filtering by tags

---
 src/client/documents/Documents.ts |  1 +
 src/client/views/FilterPanel.tsx  | 26 +++++++++++---------------
 2 files changed, 12 insertions(+), 15 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index af181b031..1acc6bb46 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -450,6 +450,7 @@ export class DocumentOptions {
     onDragStart?: ScriptField; // script to execute at start of drag operation --  e.g., when a "creator" button is dragged this script generates a different document to drop
     target?: Doc; // available for use in scripts.  used to provide a document parameter to the script (Note, this is a convenience entry since any field could be used for parameterizing a script)
     tags?: LISTt = new ListInfo('hashtags added to document, typically using a text view', true);
+    tags_chat?: LISTt = new ListInfo('hashtags added to document by chatGPT', true);
     treeView_HideTitle?: BOOLt = new BoolInfo('whether to hide the top document title of a tree view');
     treeView_HideUnrendered?: BOOLt = new BoolInfo("tells tree view not to display documents that have an 'layout_unrendered' tag unless they also have a treeView_FieldKey tag (presBox)");
     treeView_HideHeaderIfTemplate?: BOOLt = new BoolInfo('whether to hide the header for a document in a tree view only if a childLayoutTemplate is provided (presBox)');
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index b11fa3bd5..2f6d1fbaa 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -192,21 +192,17 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
         const allCollectionDocs = new Set<Doc>();
         SearchUtil.foreachRecursiveDoc(this.targetDocChildren, (depth: number, doc: Doc) => allCollectionDocs.add(doc));
         const set = new Set<string>([...StrListCast(this.Document.childFilters).map(filter => filter.split(Doc.FilterSep)[1]), Doc.FilterNone, Doc.FilterAny]);
-        if (facetHeader === 'tags')
-            allCollectionDocs.forEach(child =>
-                StrListCast(child[facetHeader])
-                    .filter(h => h)
-                    .forEach(key => set.add(key))
-            );
-        else
-            allCollectionDocs.forEach(child => {
-                const fieldVal = child[facetHeader] as FieldType;
-                if (!(fieldVal instanceof List)) {
-                    // currently we have no good way of filtering based on a field that is a list
-                    set.add(Field.toString(fieldVal));
-                    (fieldVal === true || fieldVal === false) && set.add((!fieldVal).toString());
-                }
-            });
+
+        allCollectionDocs.forEach(child => {
+            const fieldVal = child[facetHeader] as FieldType;
+            const fieldStrList = StrListCast(child[facetHeader]).filter(h => h);
+            if (fieldStrList.length) fieldStrList.forEach(key => set.add(key));
+            else if (!(fieldVal instanceof List)) {
+                // currently we have no good way of filtering based on a field that is a list
+                set.add(Field.toString(fieldVal));
+                (fieldVal === true || fieldVal === false) && set.add((!fieldVal).toString());
+            }
+        });
         const facetValues = Array.from(set).filter(v => v);
 
         let nonNumbers = 0;
-- 
cgit v1.2.3-70-g09d2


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

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

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

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


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

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

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

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


From 78f348cd1406cfaf3d0247973b826783ab3532b5 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 17 Sep 2024 21:05:17 -0400
Subject: from last

---
 src/client/views/FilterPanel.tsx           | 170 ++++++++---------
 src/client/views/global/globalScripts.ts   |  14 +-
 src/client/views/nodes/IconTagBox.tsx      |  22 +--
 src/client/views/pdf/GPTPopup/GPTPopup.tsx | 282 ++++++++++++-----------------
 4 files changed, 204 insertions(+), 284 deletions(-)

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

diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index f3f447e22..8133a4d0d 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -1,34 +1,29 @@
 /* eslint-disable react/jsx-props-no-spreading */
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@mui/material';
 import { action, computed, makeObservable, observable, ObservableMap } from 'mobx';
-import { observer } from 'mobx-react';
+import { observer, useLocalObservable } from 'mobx-react';
 import * as React from 'react';
+import { useEffect, useRef } from 'react';
 import { Handles, Rail, Slider, Ticks, Tracks } from 'react-compound-slider';
 import { AiOutlineMinusSquare, AiOutlinePlusSquare } from 'react-icons/ai';
 import { CiCircleRemove } from 'react-icons/ci';
 import { Doc, DocListCast, Field, FieldType, LinkedTo, StrListCast } from '../../fields/Doc';
+import { DocData } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { List } from '../../fields/List';
 import { RichTextField } from '../../fields/RichTextField';
+import { DocCast, StrCast } from '../../fields/Types';
+import { Button, CurrentUserUtils } from '../util/CurrentUserUtils';
 import { SearchUtil } from '../util/SearchUtil';
 import { SnappingManager } from '../util/SnappingManager';
-import { undoable, undoBatch } from '../util/UndoManager';
+import { undoable } from '../util/UndoManager';
 import { FieldsDropdown } from './FieldsDropdown';
 import './FilterPanel.scss';
 import { DocumentView } from './nodes/DocumentView';
+import { ButtonType } from './nodes/FontIconBox/FontIconBox';
 import { Handle, Tick, TooltipRail, Track } from './nodes/SliderBox-components';
 import { ObservableReactComponent } from './ObservableReactComponent';
-import { Button } from '../util/CurrentUserUtils';
-import { ButtonType } from './nodes/FontIconBox/FontIconBox';
-import { DocCast } from '../../fields/Types';
-import { CurrentUserUtils } from '../util/CurrentUserUtils';
-import { StrCast } from '../../fields/Types';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { DocData } from '../../fields/DocSymbols';
-import { Tooltip } from '@mui/material';
-import { useLocalObservable } from 'mobx-react';
-import { useRef } from 'react';
-import { useEffect } from 'react';
-
 
 interface filterProps {
     Document: Doc;
@@ -37,8 +32,7 @@ interface filterProps {
 @observer
 export class FilterPanel extends ObservableReactComponent<filterProps> {
     @observable _selectedFacetHeaders = new Set<string>();
-    public static Instance: FilterPanel; 
-
+    public static Instance: FilterPanel;
 
     constructor(props: filterProps) {
         super(props);
@@ -227,7 +221,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
     };
 
     /**
-     * Allows users to add a filter hotkey to the properties panel. Will also update the multitoggle at the top menu and the 
+     * Allows users to add a filter hotkey to the properties panel. Will also update the multitoggle at the top menu and the
      * icontags tht are displayed on the documents themselves
      * @param hotKey tite of the new hotkey
      */
@@ -246,21 +240,20 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
             scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}' },
         };
 
-        const currHotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles)
+        const currHotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
 
-        Doc.UserDoc().myFilterHotKeyTitles = new List<string>(currHotKeys.concat(hotKey))
+        Doc.UserDoc().myFilterHotKeyTitles = new List<string>(currHotKeys.concat(hotKey));
 
-        Doc.UserDoc()[hotKey] = 'bolt'
+        Doc.UserDoc()[hotKey] = 'bolt';
 
         const newBtn = CurrentUserUtils.setupContextMenuBtn(newKey, filter);
         newBtn.isSystem = newBtn[DocData].isSystem = undefined;
 
-        const subDocs = DocListCast(filter.data)
-        const opts = subDocs[subDocs.length-1]
+        const subDocs = DocListCast(filter.data);
+        const opts = subDocs[subDocs.length - 1];
         Doc.AddDocToList(filter, 'data', newBtn, opts, true);
-    }
+    };
 
-    
     /**
      * Renders the newly formed hotkey icon buttons
      * @returns the buttons to be rendered
@@ -268,16 +261,16 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
     hotKeyButtons = () => {
         const selected = DocumentView.SelectedDocs().lastElement();
         const hotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
-    
+
         // Selecting a button should make it so that the icon on the top filter panel becomes said icon
         const buttons = hotKeys.map((hotKey, i) => (
             <Tooltip key={hotKey} title={<div className="dash-tooltip">Click to customize this hotkey's icon</div>}>
-                <HotKeyIconButton hotKey={hotKey} selected = {selected}/>
+                <HotKeyIconButton hotKey={hotKey} selected={selected} />
             </Tooltip>
         ));
-    
+
         return buttons;
-    }
+    };
 
     // @observable iconPanelMap: Map<string, number> = new Map();
 
@@ -465,18 +458,16 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
     }
 }
 
-
-
 interface HotKeyButtonProps {
     hotKey: string;
-    selected?: Doc
+    selected?: Doc;
 }
 
 /**
- * Renders the buttons that correspond to each icon tag in the properties view. Allows users to change the icon, 
- * title, and delete. 
+ * Renders the buttons that correspond to each icon tag in the properties view. Allows users to change the icon,
+ * title, and delete.
  */
-const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey, selected}) => {
+const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey, selected }) => {
     const state = useLocalObservable(() => ({
         isActive: false,
         isEditing: false,
@@ -496,7 +487,7 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey, select
         },
         setHotKey(newHotKey: string) {
             this.myHotKey = newHotKey;
-        }
+        },
     }));
 
     const panelRef = useRef<HTMLDivElement>(null);
@@ -506,31 +497,29 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey, select
         state.toggleActive();
     };
 
-    const hotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles)
+    const hotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
     const buttons = DocCast(Doc.UserDoc().myContextMenuBtns);
     const filter = DocCast(buttons.Filter);
 
     /**
      * The doc of the button in the context menu that corresponds to the current hotkey
-     * @returns 
+     * @returns
      */
     const myHotKeyDoc = () => {
-        const hotKeyDocs = DocListCast(filter.data)
-        return hotKeyDocs.filter(k => StrCast(k.title) === hotKey)[0]
-
-    }
+        const hotKeyDocs = DocListCast(filter.data);
+        return hotKeyDocs.filter(k => StrCast(k.title) === hotKey)[0];
+    };
 
     /**
-     * Removes a hotkey from list 
+     * Removes a hotkey from list
      */
     const removeHotKey = () => {
         Doc.RemoveDocFromList(filter, 'data', myHotKeyDoc());
-
-    }
+    };
 
     /**
      * Deselects if the user clicks outside the button
-     * @param event 
+     * @param event
      */
     const handleClickOutside = (event: MouseEvent) => {
         if (panelRef.current && !panelRef.current.contains(event.target as Node)) {
@@ -538,24 +527,23 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey, select
             if (state.isEditing) {
                 state.stopEditing();
 
-                updateFromInput()
+                updateFromInput();
             }
         }
     };
 
     /**
-     * Updates the list of hotkeys based on the users input. replaces the old title with the new one and then assigns this new 
+     * Updates the list of hotkeys based on the users input. replaces the old title with the new one and then assigns this new
      * hotkey with the current icon
      */
-    const updateFromInput = undoable (() => {
-        const myDoc = myHotKeyDoc()
-        Doc.UserDoc().myFilterHotKeyTitles = new List<string>(hotKeys.map(k => k === hotKey ? state.myHotKey : k));
-        Doc.UserDoc()[state.myHotKey] = StrCast(Doc.UserDoc()[hotKey])
-        Doc.UserDoc()[hotKey] = ''
-        myDoc.title = state.myHotKey
-        myDoc.toolTip = `Click to toggle the ${state.myHotKey}'s group's visibility`     
-    }, '')
-
+    const updateFromInput = undoable(() => {
+        const myDoc = myHotKeyDoc();
+        Doc.UserDoc().myFilterHotKeyTitles = new List<string>(hotKeys.map(k => (k === hotKey ? state.myHotKey : k)));
+        Doc.UserDoc()[state.myHotKey] = StrCast(Doc.UserDoc()[hotKey]);
+        Doc.UserDoc()[hotKey] = '';
+        myDoc.title = state.myHotKey;
+        myDoc.toolTip = `Click to toggle the ${state.myHotKey}'s group's visibility`;
+    }, '');
 
     useEffect(() => {
         document.addEventListener('mousedown', handleClickOutside);
@@ -564,18 +552,21 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey, select
         };
     }, []);
 
-    const iconOpts = ['star', 'heart', 'bolt', 'satellite', 'palette', 'robot', 'lightbulb', 'highlighter', 'book', 'chalkboard' ];
+    const iconOpts = ['star', 'heart', 'bolt', 'satellite', 'palette', 'robot', 'lightbulb', 'highlighter', 'book', 'chalkboard'];
 
     /**
      * Panel of icons the user can choose from to represent their tag
      */
     const iconPanel = iconOpts.map((icon, i) => (
-        <button key={i} onClick={undoable((e: React.MouseEvent) => {
-            e.stopPropagation;
-            Doc.UserDoc()[hotKey] = icon;
-            myHotKeyDoc().icon = icon 
-            }, '')} className='icon-panel-button'>
-            <FontAwesomeIcon icon={icon as any} color = {SnappingManager.userColor}/>
+        <button
+            key={i}
+            onClick={undoable((e: React.MouseEvent) => {
+                e.stopPropagation;
+                Doc.UserDoc()[hotKey] = icon;
+                myHotKeyDoc().icon = icon;
+            }, '')}
+            className="icon-panel-button">
+            <FontAwesomeIcon icon={icon as any} color={SnappingManager.userColor} />
         </button>
     ));
 
@@ -584,62 +575,57 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey, select
      */
 
     return (
-        <div className={`filterHotKey-button`} 
-            onClick={(e) => {
+        <div
+            className={`filterHotKey-button`}
+            onClick={e => {
                 e.stopPropagation();
                 state.startEditing();
                 setTimeout(() => inputRef.current?.focus(), 0);
-            }}
-        >
+            }}>
             <div className={`hotKey-icon-button ${state.isActive ? 'active' : ''}`} ref={panelRef}>
                 <Tooltip title={<div className="dash-tooltip">Click to customize this hotkey's icon</div>}>
                     <button
                         type="button"
-                        className='hotKey-icon'
+                        className="hotKey-icon"
                         onClick={(e: React.MouseEvent) => {
-                            e.stopPropagation(); 
+                            e.stopPropagation();
                             handleClick();
-                        }}
-                    >
-                        <FontAwesomeIcon icon={Doc.UserDoc()[hotKey] as any} size="2xl" color={SnappingManager.userColor}/>
+                        }}>
+                        <FontAwesomeIcon icon={Doc.UserDoc()[hotKey] as any} size="2xl" color={SnappingManager.userColor} />
                     </button>
                 </Tooltip>
-                {state.isActive && (
-                    <div className="icon-panel">
-                        {iconPanel}
-                    </div>
-                )}
+                {state.isActive && <div className="icon-panel">{iconPanel}</div>}
             </div>
             {state.isEditing ? (
                 <input
                     ref={inputRef}
                     type="text"
                     value={state.myHotKey.toUpperCase()}
-                    onChange={(e) => state.setHotKey(e.target.value)}
+                    onChange={e => state.setHotKey(e.target.value)}
                     onBlur={() => {
                         state.stopEditing();
-                        updateFromInput()
+                        updateFromInput();
                     }}
-                    onKeyDown={(e) => {
+                    onKeyDown={e => {
                         if (e.key === 'Enter') {
                             state.stopEditing();
-                            updateFromInput()
-
+                            updateFromInput();
                         }
                     }}
-                    className='hotkey-title-input'
+                    className="hotkey-title-input"
                 />
             ) : (
-                <p className='hotkey-title'>{hotKey.toUpperCase()}</p>
+                <p className="hotkey-title">{hotKey.toUpperCase()}</p>
             )}
-            <button className='hotKey-close' onClick={(e: React.MouseEvent) => { 
-                e.stopPropagation(); 
-                Doc.UserDoc().myFilterHotKeyTitles = new List<string>(hotKeys.filter(k => k !== hotKey)); 
-                removeHotKey()
-
-            }}>
-                <FontAwesomeIcon icon={'x' as any} color={SnappingManager.userColor}/>
+            <button
+                className="hotKey-close"
+                onClick={(e: React.MouseEvent) => {
+                    e.stopPropagation();
+                    Doc.UserDoc().myFilterHotKeyTitles = new List<string>(hotKeys.filter(k => k !== hotKey));
+                    removeHotKey();
+                }}>
+                <FontAwesomeIcon icon={'x' as any} color={SnappingManager.userColor} />
             </button>
         </div>
     );
-})
\ No newline at end of file
+});
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 99bd537b1..6ccbf28f8 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -2,13 +2,13 @@
 import { Colors } from 'browndash-components';
 import { action, runInAction } from 'mobx';
 import { aggregateBounds } from '../../../Utils';
-import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
 import { Gestures } from '../../../pen-gestures/GestureTypes';
-import { DocumentType } from '../../documents/DocumentTypes';
+import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { LinkManager } from '../../util/LinkManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { UndoManager, undoable } from '../../util/UndoManager';
@@ -35,16 +35,12 @@ import { ImageBox } from '../nodes/ImageBox';
 import { VideoBox } from '../nodes/VideoBox';
 import { WebBox } from '../nodes/WebBox';
 import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
-import { NumListCast, StrListCast } from '../../../fields/Doc';
-import { List } from '../../../fields/List';
-import { CollectionViewType } from '../../documents/DocumentTypes';
 // import { InkTranscription } from '../InkTranscription';
 import { Docs } from '../../documents/Documents';
-import { CollectionSubView } from '../collections/CollectionSubView';
-import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
-import { PropertiesView } from '../PropertiesView';
-import { MainView } from '../MainView';
 import { SnappingManager } from '../../util/SnappingManager';
+import { MainView } from '../MainView';
+import { PropertiesView } from '../PropertiesView';
+import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function IsNoneSelected() {
     return DocumentView.Selected().length <= 0;
diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx
index e6c0961ed..e076e691a 100644
--- a/src/client/views/nodes/IconTagBox.tsx
+++ b/src/client/views/nodes/IconTagBox.tsx
@@ -1,23 +1,19 @@
-import React from 'react';
-import { observer } from 'mobx-react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@mui/material';
 import { computed } from 'mobx';
-import { ObservableReactComponent } from '../ObservableReactComponent';
-import { NumCast } from '../../../fields/Types';
-import { Doc } from '../../../fields/Doc';
+import { observer } from 'mobx-react';
+import React from 'react';
 import { numberRange } from '../../../Utils';
-import { Tooltip } from '@mui/material';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { undoable } from '../../util/UndoManager';
-import { BoolCast } from '../../../fields/Types';
-import { DocCast } from '../../../fields/Types';
-import './IconTagBox.scss';
+import { Doc, StrListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
-import { StrListCast } from '../../../fields/Doc';
-import { StrCast } from '../../../fields/Types';
+import { BoolCast, DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { CollectionViewType } from '../../documents/DocumentTypes';
 import { SnappingManager } from '../../util/SnappingManager';
+import { undoable } from '../../util/UndoManager';
 import { MainView } from '../MainView';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import { PropertiesView } from '../PropertiesView';
+import './IconTagBox.scss';
 
 export interface IconTagProps {
     doc: Doc;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 53bedbb65..ed3f99377 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -3,7 +3,7 @@ import { Button, IconButton, Type } from 'browndash-components';
 import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { CgClose, CgPathBack, CgArrowLeftO, CgCornerUpLeft } from 'react-icons/cg';
+import { CgClose, CgCornerUpLeft } from 'react-icons/cg';
 import ReactLoading from 'react-loading';
 import { TypeAnimation } from 'react-type-animation';
 import { ClientUtils } from '../../../../ClientUtils';
@@ -11,16 +11,14 @@ import { Doc } from '../../../../fields/Doc';
 import { NumCast, StrCast } from '../../../../fields/Types';
 import { Networking } from '../../../Network';
 import { GPTCallType, gptAPICall, gptImageCall } from '../../../apis/gpt/GPT';
-import { Docs } from '../../../documents/Documents';
 import { DocUtils } from '../../../documents/DocUtils';
-import { ObservableReactComponent } from '../../ObservableReactComponent';
-import { AnchorMenu } from '../AnchorMenu';
-import './GPTPopup.scss';
+import { Docs } from '../../../documents/Documents';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { SnappingManager } from '../../../util/SnappingManager';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { DocumentView } from '../../nodes/DocumentView';
-import { DocCast } from '../../../../fields/Types';
-import { RTFCast } from '../../../../fields/Types';
+import { AnchorMenu } from '../AnchorMenu';
+import './GPTPopup.scss';
 
 export enum GPTPopupMode {
     SUMMARY,
@@ -30,21 +28,15 @@ export enum GPTPopupMode {
     DATA,
     CARD,
     SORT,
-    QUIZ
+    QUIZ,
 }
 
 export enum GPTQuizType {
     CURRENT = 0,
     CHOOSE = 1,
-    MULTIPLE = 2
-
+    MULTIPLE = 2,
 }
 
-
-
-
-
-
 interface GPTPopupProps {}
 
 @observer
@@ -168,30 +160,29 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         this.cardsDoneLoading = done;
     }
 
-    @observable sortRespText: string = ''
+    @observable sortRespText: string = '';
 
     @action setSortRespText(resp: string) {
-        this.sortRespText = resp
+        this.sortRespText = resp;
     }
 
-    @observable chatSortPrompt: string = ""
+    @observable chatSortPrompt: string = '';
 
     sortPromptChanged = action((e: React.ChangeEvent<HTMLInputElement>) => {
         this.chatSortPrompt = e.target.value;
     });
 
-    @observable quizAnswer: string = ""
+    @observable quizAnswer: string = '';
 
     quizAnswerChanged = action((e: React.ChangeEvent<HTMLInputElement>) => {
         this.quizAnswer = e.target.value;
     });
 
-    @observable conversationArray: string[] = ["Hi! In this pop up, you can ask ChatGPT questions about your documents and filter / sort them. "]
-
+    @observable conversationArray: string[] = ['Hi! In this pop up, you can ask ChatGPT questions about your documents and filter / sort them. '];
 
     /**
      * When the cards are in quiz mode in the card view, allows gpt to determine whether the user's answer was correct
-     * @returns 
+     * @returns
      */
     generateQuiz = async () => {
         this.setLoading(true);
@@ -201,14 +192,13 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
 
         const selected = DocumentView.SelectedDocs().lastElement();
 
-
         const questionText = 'Question: ' + StrCast(selected['gptInputText']);
 
         if (StrCast(selected['gptRubric']) === '') {
-            const rubricText = 'Rubric: ' + await this.generateRubric(StrCast(selected['gptInputText']), selected)
+            const rubricText = 'Rubric: ' + (await this.generateRubric(StrCast(selected['gptInputText']), selected));
         }
 
-        const rubricText = 'Rubric: ' + (StrCast(selected['gptRubric']))
+        const rubricText = 'Rubric: ' + StrCast(selected['gptRubric']);
         const queryText = questionText + ' UserAnswer: ' + this.quizAnswer + '. ' + 'Rubric' + rubricText;
 
         try {
@@ -217,23 +207,20 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
                 console.error('GPT call failed');
                 return;
             }
-            console.log(res)
-            this.setQuizResp(res)
-            this.conversationArray.push(res)
+            console.log(res);
+            this.setQuizResp(res);
+            this.conversationArray.push(res);
 
             this.setLoading(false);
             this.setSortDone(true);
-
         } catch (err) {
             console.error('GPT call failed');
         }
 
-
-        if (this.onQuizRandom){
-            this.onQuizRandom()
+        if (this.onQuizRandom) {
+            this.onQuizRandom();
         }
-
-    }
+    };
 
     /**
      * Generates a rubric by which to compare the user's answer to
@@ -241,105 +228,95 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
      * @param doc the doc the user is providing info about
      * @returns gpt's response
      */
-    generateRubric = async (inputText: string, doc:Doc) => {
+    generateRubric = async (inputText: string, doc: Doc) => {
         try {
-            const res = await gptAPICall(inputText, GPTCallType.RUBRIC);            
-            doc['gptRubric']= res;
-            return res
+            const res = await gptAPICall(inputText, GPTCallType.RUBRIC);
+            doc['gptRubric'] = res;
+            return res;
         } catch (err) {
             console.error('GPT call failed');
         }
-
-    }
-
-
+    };
 
     @observable private regenerateCallback: (() => Promise<void>) | null = null;
 
     /**
      * Callback function that causes the card view to update the childpair string list
-     * @param callback 
+     * @param callback
      */
     @action public setRegenerateCallback(callback: () => Promise<void>) {
         this.regenerateCallback = callback;
     }
 
-
-
-
     public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false;
     public createFilteredDoc: (axes?: string[]) => boolean = () => false;
     public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined;
 
-    @observable quizRespText: string = ''
-
-    @action setQuizResp (resp: string) {
-        this.quizRespText = resp
+    @observable quizRespText: string = '';
 
+    @action setQuizResp(resp: string) {
+        this.quizRespText = resp;
     }
 
     /**
      * Generates a response to the user's question depending on the type of their question
      */
     generateCard = async () => {
-        console.log(this.chatSortPrompt + "USER PROMPT")
+        console.log(this.chatSortPrompt + 'USER PROMPT');
         this.setLoading(true);
         this.setSortDone(false);
 
         if (this.regenerateCallback) {
             await this.regenerateCallback();
         }
-    
+
         try {
             // const res = await gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt);
             const questionType = await gptAPICall(this.chatSortPrompt, GPTCallType.TYPE);
-            const questionNumber = questionType.split(' ')[0]
-            console.log(questionType)
-            let res = ''
+            const questionNumber = questionType.split(' ')[0];
+            console.log(questionType);
+            let res = '';
 
             switch (questionNumber) {
                 case '1':
                 case '2':
                 case '4':
                     res = await gptAPICall(this.sortDesc, GPTCallType.SUBSET, this.chatSortPrompt);
-                    break
+                    break;
                 case '6':
                     res = await gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt);
-                    break
+                    break;
                 default:
-
                     const selected = DocumentView.SelectedDocs().lastElement();
                     const questionText = StrCast(selected!['gptInputText']);
 
-
                     res = await gptAPICall(questionText, GPTCallType.INFO, this.chatSortPrompt);
-                    break
+                    break;
             }
 
             // Trigger the callback with the result
             if (this.onSortComplete) {
                 this.onSortComplete(res || 'Something went wrong :(', questionNumber, questionType.split(' ').slice(1).join(' '));
 
-                let explanation = res
+                let explanation = res;
+
+                if (questionType != '5' && questionType != '3') {
+                    // Extract explanation surrounded by ------ at the top or both at the top and bottom
+                    const explanationMatch = res.match(/------\s*([\s\S]*?)\s*(?:------|$)/) || [];
+                    explanation = explanationMatch[1] ? explanationMatch[1].trim() : 'No explanation found';
+                }
 
-                if (questionType != '5' && questionType != '3'){
-    
-                // Extract explanation surrounded by ------ at the top or both at the top and bottom
-                const explanationMatch = res.match(/------\s*([\s\S]*?)\s*(?:------|$)/) || [];
-                explanation = explanationMatch[1] ? explanationMatch[1].trim() : 'No explanation found';
-            } 
-    
                 // Set the extracted explanation to sortRespText
                 this.setSortRespText(explanation);
-                this.conversationArray.push(this.sortRespText)
-                this.scrollToBottom()
-    
+                this.conversationArray.push(this.sortRespText);
+                this.scrollToBottom();
+
                 console.log(res);
             }
         } catch (err) {
             console.error(err);
         }
-    
+
         this.setLoading(false);
         this.setSortDone(true);
     };
@@ -476,21 +453,16 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         makeObservable(this);
         GPTPopup.Instance = this;
         this.messagesEndRef = React.createRef();
-
     }
 
-
-
     scrollToBottom = () => {
-            setTimeout(() => {
-                // Code to execute after 1 second (1000 ms)
-                if (this.messagesEndRef.current) {
-
+        setTimeout(() => {
+            // Code to execute after 1 second (1000 ms)
+            if (this.messagesEndRef.current) {
                 this.messagesEndRef.current.scrollIntoView({ behavior: 'smooth', block: 'end' });
-                }
-
-              }, 50);
-      }
+            }
+        }, 50);
+    };
 
     componentDidUpdate = () => {
         if (this.loading) {
@@ -498,9 +470,9 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         }
     };
 
-    @observable quizMode : GPTQuizType = GPTQuizType.CURRENT
-    @action setQuizMode (g: GPTQuizType) {
-        this.quizMode = g
+    @observable quizMode: GPTQuizType = GPTQuizType.CURRENT;
+    @action setQuizMode(g: GPTQuizType) {
+        this.quizMode = g;
     }
 
     cardMenu = () => (
@@ -517,21 +489,18 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
                     textAlign: 'center',
                     color: '#ffffff',
                     fontSize: '16px',
-                    marginBottom: '10px'
+                    marginBottom: '10px',
                 }}
             />
             <Button
                 tooltip="Test your knowledge with ChatGPT!"
                 text="Quiz Cards!"
                 onClick={() => {
-                    this.conversationArray = ['Define the selected card!']
-                    this.setMode(GPTPopupMode.QUIZ)
-                    if (this.onQuizRandom){
-                        this.onQuizRandom()
+                    this.conversationArray = ['Define the selected card!'];
+                    this.setMode(GPTPopupMode.QUIZ);
+                    if (this.onQuizRandom) {
+                        this.onQuizRandom();
                     }
-                    
-
-
                 }}
                 color={StrCast(Doc.UserDoc().userVariantColor)}
                 type={Type.TERT}
@@ -541,13 +510,10 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
                     color: '#ffffff',
                     fontSize: '16px',
                     height: '40%',
-
                 }}
             />
         </div>
-    )
-
-
+    );
 
     handleKeyPress = async (e: React.KeyboardEvent, isSort: boolean) => {
         if (e.key === 'Enter') {
@@ -556,85 +522,72 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
             if (isSort) {
                 this.conversationArray.push(this.chatSortPrompt);
                 await this.generateCard();
-                this.chatSortPrompt = ''
-
+                this.chatSortPrompt = '';
             } else {
                 this.conversationArray.push(this.quizAnswer);
                 await this.generateQuiz();
-                this.quizAnswer = ''
-
+                this.quizAnswer = '';
             }
 
             this.scrollToBottom();
         }
-    }
-    
+    };
+
     cardActual = (opt: GPTPopupMode) => {
-        const isSort = opt === GPTPopupMode.SORT
-            return (
-
-                    <div className="btns-wrapper-gpt">
-                        <div className="chat-wrapper">
-                            <div className="chat-bubbles">
-                                {this.conversationArray.map((message, index) => (
-                                    <div
-                                        key={index}
-                                        className={`chat-bubble ${index % 2 === 1 ? 'user-message' : 'chat-message'}`}
-                                    >
-                                        {message}
-                                    </div>
-                                ))}
-                                {(!this.cardsDoneLoading || this.loading) && (
-                                    <div className={`chat-bubble chat-message`}>
-                                       ...
-                                    </div>
-                                )}
+        const isSort = opt === GPTPopupMode.SORT;
+        return (
+            <div className="btns-wrapper-gpt">
+                <div className="chat-wrapper">
+                    <div className="chat-bubbles">
+                        {this.conversationArray.map((message, index) => (
+                            <div key={index} className={`chat-bubble ${index % 2 === 1 ? 'user-message' : 'chat-message'}`}>
+                                {message}
                             </div>
+                        ))}
+                        {(!this.cardsDoneLoading || this.loading) && <div className={`chat-bubble chat-message`}>...</div>}
+                    </div>
 
-                            <div ref={this.messagesEndRef} style= {{height: '100px'}} />
-
-                        </div>
-
-                        <div className="inputWrapper">
-                            <input
-                                className="searchBox-input"
-                                defaultValue=""
-                                value={isSort ? this.chatSortPrompt : this.quizAnswer} // Controlled input
-                                autoComplete="off"
-                                onChange={isSort ? this.sortPromptChanged : this.quizAnswerChanged}
-                                onKeyDown={(e) => {
-                                    this.handleKeyPress(e, isSort)
+                    <div ref={this.messagesEndRef} style={{ height: '100px' }} />
+                </div>
 
-                                            
-                                    }
-                                }
-                                type="text"
-                                placeholder={`${isSort ? 'Have ChatGPT sort, tag, define, or filter your cards for you!' : 'Define the selected card!'}`}
-                            />
-                        </div>
-                    </div>
-            );
+                <div className="inputWrapper">
+                    <input
+                        className="searchBox-input"
+                        defaultValue=""
+                        value={isSort ? this.chatSortPrompt : this.quizAnswer} // Controlled input
+                        autoComplete="off"
+                        onChange={isSort ? this.sortPromptChanged : this.quizAnswerChanged}
+                        onKeyDown={e => {
+                            this.handleKeyPress(e, isSort);
+                        }}
+                        type="text"
+                        placeholder={`${isSort ? 'Have ChatGPT sort, tag, define, or filter your cards for you!' : 'Define the selected card!'}`}
+                    />
+                </div>
+            </div>
+        );
     };
-    
+
     sortBox = () => (
-        <div style = {{height: '80%'}}>
+        <div style={{ height: '80%' }}>
             {this.heading(this.mode === GPTPopupMode.SORT ? 'SORTING' : 'QUIZ')}
             <>
-                {!this.cardsDoneLoading? (
+                {!this.cardsDoneLoading ? (
                     <div className="content-wrapper">
                         <div className="loading-spinner">
                             <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} />
                             {this.loading ? <span>Loading...</span> : <span>Reading Cards...</span>}
                         </div>
                     </div>
+                ) : this.mode === GPTPopupMode.CARD ? (
+                    this.cardMenu()
                 ) : (
-                    (this.mode === GPTPopupMode.CARD ? this.cardMenu() : this.cardActual(this.mode)) // Call the functions to render JSX
-                )}
+                    this.cardActual(this.mode)
+                ) // Call the functions to render JSX
+                }
             </>
         </div>
     );
-    
-    
 
     imageBox = () => (
         <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
@@ -785,32 +738,25 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
                 <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} />
             ) : (
                 <>
-                {(this.mode === GPTPopupMode.SORT || this.mode === GPTPopupMode.QUIZ) && (
-                        <IconButton
-                            color={StrCast(SettingsManager.userVariantColor)}
-                            tooltip="back"
-                            icon={<CgCornerUpLeft size="16px" />}
-                            onClick={() => this.mode = GPTPopupMode.CARD}
-                            style = {{right: '50px', position: 'absolute'}}
-                        />
+                    {(this.mode === GPTPopupMode.SORT || this.mode === GPTPopupMode.QUIZ) && (
+                        <IconButton color={StrCast(SettingsManager.userVariantColor)} tooltip="back" icon={<CgCornerUpLeft size="16px" />} onClick={() => (this.mode = GPTPopupMode.CARD)} style={{ right: '50px', position: 'absolute' }} />
                     )}
                     <IconButton
                         color={StrCast(SettingsManager.userVariantColor)}
                         tooltip="close"
                         icon={<CgClose size="16px" />}
                         onClick={() => {
-                            this.setVisible(false)}}
+                            this.setVisible(false);
+                        }}
                     />
-                    
                 </>
             )}
         </div>
     );
-        
 
     render() {
         let content;
-        
+
         switch (this.mode) {
             case GPTPopupMode.SUMMARY:
                 content = this.summaryBox();
@@ -829,16 +775,12 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
             default:
                 content = null;
         }
-    
+
         return (
-            <div
-                className="summary-box"
-                style={{ display: this.visible ? 'flex' : 'none' }}
-            >
+            <div className="summary-box" style={{ display: this.visible ? 'flex' : 'none' }}>
                 {content}
                 <div className="resize-handle" />
             </div>
         );
     }
-    
 }
-- 
cgit v1.2.3-70-g09d2


From 4e0ea65c08ff07429cb32f3907268206c80889fa Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 17 Sep 2024 22:11:22 -0400
Subject: linted cardView files after merge.

---
 src/client/apis/gpt/GPT.ts             |  16 +-
 src/client/views/DocumentButtonBar.tsx |  51 ++---
 src/client/views/FilterPanel.tsx       | 352 ++++++++++++++++-----------------
 src/client/views/StyleProvider.tsx     |  10 +-
 src/client/views/nodes/FieldView.tsx   |   4 +-
 5 files changed, 208 insertions(+), 225 deletions(-)

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

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 2657bf12e..5be9d84ff 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -1,4 +1,4 @@
-import { ChatCompletionMessageParam } from 'openai/resources';
+import { ChatCompletionMessageParam, Image } from 'openai/resources';
 import { openai } from './setup';
 
 enum GPTCallType {
@@ -15,7 +15,7 @@ enum GPTCallType {
     RUBRIC = 'rubric',
     TYPE = 'type',
     SUBSET = 'subset',
-    INFO = 'info'
+    INFO = 'info',
 }
 
 type GPTCallOpts = {
@@ -48,7 +48,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
         model: 'gpt-4o',
         maxTokens: 2048,
         temp: 0.25,
-        prompt: "The user is going to give you a list of descriptions. Each one is separated by `======` on either side. Descriptions will vary in length, so make sure to only separate when you see `======`. Sort them by the user's specifications. Make sure each description is only in the list once. Each item should be separated by `======`. Immediately afterward, surrounded by `------` on BOTH SIDES, provide some insight into your reasoning for the way you sorted (and mention nothing about the formatting details given in this description). It is VERY important that you format it exactly as described, ensuring the proper number of `=` and `-` (6 of each) and NO commas"
+        prompt: "The user is going to give you a list of descriptions. Each one is separated by `======` on either side. Descriptions will vary in length, so make sure to only separate when you see `======`. Sort them by the user's specifications. Make sure each description is only in the list once. Each item should be separated by `======`. Immediately afterward, surrounded by `------` on BOTH SIDES, provide some insight into your reasoning for the way you sorted (and mention nothing about the formatting details given in this description). It is VERY important that you format it exactly as described, ensuring the proper number of `=` and `-` (6 of each) and NO commas",
     },
     describe: { model: 'gpt-4-vision-preview', maxTokens: 2048, temp: 0, prompt: 'Describe these images in 3-5 words' },
     chatcard: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Answer the following question as a short flashcard response. Do not include a label.' },
@@ -77,17 +77,15 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
         model: 'gpt-4-turbo',
         maxTokens: 1024,
         temp: 0,
-        prompt: "I'm going to give you a list of descriptions. Each one is separated by `======` on either side. Descriptions will vary in length, so make sure to only separate when you see `======`. Based on the question the user asks, provide a subset of the given descriptions that best matches the user's specifications. Make sure each description is only in the list once. Each item should be separated by `======`. Immediately afterward, surrounded by `------` on BOTH SIDES, provide some insight into your reasoning in the 2nd person (and mention nothing about the formatting details given in this description). It is VERY important that you format it exactly as described, ensuring the proper number of `=` and `-` (6 of each) and no commas"
+        prompt: "I'm going to give you a list of descriptions. Each one is separated by `======` on either side. Descriptions will vary in length, so make sure to only separate when you see `======`. Based on the question the user asks, provide a subset of the given descriptions that best matches the user's specifications. Make sure each description is only in the list once. Each item should be separated by `======`. Immediately afterward, surrounded by `------` on BOTH SIDES, provide some insight into your reasoning in the 2nd person (and mention nothing about the formatting details given in this description). It is VERY important that you format it exactly as described, ensuring the proper number of `=` and `-` (6 of each) and no commas",
     },
 
     info: {
         model: 'gpt-4-turbo',
         maxTokens: 1024,
         temp: 0,
-        prompt: "Answer the user's question with a short (<100 word) response. If a particular document is selected I will provide that information (which may help with your response)"
+        prompt: "Answer the user's question with a short (<100 word) response. If a particular document is selected I will provide that information (which may help with your response)",
     },
-
-
 };
 
 let lastCall = '';
@@ -98,7 +96,7 @@ let lastResp = '';
  * @param inputText Text to process
  * @returns AI Output
  */
-const gptAPICall = async (inputTextIn: string, callType: GPTCallType, prompt?: any) => {
+const gptAPICall = async (inputTextIn: string, callType: GPTCallType, prompt?: string) => {
     const inputText = [GPTCallType.SUMMARY, GPTCallType.FLASHCARD, GPTCallType.QUIZ].includes(callType) ? inputTextIn + '.' : inputTextIn;
     const opts: GPTCallOpts = callTypeMap[callType];
     if (lastCall === inputText) return lastResp;
@@ -131,7 +129,7 @@ const gptImageCall = async (prompt: string, n?: number) => {
             n: n ?? 1,
             size: '1024x1024',
         });
-        return response.data.map((data: any) => data.url);
+        return response.data.map((data: Image) => data.url);
         // return response.data.data[0].url;
     } catch (err) {
         console.error(err);
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index ccde1de80..87dd5f45a 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -298,16 +298,12 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                             key={icon.toString()}
                             size="sm"
                             icon={icon}
-                            onClick={e => {
-                                if (name === 'tags'){
-                                    (targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags))
+                            onClick={() => {
+                                if (name === 'tags') {
+                                    targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags);
                                 } else {
-                                    (targetDoc && (targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels))
+                                    targetDoc && (targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels);
                                 }
-
-
-
-                               
                             }}
                         />
                     </div>
@@ -315,31 +311,24 @@ 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].showIconTags = !targetDoc[DocData].showIconTags;
-                    }}
-                    >
-
-                   
-                    <FontAwesomeIcon className="documentdecorations-icon" icon="tag" />
+            <div className="documentButtonBar-icon">
+                <div className="documentButtonBar-pinTypes" style={{ width: '40px' }}>
+                    {metaBtn('tags', 'star')}
+                    {metaBtn('keywords', 'id-card')}
                 </div>
-            </Tooltip>
-            </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;
+                        }}>
+                        <FontAwesomeIcon className="documentdecorations-icon" icon="tag" />
+                    </div>
+                </Tooltip>
+            </div>
         );
     }
 
@@ -519,4 +508,4 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
             </div>
         );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index 8133a4d0d..3795a4a6d 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/jsx-props-no-spreading */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { action, computed, makeObservable, observable, ObservableMap } from 'mobx';
@@ -24,14 +23,186 @@ import { DocumentView } from './nodes/DocumentView';
 import { ButtonType } from './nodes/FontIconBox/FontIconBox';
 import { Handle, Tick, TooltipRail, Track } from './nodes/SliderBox-components';
 import { ObservableReactComponent } from './ObservableReactComponent';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
 
+interface HotKeyButtonProps {
+    hotKey: string;
+    selected?: Doc;
+}
+
+/**
+ * Renders the buttons that correspond to each icon tag in the properties view. Allows users to change the icon,
+ * title, and delete.
+ */
+const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey /*, selected */ }) => {
+    const state = useLocalObservable(() => ({
+        isActive: false,
+        isEditing: false,
+        myHotKey: hotKey,
+
+        toggleActive() {
+            this.isActive = !this.isActive;
+        },
+        deactivate() {
+            this.isActive = false;
+        },
+        startEditing() {
+            this.isEditing = true;
+        },
+        stopEditing() {
+            this.isEditing = false;
+        },
+        setHotKey(newHotKey: string) {
+            this.myHotKey = newHotKey;
+        },
+    }));
+
+    const panelRef = useRef<HTMLDivElement>(null);
+    const inputRef = useRef<HTMLInputElement>(null);
+
+    const handleClick = () => {
+        state.toggleActive();
+    };
+
+    const hotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
+    const buttons = DocCast(Doc.UserDoc().myContextMenuBtns);
+    const filter = DocCast(buttons.Filter);
+
+    /**
+     * The doc of the button in the context menu that corresponds to the current hotkey
+     * @returns
+     */
+    const myHotKeyDoc = () => {
+        const hotKeyDocs = DocListCast(filter.data);
+        return hotKeyDocs.filter(k => StrCast(k.title) === hotKey)[0];
+    };
+
+    /**
+     * Removes a hotkey from list
+     */
+    const removeHotKey = () => {
+        Doc.RemoveDocFromList(filter, 'data', myHotKeyDoc());
+    };
+
+    /**
+     * Updates the list of hotkeys based on the users input. replaces the old title with the new one and then assigns this new
+     * hotkey with the current icon
+     */
+    const updateFromInput = undoable(() => {
+        const myDoc = myHotKeyDoc();
+        Doc.UserDoc().myFilterHotKeyTitles = new List<string>(hotKeys.map(k => (k === hotKey ? state.myHotKey : k)));
+        Doc.UserDoc()[state.myHotKey] = StrCast(Doc.UserDoc()[hotKey]);
+        Doc.UserDoc()[hotKey] = '';
+        myDoc.title = state.myHotKey;
+        myDoc.toolTip = `Click to toggle the ${state.myHotKey}'s group's visibility`;
+    }, '');
+
+    /**
+     * Deselects if the user clicks outside the button
+     * @param event
+     */
+    const handleClickOutside = (event: MouseEvent) => {
+        if (panelRef.current && !panelRef.current.contains(event.target as Node)) {
+            state.deactivate();
+            if (state.isEditing) {
+                state.stopEditing();
+
+                updateFromInput();
+            }
+        }
+    };
+
+    useEffect(() => {
+        document.addEventListener('mousedown', handleClickOutside);
+        return () => {
+            document.removeEventListener('mousedown', handleClickOutside);
+        };
+    }, []);
+
+    const iconOpts = ['star', 'heart', 'bolt', 'satellite', 'palette', 'robot', 'lightbulb', 'highlighter', 'book', 'chalkboard'] as IconProp[];
+
+    /**
+     * Panel of icons the user can choose from to represent their tag
+     */
+    const iconPanel = iconOpts.map((icon, i) => (
+        <button
+            key={i}
+            onClick={undoable((e: React.MouseEvent) => {
+                e.stopPropagation;
+                Doc.UserDoc()[hotKey] = icon.toString();
+                myHotKeyDoc().icon = icon.toString();
+            }, '')}
+            className="icon-panel-button">
+            <FontAwesomeIcon icon={icon} color={SnappingManager.userColor} />
+        </button>
+    ));
+
+    /**
+     * Actually renders the buttons
+     */
+
+    return (
+        <div
+            className={`filterHotKey-button`}
+            onClick={e => {
+                e.stopPropagation();
+                state.startEditing();
+                setTimeout(() => inputRef.current?.focus(), 0);
+            }}>
+            <div className={`hotKey-icon-button ${state.isActive ? 'active' : ''}`} ref={panelRef}>
+                <Tooltip title={<div className="dash-tooltip">Click to customize this hotkey&apos;s icon</div>}>
+                    <button
+                        type="button"
+                        className="hotKey-icon"
+                        onClick={(e: React.MouseEvent) => {
+                            e.stopPropagation();
+                            handleClick();
+                        }}>
+                        <FontAwesomeIcon icon={Doc.UserDoc()[hotKey] as IconProp} size="2xl" color={SnappingManager.userColor} />
+                    </button>
+                </Tooltip>
+                {state.isActive && <div className="icon-panel">{iconPanel}</div>}
+            </div>
+            {state.isEditing ? (
+                <input
+                    ref={inputRef}
+                    type="text"
+                    value={state.myHotKey.toUpperCase()}
+                    onChange={e => state.setHotKey(e.target.value)}
+                    onBlur={() => {
+                        state.stopEditing();
+                        updateFromInput();
+                    }}
+                    onKeyDown={e => {
+                        if (e.key === 'Enter') {
+                            state.stopEditing();
+                            updateFromInput();
+                        }
+                    }}
+                    className="hotkey-title-input"
+                />
+            ) : (
+                <p className="hotkey-title">{hotKey.toUpperCase()}</p>
+            )}
+            <button
+                className="hotKey-close"
+                onClick={(e: React.MouseEvent) => {
+                    e.stopPropagation();
+                    Doc.UserDoc().myFilterHotKeyTitles = new List<string>(hotKeys.filter(k => k !== hotKey));
+                    removeHotKey();
+                }}>
+                <FontAwesomeIcon icon={'x' as IconProp} color={SnappingManager.userColor} />
+            </button>
+        </div>
+    );
+});
 interface filterProps {
     Document: Doc;
 }
 
 @observer
 export class FilterPanel extends ObservableReactComponent<filterProps> {
-    @observable _selectedFacetHeaders = new Set<string>();
+    // eslint-disable-next-line no-use-before-define
     public static Instance: FilterPanel;
 
     constructor(props: filterProps) {
@@ -40,6 +211,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
         FilterPanel.Instance = this;
     }
 
+    @observable _selectedFacetHeaders = new Set<string>();
     /**
      * @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection
      */
@@ -263,8 +435,8 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
         const hotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
 
         // Selecting a button should make it so that the icon on the top filter panel becomes said icon
-        const buttons = hotKeys.map((hotKey, i) => (
-            <Tooltip key={hotKey} title={<div className="dash-tooltip">Click to customize this hotkey's icon</div>}>
+        const buttons = hotKeys.map(hotKey => (
+            <Tooltip key={hotKey} title={<div className="dash-tooltip">Click to customize this hotkey&apos;s icon</div>}>
                 <HotKeyIconButton hotKey={hotKey} selected={selected} />
             </Tooltip>
         ));
@@ -457,175 +629,3 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
         return undefined;
     }
 }
-
-interface HotKeyButtonProps {
-    hotKey: string;
-    selected?: Doc;
-}
-
-/**
- * Renders the buttons that correspond to each icon tag in the properties view. Allows users to change the icon,
- * title, and delete.
- */
-const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey, selected }) => {
-    const state = useLocalObservable(() => ({
-        isActive: false,
-        isEditing: false,
-        myHotKey: hotKey,
-
-        toggleActive() {
-            this.isActive = !this.isActive;
-        },
-        deactivate() {
-            this.isActive = false;
-        },
-        startEditing() {
-            this.isEditing = true;
-        },
-        stopEditing() {
-            this.isEditing = false;
-        },
-        setHotKey(newHotKey: string) {
-            this.myHotKey = newHotKey;
-        },
-    }));
-
-    const panelRef = useRef<HTMLDivElement>(null);
-    const inputRef = useRef<HTMLInputElement>(null);
-
-    const handleClick = () => {
-        state.toggleActive();
-    };
-
-    const hotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
-    const buttons = DocCast(Doc.UserDoc().myContextMenuBtns);
-    const filter = DocCast(buttons.Filter);
-
-    /**
-     * The doc of the button in the context menu that corresponds to the current hotkey
-     * @returns
-     */
-    const myHotKeyDoc = () => {
-        const hotKeyDocs = DocListCast(filter.data);
-        return hotKeyDocs.filter(k => StrCast(k.title) === hotKey)[0];
-    };
-
-    /**
-     * Removes a hotkey from list
-     */
-    const removeHotKey = () => {
-        Doc.RemoveDocFromList(filter, 'data', myHotKeyDoc());
-    };
-
-    /**
-     * Deselects if the user clicks outside the button
-     * @param event
-     */
-    const handleClickOutside = (event: MouseEvent) => {
-        if (panelRef.current && !panelRef.current.contains(event.target as Node)) {
-            state.deactivate();
-            if (state.isEditing) {
-                state.stopEditing();
-
-                updateFromInput();
-            }
-        }
-    };
-
-    /**
-     * Updates the list of hotkeys based on the users input. replaces the old title with the new one and then assigns this new
-     * hotkey with the current icon
-     */
-    const updateFromInput = undoable(() => {
-        const myDoc = myHotKeyDoc();
-        Doc.UserDoc().myFilterHotKeyTitles = new List<string>(hotKeys.map(k => (k === hotKey ? state.myHotKey : k)));
-        Doc.UserDoc()[state.myHotKey] = StrCast(Doc.UserDoc()[hotKey]);
-        Doc.UserDoc()[hotKey] = '';
-        myDoc.title = state.myHotKey;
-        myDoc.toolTip = `Click to toggle the ${state.myHotKey}'s group's visibility`;
-    }, '');
-
-    useEffect(() => {
-        document.addEventListener('mousedown', handleClickOutside);
-        return () => {
-            document.removeEventListener('mousedown', handleClickOutside);
-        };
-    }, []);
-
-    const iconOpts = ['star', 'heart', 'bolt', 'satellite', 'palette', 'robot', 'lightbulb', 'highlighter', 'book', 'chalkboard'];
-
-    /**
-     * Panel of icons the user can choose from to represent their tag
-     */
-    const iconPanel = iconOpts.map((icon, i) => (
-        <button
-            key={i}
-            onClick={undoable((e: React.MouseEvent) => {
-                e.stopPropagation;
-                Doc.UserDoc()[hotKey] = icon;
-                myHotKeyDoc().icon = icon;
-            }, '')}
-            className="icon-panel-button">
-            <FontAwesomeIcon icon={icon as any} color={SnappingManager.userColor} />
-        </button>
-    ));
-
-    /**
-     * Actually renders the buttons
-     */
-
-    return (
-        <div
-            className={`filterHotKey-button`}
-            onClick={e => {
-                e.stopPropagation();
-                state.startEditing();
-                setTimeout(() => inputRef.current?.focus(), 0);
-            }}>
-            <div className={`hotKey-icon-button ${state.isActive ? 'active' : ''}`} ref={panelRef}>
-                <Tooltip title={<div className="dash-tooltip">Click to customize this hotkey's icon</div>}>
-                    <button
-                        type="button"
-                        className="hotKey-icon"
-                        onClick={(e: React.MouseEvent) => {
-                            e.stopPropagation();
-                            handleClick();
-                        }}>
-                        <FontAwesomeIcon icon={Doc.UserDoc()[hotKey] as any} size="2xl" color={SnappingManager.userColor} />
-                    </button>
-                </Tooltip>
-                {state.isActive && <div className="icon-panel">{iconPanel}</div>}
-            </div>
-            {state.isEditing ? (
-                <input
-                    ref={inputRef}
-                    type="text"
-                    value={state.myHotKey.toUpperCase()}
-                    onChange={e => state.setHotKey(e.target.value)}
-                    onBlur={() => {
-                        state.stopEditing();
-                        updateFromInput();
-                    }}
-                    onKeyDown={e => {
-                        if (e.key === 'Enter') {
-                            state.stopEditing();
-                            updateFromInput();
-                        }
-                    }}
-                    className="hotkey-title-input"
-                />
-            ) : (
-                <p className="hotkey-title">{hotKey.toUpperCase()}</p>
-            )}
-            <button
-                className="hotKey-close"
-                onClick={(e: React.MouseEvent) => {
-                    e.stopPropagation();
-                    Doc.UserDoc().myFilterHotKeyTitles = new List<string>(hotKeys.filter(k => k !== hotKey));
-                    removeHotKey();
-                }}>
-                <FontAwesomeIcon icon={'x' as any} color={SnappingManager.userColor} />
-            </button>
-        </div>
-    );
-});
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 13fef101a..eb434db40 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -326,7 +326,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                             type={Type.TERT}
                             dropdownType={DropdownType.CLICK}
                             fillWidth
-                            // eslint-disable-next-line react/no-unstable-nested-components
                             iconProvider={() => <div className='styleProvider-filterShift'><FaFilter/></div>}
                             closeOnSelect
                             setSelectedVal={((dvValue: unknown) => {
@@ -366,12 +365,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                     </Tooltip>
                 );
             };
-            const tags = () => props?.DocumentView?.() ? <TagsView Views={[props.DocumentView()]}/> :  null;
-
-            const iconTags = () => {
-                if (doc && doc![DocData].showIconTags)
-                    {return (<IconTagBox doc= {doc}></IconTagBox>)}
-            }
+            const tags = () => docView?.() ? <TagsView Views={[docView?.()]}/> :  null;
+            const iconTags = () => doc?.[DocData].showIconTags ? <IconTagBox doc= {doc}/> : null;
+        
             return (
                 <>
                     {paint()}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index c269c7bcb..683edba16 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,5 +1,3 @@
-/* eslint-disable react/no-unused-prop-types */
-/* eslint-disable react/require-default-props */
 import { Property } from 'csstype';
 import { computed } from 'mobx';
 import { observer } from 'mobx-react';
@@ -21,6 +19,7 @@ export type FocusFuncType = (doc: Doc, options: FocusViewOptions) => Opt<number>
 // eslint-disable-next-line no-use-before-define
 export type StyleProviderFuncType = (
     doc: Opt<Doc>,
+    // eslint-disable-next-line no-use-before-define
     props: Opt<FieldViewProps>,
     property: string
 ) =>
@@ -65,6 +64,7 @@ export interface FieldViewSharedProps {
     containerViewPath?: () => DocumentView[];
     fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
     isGroupActive?: () => string | undefined; // is this document part of a group that is active
+    // eslint-disable-next-line no-use-before-define
     setContentViewBox?: (view: ViewBoxInterface<FieldViewProps>) => void; // called by rendered field's viewBox so that DocumentView can make direct calls to the viewBox
     PanelWidth: () => number;
     PanelHeight: () => number;
-- 
cgit v1.2.3-70-g09d2


From 62553588078728ac462cda27b9ddd45f5a6412aa Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Thu, 19 Sep 2024 00:48:42 -0400
Subject: fixed icon disapearring bug

---
 src/client/views/FilterPanel.tsx           | 2 +-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index 3795a4a6d..b6bea1d4b 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -92,7 +92,6 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey /*, sel
         const myDoc = myHotKeyDoc();
         Doc.UserDoc().myFilterHotKeyTitles = new List<string>(hotKeys.map(k => (k === hotKey ? state.myHotKey : k)));
         Doc.UserDoc()[state.myHotKey] = StrCast(Doc.UserDoc()[hotKey]);
-        Doc.UserDoc()[hotKey] = '';
         myDoc.title = state.myHotKey;
         myDoc.toolTip = `Click to toggle the ${state.myHotKey}'s group's visibility`;
     }, '');
@@ -196,6 +195,7 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey /*, sel
         </div>
     );
 });
+
 interface filterProps {
     Document: Doc;
 }
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index ed3f99377..d5f5f620c 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -74,7 +74,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     };
     @observable
     public dataJson: string = '';
-    public dataChatPrompt: string | null = null;
+    public dataChatPrompt: string | undefined = undefined;
     @action
     public setDataJson = (text: string) => {
         if (text === '') this.dataChatPrompt = '';
-- 
cgit v1.2.3-70-g09d2


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

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

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

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


From 302b9ab98001a50ca2117df823216a99b7c25869 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 19 Sep 2024 20:03:49 -0400
Subject: changed carousel to set #star tags instead of a metaadata field.

---
 src/client/views/FilterPanel.tsx                   | 41 +++++-----------------
 src/client/views/TagsView.tsx                      |  2 +-
 .../views/collections/CollectionCarouselView.tsx   | 10 ++++--
 3 files changed, 17 insertions(+), 36 deletions(-)

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

diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index a3c8fc383..e34b66963 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -40,38 +40,17 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey /*, sel
         isEditing: false,
         myHotKey: hotKey,
 
-        toggleActive() {
-            this.isActive = !this.isActive;
-        },
-        deactivate() {
-            this.isActive = false;
-        },
-        startEditing() {
-            this.isEditing = true;
-        },
-        stopEditing() {
-            this.isEditing = false;
-        },
-        setHotKey(newHotKey: string) {
-            this.myHotKey.title = newHotKey;
-        },
-    }));
+        toggleActive() { this.isActive = !this.isActive; },
+        deactivate()   { this.isActive = false; },
+        startEditing() { this.isEditing = true; },
+        stopEditing()  { this.isEditing = false; },
+        setHotKey(newHotKey: string) { this.myHotKey.title = newHotKey; },
+    })); // prettier-ignore
 
     const panelRef = useRef<HTMLDivElement>(null);
     const inputRef = useRef<HTMLInputElement>(null);
 
-    const handleClick = () => {
-        state.toggleActive();
-    };
-
-    const buttons = DocCast(Doc.UserDoc().myContextMenuBtns);
-
-    /**
-     * Removes a hotkey from list
-     */
-    const removeHotKey = () => {
-        Doc.RemFromFilterHotKeys(hotKey);
-    };
+    const handleClick = () => state.toggleActive();
 
     /**
      * Updates the list of hotkeys based on the users input. replaces the old title with the new one and then assigns this new
@@ -99,9 +78,7 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey /*, sel
 
     useEffect(() => {
         document.addEventListener('mousedown', handleClickOutside);
-        return () => {
-            document.removeEventListener('mousedown', handleClickOutside);
-        };
+        return () => document.removeEventListener('mousedown', handleClickOutside);
     }, []);
 
     const iconOpts = ['star', 'heart', 'bolt', 'satellite', 'palette', 'robot', 'lightbulb', 'highlighter', 'book', 'chalkboard'] as IconProp[];
@@ -172,7 +149,7 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey /*, sel
                 className="hotKey-close"
                 onClick={(e: React.MouseEvent) => {
                     e.stopPropagation();
-                    removeHotKey();
+                    Doc.RemFromFilterHotKeys(hotKey);
                 }}>
                 <FontAwesomeIcon icon={'x' as IconProp} color={SnappingManager.userColor} />
             </button>
diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx
index 2f30edb80..f44fd1d03 100644
--- a/src/client/views/TagsView.tsx
+++ b/src/client/views/TagsView.tsx
@@ -85,7 +85,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
     public static allDocsWithTag = (tag: string) => DocListCast(TagItem.findTagCollectionDoc(tag)?.[DocData].docs);
 
     public static docHasTag = (doc: Doc, tag: string) => {
-        return StrListCast(doc.tags).includes(tag);
+        return StrListCast(doc?.tags).includes(tag);
     };
     /**
      * Adds a tag to the metadata of this document and adds the Doc to the corresponding tag collection Doc (or creates it)
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 4609be374..974cd3e36 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -14,6 +14,7 @@ import { FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import './CollectionCarouselView.scss';
 import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
+import { TagItem } from '../TagsView';
 
 enum cardMode {
     PRACTICE = 'practice',
@@ -28,7 +29,7 @@ enum practiceVal {
 export class CollectionCarouselView extends CollectionSubView() {
     private _dropDisposer?: DragManager.DragDropDisposer;
     get practiceField() { return this.fieldKey + "_practice"; } // prettier-ignore
-    get starField()     { return "star"; } // prettier-ignore
+    get starField()     { return "#star"; } // prettier-ignore
 
     _fadeTimer: NodeJS.Timeout | undefined;
     _resetter: IReactionDisposer | undefined;
@@ -108,7 +109,10 @@ export class CollectionCarouselView extends CollectionSubView() {
     star = (e: React.MouseEvent) => {
         e.stopPropagation();
         const curDoc = this.carouselItems[this.carouselIndex];
-        curDoc && (curDoc[this.starField] = curDoc[this.starField] ? undefined : true);
+        if (curDoc) {
+            if (TagItem.docHasTag(curDoc, this.starField)) TagItem.removeTagFromDoc(curDoc, this.starField);
+            else TagItem.addTagToDoc(curDoc, this.starField);
+        }
     };
 
     /*
@@ -241,7 +245,7 @@ export class CollectionCarouselView extends CollectionSubView() {
                     <FontAwesomeIcon icon="chevron-right" size="2x" />
                 </div>
                 <div key="star" className="carouselView-star" onClick={this.star}>
-                    <FontAwesomeIcon icon="star" color={this.carouselItems?.[this.carouselIndex][this.starField] ? 'yellow' : 'gray'} size="1x" />
+                    <FontAwesomeIcon icon="star" color={TagItem.docHasTag(this.carouselItems?.[this.carouselIndex], this.starField) ? 'yellow' : 'gray'} size="1x" />
                 </div>
                 <div key="remove" className="carouselView-remove" onClick={e => this.setPracticeVal(e, practiceVal.MISSED)} style={{ visibility: this.layoutDoc.filterOp === cardMode.PRACTICE ? 'visible' : 'hidden' }}>
                     <FontAwesomeIcon icon="xmark" color="red" size="1x" />
-- 
cgit v1.2.3-70-g09d2

