From 5cdabed4f867054ff1eade0365a56864c51e328f Mon Sep 17 00:00:00 2001
From: alyssaf16 <alyssa_feinberg@brown.edu>
Date: Tue, 5 Mar 2024 11:46:13 -0500
Subject: hover to show alt content

---
 .../views/nodes/formattedText/FormattedTextBox.tsx | 28 ++++++++++++++++++----
 1 file changed, 23 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f2c4c6c8f..0193cdbfc 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -924,6 +924,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             event: () => (this.layoutDoc._layout_enableAltContentUI = !this.layoutDoc._layout_enableAltContentUI),
             icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye',
         });
+        if (this.Document._layout_enableAltContentUI) {
+            const usepath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
+            appearanceItems.push({
+                description: (this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate:hover' ? 'no hover' : 'hover') + ' to show alt content',
+                event: () => (this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usepath === 'alternate' || usepath === undefined ? 'alternate:hover' : undefined),
+                icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye',
+            });
+            // if (usepath === undefined || usepath === 'alternate') usepath = 'alternate: hover'
+            // if (usepath === 'alternate:hover') usepath = undefined;
+        }
+
         !Doc.noviceMode && appearanceItems.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
         !Doc.noviceMode &&
             appearanceItems.push({
@@ -1939,7 +1950,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     cycleAlternateText = () => {
         if (this.layoutDoc._layout_enableAltContentUI) {
             const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
-            this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined;
+            this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : undefined;
+            //: usePath === 'alternate' ? 'alternate:hover' : undefined;
+            //usePath === undefined ? 'alternate' :
+            //undefined;
+            //: usePath === 'alternate' ? 'alternate:hover' : undefined;
         }
     };
     @computed get overlayAlternateIcon() {
@@ -1947,18 +1962,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         return (
             <Tooltip
                 title={
+                    <div className="dash-tooltip">
+                        flip
+                        {/* 
                     <div className="dash-tooltip">
                         toggle (%/) between
                         <span style={{ color: usePath === undefined ? 'black' : undefined }}>
-                            <em> primary, </em>
-                        </span>
+                            <em> primary </em>
+                        </span> and
                         <span style={{ color: usePath === 'alternate' ? 'black' : undefined }}>
-                            <em>alternate, </em>
+                            <em>alternate </em>
                         </span>
                         and show
                         <span style={{ color: usePath === 'alternate:hover' ? 'black' : undefined }}>
                             <em> alternate on hover</em>
-                        </span>
+                        </span> */}
                     </div>
                 }>
                 <div
-- 
cgit v1.2.3-70-g09d2


From 73f709846365cb266c8489678d5c6713c0c43158 Mon Sep 17 00:00:00 2001
From: alyssaf16 <alyssa_feinberg@brown.edu>
Date: Tue, 5 Mar 2024 17:23:33 -0500
Subject: difference between flashcard and comparison node

---
 src/client/documents/Documents.ts                  | 12 ++++++-
 src/client/util/CurrentUserUtils.ts                |  3 +-
 src/client/views/nodes/ComparisonBox.tsx           | 38 ++++++++++++----------
 src/client/views/nodes/DocumentView.tsx            |  3 ++
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 --
 5 files changed, 37 insertions(+), 21 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2d2f5fe4a..f334e958a 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -265,6 +265,7 @@ export class DocumentOptions {
     _layout_fitContentsToBox?: BOOLt = new BoolInfo('whether a freeformview should zoom/scale to create a shrinkwrapped view of its content');
     _layout_fieldKey?: STRt = new StrInfo('the field key containing the current layout definition', false);
     _layout_enableAltContentUI?: BOOLt = new BoolInfo('whether to show alternate content button');
+    _layout_isFlashcard?: BOOLt = new BoolInfo('whether comparison node should be displayed as a flashcard');
     _layout_showTitle?: string; // field name to display in header (:hover is an optional suffix)
     _layout_showSidebar?: BOOLt = new BoolInfo('whether an annotationsidebar should be displayed for text docuemnts');
     _layout_showCaption?: string; // which field to display in the caption area.  leave empty to have no caption
@@ -728,7 +729,16 @@ export namespace Docs {
                 {
                     data: '',
                     layout: { view: ComparisonBox, dataField: defaultDataKey },
-                    options: { backgroundColor: 'gray', dropAction: 'move', waitForDoubleClickToClick: 'always', layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsLayoutSplit' },
+                    options: {
+                        _layout_isFlashcard: false,
+                        backgroundColor: 'gray',
+                        dropAction: 'move',
+                        waitForDoubleClickToClick: 'always',
+                        layout_reflowHorizontal: true,
+                        layout_reflowVertical: true,
+                        layout_nativeDimEditable: true,
+                        systemIcon: 'BsLayoutSplit',
+                    },
                 },
             ],
             [
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 714e33d25..95e7841bf 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -241,7 +241,8 @@ export class CurrentUserUtils {
                           creator:(opts:DocumentOptions)=> any // how to create the empty thing if it doesn't exist
               }[] = [
             {key: "Note",       creator: opts => Docs.Create.TextDocument("", opts),        opts: { _width: 200, _layout_autoHeight: true }},
-            {key: "Flashcard",  creator: opts => Docs.Create.TextDocument("", opts),        opts: { _width: 200, _layout_autoHeight: true, _layout_enableAltContentUI: true}},
+            {key: "Flashcard", creator: Docs.Create.ComparisonDocument,                    opts: { _layout_isFlashcard: true, _width: 300, _height: 300 }},
+            //{key: "Flashcard",  creator: opts => Docs.Create.TextDocument("", opts),        opts: { _width: 200, _layout_autoHeight: true, _layout_enableAltContentUI: true}},
             {key: "Equation",   creator: opts => Docs.Create.EquationDocument("",opts),     opts: { _width: 300, _height: 35, }},
             {key: "Noteboard",  creator: opts => Docs.Create.NoteTakingDocument([], opts),  opts: { _width: 250, _height: 200, _layout_fitWidth: true}},
             {key: "Simulation", creator: opts => Docs.Create.SimulationDocument(opts),      opts: { _width: 300, _height: 300, }},
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 116dc48a6..bdf92038f 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -206,24 +206,28 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
             );
         };
 
-        return (
-            <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}>
-                {displayBox(`${this.fieldKey}_2`, 1, this._props.PanelWidth() - 3)}
-                <div className="clip-div" style={{ width: this.clipWidth + '%', transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, 'gray') }}>
-                    {displayBox(`${this.fieldKey}_1`, 0, 0)}
-                </div>
+        if (this.Document._layout_isFlashcard) {
+            return <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}>{displayBox(`${this.fieldKey}_2`, 1, this._props.PanelWidth() - 3)}</div>;
+        } else {
+            return (
+                <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}>
+                    {displayBox(`${this.fieldKey}_2`, 1, this._props.PanelWidth() - 3)}
+                    <div className="clip-div" style={{ width: this.clipWidth + '%', transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, 'gray') }}>
+                        {displayBox(`${this.fieldKey}_1`, 0, 0)}
+                    </div>
 
-                <div
-                    className="slide-bar"
-                    style={{
-                        left: `calc(${this.clipWidth + '%'} - 0.5px)`,
-                        cursor: this.clipWidth < 5 ? 'e-resize' : this.clipWidth / 100 > (this._props.PanelWidth() - 5) / this._props.PanelWidth() ? 'w-resize' : undefined,
-                    }}
-                    onPointerDown={e => !this._isAnyChildContentActive && this.registerSliding(e, this._props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */
-                >
-                    <div className="slide-handle" />
+                    <div
+                        className="slide-bar"
+                        style={{
+                            left: `calc(${this.clipWidth + '%'} - 0.5px)`,
+                            cursor: this.clipWidth < 5 ? 'e-resize' : this.clipWidth / 100 > (this._props.PanelWidth() - 5) / this._props.PanelWidth() ? 'w-resize' : undefined,
+                        }}
+                        onPointerDown={e => !this._isAnyChildContentActive && this.registerSliding(e, this._props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */
+                    >
+                        <div className="slide-handle" />
+                    </div>
                 </div>
-            </div>
-        );
+            );
+        }
     }
 }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ed5d31664..7e86125e5 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -569,6 +569,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                 appearanceItems.splice(0, 0, { description: 'Open in Lightbox', event: () => LightboxView.Instance.SetLightboxDoc(this.Document), icon: 'external-link-alt' });
             }
             appearanceItems.push({ description: 'Pin', event: () => this._props.pinToPres(this.Document, {}), icon: 'eye' });
+
+            // if(this.layoutDoc.)
+            appearanceItems.push({ description: 'Pin', event: () => this._props.pinToPres(this.Document, {}), icon: 'eye' });
             !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template   ', event: () => this._props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
             !appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'compass' });
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 0193cdbfc..61d6094b9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -931,8 +931,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 event: () => (this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usepath === 'alternate' || usepath === undefined ? 'alternate:hover' : undefined),
                 icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye',
             });
-            // if (usepath === undefined || usepath === 'alternate') usepath = 'alternate: hover'
-            // if (usepath === 'alternate:hover') usepath = undefined;
         }
 
         !Doc.noviceMode && appearanceItems.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
-- 
cgit v1.2.3-70-g09d2


From 32a742020243ffadfdae2cbc0a8b2853b0f06fc5 Mon Sep 17 00:00:00 2001
From: alyssaf16 <alyssa_feinberg@brown.edu>
Date: Sun, 10 Mar 2024 14:33:08 -0400
Subject: flashcard

---
 src/client/views/nodes/ComparisonBox.tsx                  | 12 +++++++++++-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx |  4 ----
 2 files changed, 11 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index fa0ab61bc..86a9bcf63 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -37,6 +37,14 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     get clipWidthKey() {
         return '_' + this._props.fieldKey + '_clipWidth';
     }
+
+    @computed get clipHeight() {
+        return NumCast(this.layoutDoc[this.clipHeightKey], 200);
+    }
+    get clipHeightKey() {
+        return '_' + this._props.fieldKey + '_clipHeight';
+    }
+
     componentDidMount() {
         this._props.setContentViewBox?.(this);
     }
@@ -80,6 +88,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                     this._animating = 'all 200ms';
                     // on click, animate slider movement to the targetWidth
                     this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this._props.PanelWidth();
+
                     setTimeout(
                         action(() => (this._animating = '')),
                         200
@@ -201,6 +210,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         const displayDoc = (which: string) => {
             const whichDoc = DocCast(this.dataDoc[which]);
             const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc);
+
             return targetDoc ? (
                 <>
                     <DocumentView
@@ -211,7 +221,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                         moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
                         removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2}
                         NativeWidth={returnZero}
-                        NativeHeight={returnZero}
+                        NativeHeight={() => this.clipHeight}
                         isContentActive={emptyFunction}
                         isDocumentActive={returnFalse}
                         whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 61d6094b9..4250b018e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1949,10 +1949,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         if (this.layoutDoc._layout_enableAltContentUI) {
             const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
             this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : undefined;
-            //: usePath === 'alternate' ? 'alternate:hover' : undefined;
-            //usePath === undefined ? 'alternate' :
-            //undefined;
-            //: usePath === 'alternate' ? 'alternate:hover' : undefined;
         }
     };
     @computed get overlayAlternateIcon() {
-- 
cgit v1.2.3-70-g09d2


From d40efe81ab30485266b7b686dfb35c531e75a568 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 8 May 2024 23:16:54 -0400
Subject: fixed text selection on web pages.  cleaned up gptSummarize to work
 on any text.

---
 src/ClientUtils.ts                                        |  2 +-
 src/client/views/MarqueeAnnotator.tsx                     |  1 +
 src/client/views/nodes/DocumentView.tsx                   |  9 +++++++++
 src/client/views/nodes/formattedText/FormattedTextBox.tsx |  3 ++-
 src/client/views/pdf/AnchorMenu.tsx                       | 15 ++++-----------
 5 files changed, 17 insertions(+), 13 deletions(-)

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

diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts
index dbbba896f..d03ae1486 100644
--- a/src/ClientUtils.ts
+++ b/src/ClientUtils.ts
@@ -518,7 +518,7 @@ export function simulateMouseClick(element: Element | null | undefined, x: numbe
 export function getWordAtPoint(elem: any, x: number, y: number): string | undefined {
     if (elem.tagName === 'INPUT') return 'input';
     if (elem.tagName === 'TEXTAREA') return 'textarea';
-    if (elem.nodeType === elem.TEXT_NODE) {
+    if (elem.nodeType === elem.TEXT_NODE || elem.textContent) {
         const range = elem.ownerDocument.createRange();
         range.selectNodeContents(elem);
         let currentPos = 0;
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index a917a7d57..c18ac6738 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -258,6 +258,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
     };
     @action
     onEnd = (x: number, y: number) => {
+        AnchorMenu.Instance.setSelectedText('');
         const marquees = this.props.marqueeContainer.getElementsByClassName('marqueeAnnotator-dragBox');
         const marqueeStyle = (Array.from(marquees).lastElement() as HTMLDivElement)?.style;
         if (!this.isEmpty && marqueeStyle) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8df28a770..ada3de355 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -981,6 +981,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
 @observer
 export class DocumentView extends DocComponent<DocumentViewProps>() {
     public static ROOT_DIV = 'documentView-effectsWrapper';
+    /**
+     * Opens a new Tab for the doc in the specified location (or in the lightbox)
+     */
     public static addSplit: (Doc: Doc, where: OpenWhereMod) => void;
     // Lightbox
     public static _lightboxDoc: () => Doc | undefined;
@@ -1012,7 +1015,13 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     public static DeselectAll: (except?: Doc) => void | undefined;
     public static DeselectView: (dv: DocumentView | undefined) => void | undefined;
     public static SelectView: (dv: DocumentView | undefined, extendSelection: boolean) => void | undefined;
+    /**
+     * returns a list of all currently selected DocumentViews
+     */
     public static Selected: () => DocumentView[];
+    /**
+     * returns a list of all currently selected Docs
+     */
     public static SelectedDocs: () => Doc[];
     public static SelectSchemaDoc: (doc: Doc, deselectAllFirst?: boolean) => void;
     public static SelectedSchemaDoc: () => Opt<Doc>;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index e354aedb7..624721d7c 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -240,7 +240,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     @action
     setupAnchorMenu = () => {
         AnchorMenu.Instance.Status = 'marquee';
-
         AnchorMenu.Instance.OnClick = () => {
             !this.layoutDoc.layout_showSidebar && this.toggleSidebar();
             setTimeout(() => this._sidebarRef.current?.anchorMenuClick(this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true))); // give time for sidebarRef to be created
@@ -292,6 +291,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.DocumentView?.()!, () => this.getAnchor(true), targetCreator), e.pageX, e.pageY);
         });
+
+        AnchorMenu.Instance.setSelectedText(window.getSelection()?.toString() ?? '');
         const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
         this._props.rootSelected?.() && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
         let ele: Opt<HTMLDivElement>;
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index a837969aa..495ea59f0 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -36,10 +36,10 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     @observable public Status: 'marquee' | 'annotation' | '' = '';
 
     // GPT additions
-    @observable private selectedText: string = '';
+    @observable private _selectedText: string = '';
     @action
     public setSelectedText = (txt: string) => {
-        this.selectedText = txt;
+        this._selectedText = txt.trim();
     };
 
     public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
@@ -76,7 +76,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
      * @param e pointer down event
      */
     gptSummarize = async () => {
-        GPTPopup.Instance?.setSelectedText(this.selectedText);
+        GPTPopup.Instance?.setSelectedText(this._selectedText);
         GPTPopup.Instance.generateSummary();
     };
 
@@ -140,13 +140,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         this.highlightColor = ClientUtils.colorString(col);
     };
 
-    /**
-     * Returns whether the selected text can be summarized. The goal is to have
-     * all selected text available to summarize but its only supported for pdf and web ATM.
-     * @returns Whether the GPT icon for summarization should appear
-     */
-    canSummarize = () => DocumentView.SelectedDocs().some(doc => [DocumentType.PDF, DocumentType.WEB].includes(doc.type as any));
-
     render() {
         const buttons =
             this.Status === 'marquee' ? (
@@ -161,7 +154,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                         />
                     </div>
                     {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection */}
-                    {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && (
+                    {this._selectedText && (
                         <IconButton
                             tooltip="Summarize with AI" //
                             onPointerDown={this.gptSummarize}
-- 
cgit v1.2.3-70-g09d2


From dc20525c09943fef332f8e7dac54aab3039e8df7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 10 May 2024 16:34:37 -0400
Subject: fixed exception when text templates have no inherited data, and when
 text views are modified after the editor has been destroyed.  random code
 cleanup

---
 src/client/views/StyleProvider.tsx                 |  6 +-
 src/client/views/collections/TreeView.tsx          |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 64 ++++++++++------------
 .../views/nodes/formattedText/FormattedTextBox.tsx |  7 ++-
 4 files changed, 36 insertions(+), 43 deletions(-)

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

diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 0b8201903..3842f930a 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -80,8 +80,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
     const isNonTransparentLevel = isNonTransparent ? Number(property.replace(/.*:nonTransparent([0-9]+).*/, '$1')) : 0; // property.includes(':nonTransparent');
     const isAnnotated = property.includes(':annotated');
     const layoutDoc = doc ? Doc.Layout(doc) : doc;
-    const isOpen = property.includes(':open');
-    const boxBackground = property.includes(':box');
+    const isOpen = property.includes(':treeOpen');
+    const boxBackground = property.includes(':docView'); // background color of docView's bounds can be different than the background of contents -- eg FontIconBox
     const {
         DocumentView: docView,
         fieldKey: fieldKeyProp,
@@ -115,7 +115,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
     // prettier-ignore
     switch (property.split(':')[0]) {
         case StyleProp.TreeViewIcon: {
-            const img = ImageCast(doc?.icon, ImageCast(doc?.data));
+            const img = ImageCast(doc?.icon ?? doc?.[doc ? Doc.LayoutFieldKey(doc) : ""]);
             if (img) {
                 const ext = extname(img.url.href);
                 const url = doc?.icon ? img.url.href : img.url.href.replace(ext, '_s' + ext);
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index bc12d1c28..b82421e6b 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -779,7 +779,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
 
     @computed get renderBullet() {
         TraceMobx();
-        const iconType = this.treeView._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':open' : !this.childDocs.length ? ':empty' : '')) || 'question';
+        const iconType = this.treeView._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':treeOpen' : !this.childDocs.length ? ':empty' : '')) || 'question';
         const color = SettingsManager.userColor;
         const checked = this.onCheckedClick ? this.Document.treeView_Checked ?? 'unchecked' : undefined;
         return (
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 14feca0b5..804d014a1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -123,31 +123,23 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
 
     animateScaleTime = () => this._animateScaleTime ?? 100;
     style = (doc: Doc, sprop: StyleProp | string) => this._props.styleProvider?.(doc, this._props, sprop);
-    @computed get showTitle()          { return this.style(this.layoutDoc, StyleProp.ShowTitle) as Opt<string>; } // prettier-ignore
-    @computed get opacity()            { return this.style(this.layoutDoc, StyleProp.Opacity); } // prettier-ignore
-    @computed get boxShadow()          { return this.style(this.layoutDoc, StyleProp.BoxShadow); } // prettier-ignore
-    @computed get borderRounding()     { return this.style(this.layoutDoc, StyleProp.BorderRounding); } // prettier-ignore
-    @computed get widgetDecorations()  { return this.style(this.layoutDoc, StyleProp.Decorations); } // prettier-ignore
-    @computed get backgroundBoxColor() { return this.style(this.layoutDoc, StyleProp.BackgroundColor + ':box'); } // prettier-ignore
-    @computed get headerMargin()       { return this.style(this.layoutDoc, StyleProp.HeaderMargin) ?? 0; } // prettier-ignore
-    @computed get showCaption()        { return this.style(this.layoutDoc, StyleProp.ShowCaption) ?? 0;  } // prettier-ignore
-    @computed get titleHeight()        { return this.style(this.layoutDoc, StyleProp.TitleHeight) ?? 0;  } // prettier-ignore
-    @computed get docContents()        { return this.style(this.Document, StyleProp.DocContents); } // prettier-ignore
-    @computed get highlighting()       { return this.style(this.Document, StyleProp.Highlighting); } // prettier-ignore
-    @computed get borderPath()         { return this.style(this.Document, StyleProp.BorderPath); } // prettier-ignore
-
-    @computed get onClickHandler() {
-        return this._props.onClickScript?.() ?? ScriptCast(this.Document.onClick, ScriptCast(this.layoutDoc.onClick));
-    }
-    @computed get onDoubleClickHandler() {
-        return this._props.onDoubleClickScript?.() ?? ScriptCast(this.layoutDoc.onDoubleClick, ScriptCast(this.Document.onDoubleClick));
-    }
-    @computed get onPointerDownHandler() {
-        return this._props.onPointerDownScript?.() ?? ScriptCast(this.layoutDoc.onPointerDown, ScriptCast(this.Document.onPointerDown));
-    }
-    @computed get onPointerUpHandler() {
-        return this._props.onPointerUpScript?.() ?? ScriptCast(this.layoutDoc.onPointerUp, ScriptCast(this.Document.onPointerUp));
-    }
+    @computed get opacity()           { return this.style(this.layoutDoc, StyleProp.Opacity); } // prettier-ignore
+    @computed get boxShadow()         { return this.style(this.layoutDoc, StyleProp.BoxShadow); } // prettier-ignore
+    @computed get borderRounding()    { return this.style(this.layoutDoc, StyleProp.BorderRounding); } // prettier-ignore
+    @computed get widgetDecorations() { return this.style(this.layoutDoc, StyleProp.Decorations); } // prettier-ignore
+    @computed get backgroundBoxColor(){ return this.style(this.layoutDoc, StyleProp.BackgroundColor + ':docView'); } // prettier-ignore
+    @computed get showTitle()         { return this.style(this.layoutDoc, StyleProp.ShowTitle) as Opt<string>; } // prettier-ignore
+    @computed get showCaption()       { return this.style(this.layoutDoc, StyleProp.ShowCaption) ?? 0;  } // prettier-ignore
+    @computed get headerMargin()      { return this.style(this.layoutDoc, StyleProp.HeaderMargin) ?? 0; } // prettier-ignore
+    @computed get titleHeight()       { return this.style(this.layoutDoc, StyleProp.TitleHeight) ?? 0;  } // prettier-ignore
+    @computed get docContents()       { return this.style(this.Document, StyleProp.DocContents); } // prettier-ignore
+    @computed get highlighting()      { return this.style(this.Document, StyleProp.Highlighting); } // prettier-ignore
+    @computed get borderPath()        { return this.style(this.Document, StyleProp.BorderPath); } // prettier-ignore
+
+    @computed get onClickHdlr()       { return this._props.onClickScript?.()       ?? ScriptCast(this.layoutDoc.onClick       ?? this.Document.onClick); } // prettier-ignore
+    @computed get onDoubleClickHdlr() { return this._props.onDoubleClickScript?.() ?? ScriptCast(this.layoutDoc.onDoubleClick ?? this.Document.onDoubleClick); } // prettier-ignore
+    @computed get onPointerDownHdlr() { return this._props.onPointerDownScript?.() ?? ScriptCast(this.layoutDoc.onPointerDown ?? this.Document.onPointerDown); } // prettier-ignore
+    @computed get onPointerUpHdlr()   { return this._props.onPointerUpScript?.()   ?? ScriptCast(this.layoutDoc.onPointerUp   ?? this.Document.onPointerUp); } // prettier-ignore
 
     @computed get disableClickScriptFunc() {
         const onScriptDisable = this._props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable;
@@ -163,7 +155,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
         TraceMobx();
         return this._props.contentPointerEvents ??
             ((!this.disableClickScriptFunc && //
-                this.onClickHandler &&
+                this.onClickHdlr &&
                 !SnappingManager.ExploreMode &&
                 !this.layoutDoc.layout_isSvg &&
                 this.isContentActive() !== true) ||
@@ -315,8 +307,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
             };
             if (this._doubleTap) {
                 const defaultDblclick = this._props.defaultDoubleClick?.() || this.Document.defaultDoubleClick;
-                if (this.onDoubleClickHandler?.script) {
-                    UndoManager.RunInBatch(() => this.onDoubleClickHandler.script.run(scriptProps, console.log).result?.select && this._props.select(false), 'on double click: ' + this.Document.title);
+                if (this.onDoubleClickHdlr?.script) {
+                    UndoManager.RunInBatch(() => this.onDoubleClickHdlr.script.run(scriptProps, console.log).result?.select && this._props.select(false), 'on double click: ' + this.Document.title);
                 } else if (!Doc.IsSystem(this.Document) && defaultDblclick !== 'ignore') {
                     UndoManager.RunInBatch(() => this._props.addDocTab(this.Document, OpenWhere.lightboxAlways), 'double tap');
                     DocumentView.DeselectAll();
@@ -329,9 +321,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                 this._singleClickFunc = undefined;
             } else {
                 let clickFunc: undefined | (() => any);
-                if (!this.disableClickScriptFunc && this.onClickHandler?.script) {
+                if (!this.disableClickScriptFunc && this.onClickHdlr?.script) {
                     clickFunc = undoable(() => {
-                        this.onClickHandler?.script.run(scriptProps, console.log).result?.select && this._props.select(false);
+                        this.onClickHdlr?.script.run(scriptProps, console.log).result?.select && this._props.select(false);
                     }, 'click ' + this.Document.title);
                 } else {
                     // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
@@ -409,10 +401,10 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
         this.cleanupPointerEvents();
         this._longPressSelector && clearTimeout(this._longPressSelector);
 
-        if (this.onPointerUpHandler?.script) {
-            this.onPointerUpHandler.script.run({ this: this.Document }, console.log);
+        if (this.onPointerUpHdlr?.script) {
+            this.onPointerUpHdlr.script.run({ this: this.Document }, console.log);
         } else if (e.button === 0 && ClientUtils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
-            this._doubleTap = (this.onDoubleClickHandler?.script || this.Document.defaultDoubleClick !== 'ignore') && Date.now() - this._lastTap < ClientUtils.CLICK_TIME;
+            this._doubleTap = (this.onDoubleClickHdlr?.script || this.Document.defaultDoubleClick !== 'ignore') && Date.now() - this._lastTap < ClientUtils.CLICK_TIME;
             if (!this.isContentActive()) this._lastTap = Date.now(); // don't want to process the start of a double tap if the doucment is selected
         }
         // eslint-disable-next-line no-use-before-define
@@ -585,7 +577,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                 !Doc.noviceMode && onClicks.push({ description: 'Toggle Detail', event: this.setToggleDetail, icon: 'concierge-bell' });
 
                 if (!this.Document.annotationOn) {
-                    onClicks.push({ description: this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' });
+                    onClicks.push({ description: this.onClickHdlr ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' });
                     !Doc.noviceMode && onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
                     !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
                 } else if (Doc.Links(this.Document).length) {
@@ -688,7 +680,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     rootSelected = () => this._rootSelected;
     panelHeight = () => this._props.PanelHeight() - this.headerMargin;
     screenToLocalContent = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin);
-    onClickFunc = this.disableClickScriptFunc ? undefined : () => this.onClickHandler;
+    onClickFunc = this.disableClickScriptFunc ? undefined : () => this.onClickHdlr;
     setHeight = (height: number) => { !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height)); } // prettier-ignore
     setContentView = action((view: ViewBoxInterface<FieldViewProps>) => { this._componentView = view; }); // prettier-ignore
     isContentActive = (): boolean | undefined => this._isContentActive;
@@ -793,7 +785,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     width: 100 - sidebarWidthPercent + '%',
                     color: background === 'transparent' ? SnappingManager.userColor : lightOrDark(background),
                     background,
-                    pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined,
+                    pointerEvents: (!this.disableClickScriptFunc && this.onClickHdlr) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined,
                 }}>
                 {!dropdownWidth ? null : (
                     <div className="documntViewInternal-dropdown" style={{ width: dropdownWidth }}>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 624721d7c..e62a639c8 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -320,7 +320,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         return '';
     };
     dispatchTransaction = (tx: Transaction) => {
-        if (this._editorView) {
+        if (this._editorView && !this._editorView.isDestroyed) {
             const state = this._editorView.state.apply(tx);
             this._editorView.updateState(state);
             this.tryUpdateDoc(false);
@@ -353,6 +353,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
                 let unchanged = true;
                 const textChange = newText !== prevData?.Text; // the Text string can change even if the RichText doesn't because dashFieldViews may return new strings as the data they reference changes
+                const rtField = (layoutData !== prevData ? layoutData : undefined) ?? protoData;
                 if (this._applyingChange !== this.fieldKey && (force || textChange || removeSelection(newJson) !== removeSelection(prevData?.Data))) {
                     this._applyingChange = this.fieldKey;
                     textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now())));
@@ -366,10 +367,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                             this._applyingChange = ''; // turning this off here allows a Doc to retrieve data from template if noTemplate below is changed to false
                             unchanged = false;
                         }
-                    } else {
+                    } else if (rtField) {
                         // if we've deleted all the text in a note driven by a template, then restore the template data
                         dataDoc[this.fieldKey] = undefined;
-                        this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(((layoutData !== prevData ? layoutData : undefined) ?? protoData)?.Data)));
+                        this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(rtField.Data)));
                         ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, text: newText });
                         unchanged = false;
                     }
-- 
cgit v1.2.3-70-g09d2


From 6a332d5c1e248d976c44eecc67161b4d1b401a0e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 14 May 2024 23:16:04 -0400
Subject: post merge fixes for sarah-ai-visualizatoin

---
 src/client/views/DocComponent.tsx                         |  2 +-
 src/client/views/nodes/DataVizBox/utils/D3Utils.ts        |  5 ++++-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 10 ++++++++--
 src/client/views/nodes/generativeFill/GenerativeFill.tsx  |  6 +++---
 4 files changed, 16 insertions(+), 7 deletions(-)

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

diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 94e84e647..e5752dcd2 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -233,7 +233,7 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
 
                     const annoDocs = Doc.Get(targetDataDoc, annotationKey ?? this.annotationKey, true) as List<Doc>; // get the dataDoc directly ... when using templates there may be some default items already there, but we can't change them, so we copy them below (should really be some kind of inheritance since the template contents could change)
                     if (annoDocs instanceof List) annoDocs.push(...added.filter(add => !annoDocs.includes(add)));
-                    else targetDataDoc[annotationKey ?? this.annotationKey] = new List<Doc>([...added, ...(annoDocs === undefined ? DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]) : [])]);
+                    else targetDataDoc[annotationKey || this.annotationKey] = new List<Doc>([...added, ...(annoDocs === undefined ? DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]) : [])]);
                     targetDataDoc[(annotationKey ?? this.annotationKey) + '_modificationDate'] = new DateField();
                 }
             }
diff --git a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
index be05c3529..ffc859c92 100644
--- a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
+++ b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
@@ -1,6 +1,9 @@
 import * as d3 from 'd3';
-import { DataPoint } from '../components/LineChart';
 
+export interface DataPoint {
+    x: number;
+    y: number;
+}
 // TODO: nda - implement function that can handle range for strings
 
 export const minMaxRange = (dataPts: DataPoint[][]) => {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index e62a639c8..93cb67491 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -955,7 +955,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             icon: 'star',
         });
         optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' });
-        optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' });
+        optionItems.push({ description: `Ask GPT-3`, event: this.askGPT, icon: 'lightbulb' });
         this._props.renderDepth &&
             optionItems.push({
                 description: !this.Document._createDocOnCR ? 'Create New Doc on Carriage Return' : 'Allow Carriage Returns',
@@ -989,6 +989,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
 
     askGPT = action(async () => {
         try {
+            GPTPopup.Instance.setSidebarId(this.SidebarKey);
+            GPTPopup.Instance.addDoc = this.sidebarAddDocument;
             const res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
             if (!res) {
                 this.animateRes(0, 'Something went wrong.');
@@ -1573,7 +1575,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             e.preventDefault();
         }
     };
-    onSelectEnd = (): void => document.removeEventListener('pointerup', this.onSelectEnd);
+    onSelectEnd = () => {
+        GPTPopup.Instance.setSidebarId(this.SidebarKey);
+        GPTPopup.Instance.addDoc = this.sidebarAddDocument;
+        document.removeEventListener('pointerup', this.onSelectEnd);
+    };
     onPointerUp = (e: React.PointerEvent): void => {
         const state = this.EditorView?.state;
         if (state && this.ProseRef?.children[0].className.includes('-focused') && this._props.isContentActive() && !e.button) {
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 25289967e..6d8ba9222 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -535,14 +535,14 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                             key={i}
                             alt="image edits"
                             width={75}
-                            src={edit[0] as string}
+                            src={edit.url}
                             style={{ cursor: 'pointer' }}
                             onClick={async () => {
                                 const img = new Image();
-                                img.src = edit[0] as string;
+                                img.src = edit.url;
                                 ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height);
                                 currImg.current = img;
-                                parentDoc.current = edit[1] as Doc;
+                                parentDoc.current = edit.saveRes ?? null;
                             }}
                         />
                     ))}
-- 
cgit v1.2.3-70-g09d2


From 41e47a7b7789e8c192fb31188cf549178ec71a22 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 16 May 2024 11:28:10 -0400
Subject: moved isHovering to DocumentView as instance variable instead from
 FormattedText as a Document field.  cleaned up cluster style provider.  fixed
 freeformView to not trigger as many updates when a doc is dropped, and fixed
 layoutEngines to not return a background or color unless actually set by the
 engine.

---
 src/client/views/StyleProvider.tsx                 |  2 +-
 .../CollectionFreeFormClusters.ts                  | 16 ++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 71 +++++++++++-----------
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 13 ++--
 src/client/views/nodes/DocumentView.tsx            |  2 +
 src/client/views/nodes/FieldView.tsx               |  1 +
 .../views/nodes/formattedText/FormattedTextBox.tsx |  8 +--
 7 files changed, 53 insertions(+), 60 deletions(-)

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

diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 3842f930a..557f7db37 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -218,7 +218,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
             if (SnappingManager.LastPressedBtn === doc?.[Id]) return SnappingManager.userColor; // hack to indicate active menu panel item
             const dataKey = doc ? Doc.LayoutFieldKey(doc) : "";
             const usePath = StrCast(doc?.[dataKey + "_usePath"]);
-            const alternate = usePath.includes(":hover") ? ( doc?.isHovering ? '_' + usePath.replace(":hover","") : "") : usePath ? "_" +usePath:usePath;
+            const alternate = usePath.includes(":hover") ? ( props?.isHovering?.() ? '_' + usePath.replace(":hover","") : "") : usePath ? "_" +usePath:usePath;
             let docColor: Opt<string> = StrCast(doc?.[fieldKey+alternate], StrCast(doc?.['backgroundColor' +alternate], isCaption ? 'rgba(0,0,0,0.4)' : ''));
             if (doc?.[StrCast(doc?.layout_fieldKey)] instanceof Doc) docColor = StrCast(doc._backgroundColor,docColor)
             // prettier-ignore
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts b/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts
index 26a52cd2a..6415d5225 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts
@@ -2,7 +2,7 @@ import { action, observable } from 'mobx';
 import { CollectionFreeFormView } from '.';
 import { intersectRect } from '../../../../Utils';
 import { Doc, Opt } from '../../../../fields/Doc';
-import { NumCast, StrCast } from '../../../../fields/Types';
+import { Cast, NumCast, StrCast } from '../../../../fields/Types';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { DragManager } from '../../../util/DragManager';
 import { dropActionType } from '../../../util/DropActionTypes';
@@ -179,7 +179,6 @@ export class CollectionFreeFormClusters {
     };
 
     styleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => {
-        let styleProp = this.viewStyleProvider?.(doc, props, property); // bcz: check 'props'  used to be renderDepth + 1
         if (doc && this.childDocs?.includes(doc))
             switch (property.split(':')[0]) {
                 case StyleProp.BackgroundColor:
@@ -189,14 +188,9 @@ export class CollectionFreeFormClusters {
                             if (this._clusterSets.length <= cluster) {
                                 setTimeout(() => doc && this.addDocument(doc));
                             } else {
-                                // choose a cluster color from a palette
-                                const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)'];
-                                styleProp = colors[cluster % colors.length];
-                                const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor);
-                                // override the cluster color with an explicitly set color on a non-background document.  then override that with an explicitly set color on a background document
-                                set?.forEach(s => {
-                                    styleProp = StrCast(s.backgroundColor);
-                                });
+                                const palette = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)'];
+                                // override palette cluster color with an explicitly set cluster doc color
+                                return this._clusterSets[cluster]?.reduce((b, s) => StrCast(s.backgroundColor, b), palette[cluster % palette.length]);
                             }
                         }
                     }
@@ -208,7 +202,7 @@ export class CollectionFreeFormClusters {
                     break;
                 default:
             }
-        return styleProp;
+        return this.viewStyleProvider?.(doc, props, property); // bcz: check 'props'  used to be renderDepth + 1
     };
 
     tryToSelect = (addToSel: boolean) => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 53493a968..cc195385b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -404,38 +404,40 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const fromScreenXf = NumCast(refDoc.z) ? this.ScreenToLocalBoxXf() : this.screenToFreeformContentsXf;
         const [xpo, ypo] = fromScreenXf.transformPoint(de.x, de.y);
         const [x, y] = [xpo - docDragData.offset[0], ypo - docDragData.offset[1]];
-        const zsorted = this.childLayoutPairs
-            .map(pair => pair.layout)
-            .slice()
-            .sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
-        zsorted.forEach((doc, index) => {
-            doc.zIndex = doc.stroke_isInkMask ? 5000 : index + 1;
-        });
-        const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000));
-        const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)];
-
-        docDragData.droppedDocuments.forEach((d, i) => {
-            const layoutDoc = Doc.Layout(d);
-            const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], fromScreenXf.Rotate);
-            if (this.Document._currentFrame !== undefined) {
-                CollectionFreeFormDocumentView.setupKeyframes([d], NumCast(this.Document._currentFrame), false);
-                const pvals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000)); // get filled in values (uses defaults when not value is specified) for position
-                const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000), false); // get non-default values for everything else
-                vals.x = NumCast(pvals.x) + delta.x;
-                vals.y = NumCast(pvals.y) + delta.y;
-                CollectionFreeFormDocumentView.setValues(NumCast(this.Document._currentFrame), d, vals);
-            } else {
-                d.x = NumCast(d.x) + delta.x;
-                d.y = NumCast(d.y) + delta.y;
-            }
-            d._layout_modificationDate = new DateField();
-            const nd = [Doc.NativeWidth(layoutDoc), Doc.NativeHeight(layoutDoc)];
-            layoutDoc._width = NumCast(layoutDoc._width, 300);
-            layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? (nd[1] / nd[0]) * NumCast(layoutDoc._width) : 300);
-            !d._keepZWhenDragged && (d.zIndex = zsorted.length + 1 + i); // bringToFront
+        runInAction(() => {
+            // needs to be in action to avoid having each edit trigger a freeform layout engine recompute - this triggers just one for each document at the end
+            const zsorted = this.childLayoutPairs
+                .map(pair => pair.layout) //
+                .sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
+            zsorted.forEach((doc, index) => {
+                doc.zIndex = doc.stroke_isInkMask ? 5000 : index + 1;
+            });
+            const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000));
+            const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)];
+
+            docDragData.droppedDocuments.forEach((d, i) => {
+                const layoutDoc = Doc.Layout(d);
+                const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], fromScreenXf.Rotate);
+                if (this.Document._currentFrame !== undefined) {
+                    CollectionFreeFormDocumentView.setupKeyframes([d], NumCast(this.Document._currentFrame), false);
+                    const pvals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000)); // get filled in values (uses defaults when not value is specified) for position
+                    const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000), false); // get non-default values for everything else
+                    vals.x = NumCast(pvals.x) + delta.x;
+                    vals.y = NumCast(pvals.y) + delta.y;
+                    CollectionFreeFormDocumentView.setValues(NumCast(this.Document._currentFrame), d, vals);
+                } else {
+                    d.x = NumCast(d.x) + delta.x;
+                    d.y = NumCast(d.y) + delta.y;
+                }
+                d._layout_modificationDate = new DateField();
+                const nd = [Doc.NativeWidth(layoutDoc), Doc.NativeHeight(layoutDoc)];
+                layoutDoc._width = NumCast(layoutDoc._width, 300);
+                layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? (nd[1] / nd[0]) * NumCast(layoutDoc._width) : 300);
+                !d._keepZWhenDragged && (d.zIndex = zsorted.length + 1 + i); // bringToFront
+            });
+            (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this._clusters.addDocuments(docDragData.droppedDocuments);
         });
 
-        (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this._clusters.addDocuments(docDragData.droppedDocuments);
         return true;
     }
 
@@ -1128,16 +1130,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const rotation = Cast(_rotation,'number',
             !this.layoutDoc._rotation_jitter ? null
                 : NumCast(this.layoutDoc._rotation_jitter) * random(-1, 1, NumCast(x), NumCast(y)) );
-        const childProps = { ...this._props, fieldKey: '', styleProvider: this._clusters.styleProvider };
         return {
             x: isNaN(NumCast(x)) ? 0 : NumCast(x),
             y: isNaN(NumCast(y)) ? 0 : NumCast(y),
             z: Cast(z, 'number'),
             autoDim,
             rotation,
-            color: Cast(color, 'string') ? StrCast(color) : this._clusters.styleProvider(childDoc, childProps, StyleProp.Color),
-            backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this._clusters.styleProvider(childDoc, childProps, StyleProp.BackgroundColor),
-            opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this._clusters.styleProvider?.(childDoc, childProps, StyleProp.Opacity),
+            color: Cast(color, 'string', null),
+            backgroundColor: Cast(backgroundColor, 'string', null),
+            opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number', null),
             zIndex: Cast(zIndex, 'number'),
             width: _width,
             height: _height,
@@ -1209,6 +1210,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     doFreeformLayout(poolData: Map<string, PoolData>) {
+        this._clusters.initLayout();
         this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => poolData.set(pair.layout[Id], this.getCalculatedPositions(pair)));
         return [] as ViewDefResult[];
     }
@@ -1237,7 +1239,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 })
             );
 
-        this._clusters.initLayout();
         return elements;
     };
 
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 62c4cc61a..d65e2d65e 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -126,15 +126,16 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     PanelHeight = ()       => this._props.autoDim ? this._props.PanelHeight?.() : this.Height; // prettier-ignore
 
     styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => {
-        if (doc === this.layoutDoc) {
+        const overrideProp = () => {
             switch (property.split(':')[0]) {
-                case StyleProp.Opacity:         return this.Opacity; // only change the opacity for this specific document, not its children
+                case StyleProp.Opacity:         return this.Opacity; 
                 case StyleProp.BackgroundColor: return this.BackgroundColor;
                 case StyleProp.Color:           return this.Color;
-                default:
-            } // prettier-ignore
-        }
-        return this._props.styleProvider?.(doc, props, property);
+                default:                        return undefined;
+            }}; // prettier-ignore
+
+        // only override values for this specific document, not any children
+        return (doc === this.layoutDoc && overrideProp()) ?? this._props.styleProvider?.(doc, props, property);
     };
 
     public static getValues(doc: Doc, time: number, fillIn: boolean = true) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f56c8288e..66540678d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1383,6 +1383,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     PanelHeight = () => this.panelHeight;
     NativeDimScaling = () => this.nativeScaling;
     hideLinkCount = () => !!this.hideLinkButton;
+    isHovering = () => this._isHovering;
     selfView = () => this;
     /**
      * @returns Transform to the document view (in the coordinate system of whatever contains the DocumentView)
@@ -1449,6 +1450,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
                         <DocumentViewInternal
                             {...this._props}
                             parent={undefined}
+                            isHovering={this.isHovering}
                             fieldKey={this.LayoutFieldKey}
                             DataTransition={this.DataTransition}
                             DocumentView={this.selfView}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 3f351a072..818d26956 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -91,6 +91,7 @@ export interface FieldViewProps extends FieldViewSharedProps {
     docViewPath: () => DocumentView[];
     setHeight?: (height: number) => void;
     NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal
+    isHovering?: () => boolean;
 
     // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React)
     // See currentUserUtils headerTemplate for examples of creating text boxes from html which set some of these fields
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 93cb67491..de9ba87d3 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1990,9 +1990,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
     }
     @computed get _fieldKey() {
         const usePath = this._props.ignoreUsePath ? '' : StrCast(this.layoutDoc[`${this._props.fieldKey}_usePath`]);
-        return this._props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering || this._props.isContentActive()) ? `_${usePath.replace(':hover', '')}` : '');
+        return this._props.fieldKey + (usePath && (!usePath.includes(':hover') || this._props.isHovering?.() || this._props.isContentActive()) ? `_${usePath.replace(':hover', '')}` : '');
     }
-    @observable _isHovering = false;
     onPassiveWheel = (e: WheelEvent) => {
         if (e.clientX > this.ProseRef!.getBoundingClientRect().right) {
             return;
@@ -2038,11 +2037,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         return styleFromLayout?.height === '0px' ? null : (
             <div
                 className="formattedTextBox"
-                onPointerEnter={action(() => {
-                    this._isHovering = true;
-                    this.layoutDoc[`_${this._props.fieldKey}_usePath`] && (this.Document.isHovering = true);
-                })}
-                onPointerLeave={action(() => { this.Document.isHovering = this._isHovering = false; })} // prettier-ignore
                 ref={r => {
                     this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
                     this._oldWheel = r;
-- 
cgit v1.2.3-70-g09d2


From 5ff0bef5d3c4825aa7210a26c98aae3b24f4a835 Mon Sep 17 00:00:00 2001
From: alyssaf16 <alyssa_feinberg@brown.edu>
Date: Fri, 17 May 2024 13:18:40 -0400
Subject: chatcards, quizcards, and ai flashcards

---
 src/client/apis/gpt/GPT.ts                         |  36 ++++--
 src/client/util/CurrentUserUtils.ts                |   2 -
 .../views/collections/CollectionCarouselView.tsx   |  79 ++++++++++--
 src/client/views/collections/CollectionView.tsx    |  15 ++-
 src/client/views/nodes/ComparisonBox.scss          | 143 +++++++++++++++++++++
 src/client/views/nodes/ComparisonBox.tsx           | 109 +++++++++++-----
 src/client/views/nodes/DocumentView.tsx            |  24 +++-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   5 +-
 src/client/views/pdf/AnchorMenu.tsx                |  27 ++--
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |  45 -------
 src/client/views/pdf/PDFViewer.tsx                 |   4 +-
 11 files changed, 367 insertions(+), 122 deletions(-)

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

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 2757fc830..6600ddab2 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -1,10 +1,13 @@
 import { ClientOptions, OpenAI } from 'openai';
+import { ChatCompletionMessageParam } from 'openai/resources';
 
 enum GPTCallType {
     SUMMARY = 'summary',
     COMPLETION = 'completion',
     EDIT = 'edit',
+    CHATCARD = 'chatcard',
     FLASHCARD = 'flashcard',
+    QUIZ = 'quiz',
 }
 
 type GPTCallOpts = {
@@ -15,10 +18,17 @@ type GPTCallOpts = {
 };
 
 const callTypeMap: { [type: string]: GPTCallOpts } = {
-    summary: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' },
-    edit: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' },
-    flashcard: { model: 'gpt-3.5-turbo-instruct', maxTokens: 512, temp: 0.5, prompt: 'Make flashcards out of this text with questions and answers: ' },
-    completion: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: '' },
+    summary: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' },
+    edit: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' },
+    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: '' },
+    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.' },
+    quiz: {
+        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',
+    },
 };
 
 /**
@@ -28,7 +38,8 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
  * @returns AI Output
  */
 const gptAPICall = async (inputText: string, callType: GPTCallType) => {
-    if (callType === GPTCallType.SUMMARY || callType == GPTCallType.FLASHCARD) inputText += '.';
+    if (!inputText) return 'Please provide a response.';
+    if (callType === GPTCallType.SUMMARY || callType == GPTCallType.FLASHCARD || GPTCallType.QUIZ) inputText += '.';
     const opts: GPTCallOpts = callTypeMap[callType];
     try {
         const configuration: ClientOptions = {
@@ -36,13 +47,20 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => {
             dangerouslyAllowBrowser: true,
         };
         const openai = new OpenAI(configuration);
-        const response = await openai.completions.create({
+
+        let messages: ChatCompletionMessageParam[] = [
+            { role: 'system', content: opts.prompt },
+            { role: 'user', content: inputText },
+        ];
+
+        const response = await openai.chat.completions.create({
             model: opts.model,
-            max_tokens: opts.maxTokens,
+            messages: messages,
             temperature: opts.temp,
-            prompt: `${opts.prompt}${inputText}`,
+            max_tokens: opts.maxTokens,
         });
-        return response.choices[0].text;
+
+        return response.choices[0].message.content;
     } catch (err) {
         console.log(err);
         return 'Error connecting with API.';
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index ab0315ba1..b6ba3f187 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -342,8 +342,6 @@ pie title Minerals in my tap water
               }[] = [
             {key: "Note",       creator: opts => Docs.Create.TextDocument("", opts),        opts: { _width: 200, _layout_autoHeight: true }},
             {key: "Flashcard",       creator: opts => Docs.Create.ComparisonDocument("", opts), opts: { _layout_isFlashcard: true, _width: 300, _height: 300}},
-            // {key: "Flashcard",  creator: Docs.Create.ComparisonDocument("", opts),           opts: { _layout_isFlashcard: true, _width: 300, _height: 300 }},
-            //{key: "Flashcard",  creator: opts => Docs.Create.TextDocument("", opts),        opts: { _width: 200, _layout_autoHeight: true, _layout_enableAltContentUI: true}},
             {key: "Equation",   creator: opts => Docs.Create.EquationDocument("",opts),     opts: { _width: 300, _height: 35, }},
             {key: "Noteboard",  creator: opts => Docs.Create.NoteTakingDocument([], opts),  opts: { _width: 250, _height: 200, _layout_fitWidth: true}},
             {key: "Simulation", creator: opts => Docs.Create.SimulationDocument(opts),      opts: { _width: 300, _height: 300, }},
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 7f5176123..d45b0822b 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -33,29 +33,53 @@ export class CollectionCarouselView extends CollectionSubView() {
         }
     };
 
+    /**
+     * Goes to the next flashcard in the stack and filters
+     * based on the the currently selected option.
+     */
     advance = (e: React.MouseEvent) => {
         e.stopPropagation();
         this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 1) % this.childLayoutPairs.length;
         var startInd = this.layoutDoc._carousel_index;
 
         // if the star filter is selected
-        if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'star') {
-            // go to a new index that is starred, skip the ones that aren't
+        if (this.layoutDoc[`filterOp`] == 'star') {
+            // go to a flashcard that is starred, skip the ones that aren't
             while (!this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_star`] && (startInd + 1) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) {
                 startInd = (startInd + 1) % this.childLayoutPairs.length;
             }
             this.layoutDoc._carousel_index = startInd;
+            // if there aren't any starred, show all cards
+            if (!this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_star`]) {
+                this.layoutDoc[`filterOp`] = 'all';
+            }
         }
 
         // if the practice filter is selected
-        if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice') {
+        if (this.layoutDoc[`filterOp`] == 'practice') {
             // go to a new index that is missed, skip the ones that are correct
             while (this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] == 'correct' && (startInd + 1) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) {
                 startInd = (startInd + 1) % this.childLayoutPairs.length;
             }
             this.layoutDoc._carousel_index = startInd;
+
+            // if the user has gone through all of the cards and gotten them all correct, show all cards and exit practice mode
+            if (this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] == 'correct') {
+                this.layoutDoc[`filterOp`] = 'all';
+
+                // set all the cards to missed
+                for (var i = 0; i < this.childLayoutPairs.length; i++) {
+                    const curDoc = this.childLayoutPairs?.[NumCast(i)];
+                    curDoc.layout[`${this.fieldKey}_missed`] = undefined;
+                }
+            }
         }
     };
+
+    /**
+     * Goes to the previous flashcard in the stack and filters
+     * based on the the currently selected option.
+     */
     goback = (e: React.MouseEvent) => {
         e.stopPropagation();
         this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length;
@@ -63,38 +87,58 @@ export class CollectionCarouselView extends CollectionSubView() {
         var startInd = this.layoutDoc._carousel_index;
 
         // if the star filter is selected
-        if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'star') {
+        if (this.layoutDoc[`filterOp`] == 'star') {
             // go to a new index that is starred, skip the ones that aren't
             while (!this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_star`] && (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) {
                 startInd = (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length;
             }
             this.layoutDoc._carousel_index = startInd;
+            // if there aren't any starred, show all cards
+            if (!this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_star`]) {
+                this.layoutDoc[`filterOp`] = 'all';
+            }
         }
 
         // if the practice filter is selected
-        if (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice') {
+        if (this.layoutDoc[`filterOp`] == 'practice') {
             // go to a new index that is missed, skip the ones that are correct
             while (this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] == 'correct' && (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length != this.layoutDoc._carousel_index) {
                 startInd = (startInd - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length;
             }
+
             this.layoutDoc._carousel_index = startInd;
+
+            // See all flashcards when finish going through practice mode and set all of the flashcards back to
+            if (this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] == 'correct') {
+                this.layoutDoc[`filterOp`] = 'all';
+
+                for (var i = 0; i < this.childLayoutPairs.length; i++) {
+                    const curDoc = this.childLayoutPairs?.[NumCast(i)];
+                    curDoc.layout[`${this.fieldKey}_missed`] = undefined;
+                }
+            }
         }
     };
 
+    /*
+     * Stars the document when the star button is pressed.
+     */
     star = (e: React.MouseEvent) => {
         e.stopPropagation();
-        // stars the document when the button is pressed
         const curDoc = this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)];
+        if (!curDoc) return;
         if (curDoc.layout[`${this.fieldKey}_star`] == undefined) curDoc.layout[`${this.fieldKey}_star`] = true;
         else curDoc.layout[`${this.fieldKey}_star`] = !curDoc.layout[`${this.fieldKey}_star`];
     };
 
+    /*
+     * Sets a flashcard to either missed or correct depending on if they got the question right in practice mode.
+     */
     missed = (e: React.MouseEvent, val: string) => {
         e.stopPropagation();
         const curDoc = this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)];
         curDoc.layout[`${this.fieldKey}_missed`] = val;
-        this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 1) % this.childLayoutPairs.length;
-        this.advance;
+        this.advance(e);
     };
 
     captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<FieldViewProps>, property: string): any => {
@@ -154,6 +198,7 @@ export class CollectionCarouselView extends CollectionSubView() {
         );
     }
     @computed get buttons() {
+        if (!this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)]) return;
         return (
             <>
                 <div key="back" className="carouselView-back" onClick={this.goback}>
@@ -165,10 +210,10 @@ export class CollectionCarouselView extends CollectionSubView() {
                 <div key="star" className="carouselView-star" onClick={this.star}>
                     <FontAwesomeIcon icon={'star'} color={this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)].layout[`${this.fieldKey}_star`] ? 'yellow' : 'gray'} size={'1x'} />
                 </div>
-                <div key="remove" className="carouselView-remove" onClick={e => this.missed(e, 'missed')} style={{ visibility: this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice' ? 'visible' : 'hidden' }}>
+                <div key="remove" className="carouselView-remove" onClick={e => this.missed(e, 'missed')} style={{ visibility: this.layoutDoc[`filterOp`] == 'practice' ? 'visible' : 'hidden' }}>
                     <FontAwesomeIcon icon={'xmark'} color={'red'} size={'1x'} />
                 </div>
-                <div key="check" className="carouselView-check" onClick={e => this.missed(e, 'correct')} style={{ visibility: this.layoutDoc[`_${this._props.fieldKey}_filterOp`] == 'practice' ? 'visible' : 'hidden' }}>
+                <div key="check" className="carouselView-check" onClick={e => this.missed(e, 'correct')} style={{ visibility: this.layoutDoc[`filterOp`] == 'practice' ? 'visible' : 'hidden' }}>
                     <FontAwesomeIcon icon={'check'} color={'green'} size={'1x'} />
                 </div>
             </>
@@ -185,6 +230,18 @@ export class CollectionCarouselView extends CollectionSubView() {
                     color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color),
                 }}>
                 {this.content}
+                {/* Displays a message to the user to add more flashcards if they are in practice mode and no flashcards are there. */}
+                <p
+                    style={{
+                        display: !this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)] && this.layoutDoc[`filterOp`] == 'practice' ? 'flex' : 'none',
+                        justifyContent: 'center',
+                        alignItems: 'center',
+                        height: '100%',
+                        zIndex: '-1',
+                    }}>
+                    Add flashcards!
+                </p>
+                {/* Displays a message to the user that a flashcard was recently missed if they had previously gotten it wrong. */}
                 <p
                     style={{
                         color: 'red',
@@ -192,7 +249,7 @@ export class CollectionCarouselView extends CollectionSubView() {
                         position: 'relative',
                         left: '10px',
                         top: '10px',
-                        visibility: this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)].layout[`${this.fieldKey}_missed`] == 'missed' ? 'visible' : 'hidden',
+                        display: this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)] ? (this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)].layout[`${this.fieldKey}_missed`] == 'missed' ? 'block' : 'none') : 'none',
                     }}>
                     Recently missed!
                 </p>
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 168176edf..2f0f2a773 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -80,6 +80,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
         super(props);
         makeObservable(this);
         this._annotationKeySuffix = returnEmptyString;
+        this.layoutDoc[`filterOp`] = 'all';
     }
 
     componentDidMount() {
@@ -176,14 +177,18 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
                     return newRendition;
                 });
 
+            // creates menu option for flashcard filters
             const revealOptions = cm.findByDescription('Filter Flashcards');
             const revealItems: ContextMenuProps[] = revealOptions && 'subitems' in revealOptions ? revealOptions.subitems : [];
-            revealItems.push({ description: 'All', event: () => (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] = 'all'), icon: 'eye-slash' });
-            revealItems.push({ description: 'Star', event: () => (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] = 'star'), icon: 'hand-point-up' });
-            revealItems.push({ description: 'Practice Mode', event: () => (this.layoutDoc[`_${this._props.fieldKey}_filterOp`] = 'practice'), icon: 'rotate' });
+            revealItems.push({ description: 'All', event: () => (this.layoutDoc[`filterOp`] = 'all'), icon: 'layer-group' });
+            revealItems.push({ description: 'Star', event: () => (this.layoutDoc[`filterOp`] = 'star'), icon: 'star' });
+            revealItems.push({ description: 'Practice Mode', event: () => (this.layoutDoc[`filterOp`] = 'practice'), icon: 'check' });
+            revealItems.push({ description: 'Quiz Cards', event: () => (this.layoutDoc[`filterOp`] = 'quiz'), icon: 'pencil' });
 
-            //revealItems.push({ description: 'Bring to Front', event: () => SelectionManager.Views.forEach(dv => dv._props.bringToFront?.(dv.Document, false)), icon: 'arrow-up' });
-            !revealOptions && cm.addItem({ description: 'Filter Flashcards', addDivider: false, noexpand: true, subitems: revealItems, icon: 'layer-group' });
+            // only show the filter options if it is a collection of type Carousel view
+            if (this.Document?._type_collection === CollectionViewType.Carousel) {
+                !revealOptions && cm.addItem({ description: 'Filter Flashcards', addDivider: false, noexpand: true, subitems: revealItems, icon: 'layer-group' });
+            }
 
             const options = cm.findByDescription('Options...');
             const optionItems = options && 'subitems' in options ? options.subitems : [];
diff --git a/src/client/views/nodes/ComparisonBox.scss b/src/client/views/nodes/ComparisonBox.scss
index 39c864b2b..093b9c004 100644
--- a/src/client/views/nodes/ComparisonBox.scss
+++ b/src/client/views/nodes/ComparisonBox.scss
@@ -1,4 +1,5 @@
 .comparisonBox-interactive,
+.quiz-card,
 .comparisonBox {
     border-radius: inherit;
     width: 100%;
@@ -7,6 +8,40 @@
     z-index: 0;
     pointer-events: none;
     display: flex;
+    p {
+        color: rgb(0, 0, 0);
+        -webkit-text-stroke-color: black;
+        -webkit-text-stroke-width: 0.2px;
+    }
+
+    .input-box {
+        position: relative;
+        padding: 10px;
+        width: 100%;
+        height: 100%;
+        display: flex;
+    }
+
+    .submit-button {
+        position: relative;
+        padding-bottom: 10px;
+        padding-left: 5px;
+        padding-right: 5px;
+        width: 100%;
+        height: 15%;
+        display: flex;
+
+        button {
+            flex: 1;
+            position: relative;
+        }
+    }
+    textarea {
+        flex: 1;
+        padding: 10px;
+        position: relative;
+        resize: none;
+    }
 
     .clip-div {
         position: absolute;
@@ -95,4 +130,112 @@
             display: flex;
         }
     }
+    // .input-box {
+    //     position: relative;
+    //     padding: 10px;
+    // }
+    // input[type='text'] {
+    //     flex: 1;
+    //     position: relative;
+    //     margin-right: 10px;
+    //     width: 100px;
+    // }
+}
+
+// .quiz-card {
+//     position: relative;
+
+//     input[type='text'] {
+//         flex: 1;
+//         position: relative;
+//         margin-right: 10px;
+//         width: 100px;
+//     }
+// }
+.QuizCard {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+
+    .QuizCard-wrapper {
+        width: 100%;
+        height: 100%;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        .QuizCardBox {
+            /* existing code */
+
+            .DIYNodeBox-iframe {
+                height: 100%;
+                width: 100%;
+                border: none;
+            }
+        }
+
+        .search-bar {
+            display: flex;
+            justify-content: left;
+            align-items: left;
+            width: 100%;
+            padding: 10px;
+
+            input[type='text'] {
+                flex: 1;
+                margin-right: 10px;
+            }
+
+            button {
+                padding: 5px 10px;
+            }
+        }
+
+        .content {
+            flex: 1;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            width: 100%;
+            height: 100%;
+            .diagramBox {
+                flex: 1;
+                display: flex;
+                justify-content: center;
+                align-items: center;
+                width: 100%;
+                height: 100%;
+                svg {
+                    flex: 1;
+                    display: flex;
+                    justify-content: center;
+                    align-items: center;
+                    width: 100%;
+                    height: 100%;
+                }
+            }
+        }
+
+        .loading-circle {
+            position: relative;
+            width: 50px;
+            height: 50px;
+            border-radius: 50%;
+            border: 3px solid #ccc;
+            border-top-color: #333;
+            animation: spin 1s infinite linear;
+        }
+
+        @keyframes spin {
+            0% {
+                transform: rotate(0deg);
+            }
+            100% {
+                transform: rotate(360deg);
+            }
+        }
+    }
 }
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 19fccce8a..9fd4d696a 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -2,7 +2,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents, unimplementedFunction } from '../../../Utils';
 import { Doc, Opt, DocListCast } from '../../../fields/Doc';
 import { DocCast, NumCast, RTFCast, StrCast } from '../../../fields/Types';
 import { DocUtils, Docs } from '../../documents/Documents';
@@ -34,6 +34,17 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         makeObservable(this);
     }
 
+    @observable inputValue = '';
+    @observable outputValue = '';
+    @observable loading = false;
+    @observable errorMessage = '';
+    @observable outputMessage = '';
+
+    @action handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
+        this.inputValue = e.target.value;
+        console.log(this.inputValue);
+    };
+
     @observable _animating = '';
 
     @computed get clipWidth() {
@@ -160,7 +171,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
             e => {
                 const de = new DragManager.DocumentDragData([DocCast(this.dataDoc[which])], dropActionType.move);
                 de.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
-                    //this.clearDoc(which);
                     return addDocument(doc);
                 };
                 de.canEmbed = true;
@@ -181,15 +191,24 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     remDoc2 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_2'), true);
     _closeRef = React.createRef<HTMLDivElement>();
 
+    /**
+     * Flips a flashcard to the alternate side for the user to view.
+     */
     flipFlashcard = () => {
         const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
-
         this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : undefined;
     };
+
+    /**
+     * Changes the view option to hover for a flashcard.
+     */
     hoverFlip = (side: string | undefined) => {
         if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hover') this.layoutDoc[`_${this._props.fieldKey}_usePath`] = side;
     };
 
+    /**
+     * Creates the button used to flip the flashcards.
+     */
     @computed get overlayAlternateIcon() {
         const usepath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
         return (
@@ -203,21 +222,10 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                                 this.flipFlashcard();
                                 console.log('Print Front of cards: ' + RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text);
                                 console.log('Print Back of cards: ' + RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text);
-
-                                //const queryText = RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text;
-                                // DocCast(this.dataDoc[this.fieldKey + '_1'])[DocData].text = 'hello';
-                                // const mes = gptAPICall(queryText, GPTCallType.COMPLETION).trim();
-                                // const res = await gptAPICall(queryText, GPTCallType.COMPLETION)
-                                // console.log(res);
-                                //.then(value => (DocCast(this.dataDoc[this.fieldKey + '_1']).text = value.trim()));
-                                if (usepath !== 'alternate') {
-                                    this.askGPT();
-                                }
                             }
                         })
                     }
                     style={{
-                        //display: this._props.isContentActive() && !SnappingManager.IsDragging ? 'flex' : 'none',
                         background: usepath === 'alternate' ? 'white' : 'black',
                         color: usepath === 'alternate' ? 'black' : 'white',
                     }}>
@@ -227,15 +235,34 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         );
     }
 
+    @action handleRenderGPTClick = () => {
+        // Call the GPT model and get the output
+        this.layoutDoc[`_${this._props.fieldKey}_usePath`] = 'alternate';
+        this.outputValue = '';
+        if (this.inputValue) this.askGPT();
+    };
+
+    @action handleRenderClick = () => {
+        // Call the GPT model and get the output
+        this.layoutDoc[`_${this._props.fieldKey}_usePath`] = undefined;
+    };
+
+    /**
+     * Calls the GPT model to create QuizCards. Evaluates how similar the user's response is to the alternate
+     * side of the flashcard.
+     */
     askGPT = async (): Promise<string | undefined> => {
-        const queryText = RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text;
+        const questionText = 'Question: ' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text);
+        const rubricText = ' Rubric: ' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text);
+        const queryText = questionText + ' UserAnswer: ' + this.inputValue + '. ' + rubricText;
+
         try {
-            let res = await gptAPICall(StrCast(queryText), GPTCallType.COMPLETION);
+            let res = await gptAPICall(queryText, GPTCallType.QUIZ);
             if (!res) {
                 console.error('GPT call failed');
                 return;
             }
-            DocCast(this.dataDoc[this.fieldKey + '_0'])[DocData].text = res;
+            this.outputValue = res;
             console.log(res);
         } catch (err) {
             console.error('GPT call failed');
@@ -292,8 +319,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
             );
         };
         const displayBox = (which: string, index: number, cover: number) => {
-            // if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hide/reveal') this.layoutDoc[this.clipHeightKey] = 100;
-            // else this.layoutDoc.height = 300;
             return (
                 <div className={`${index === 0 ? 'before' : 'after'}Box-cont`} key={which} style={{ width: this._props.PanelWidth() }} onPointerDown={e => this.registerSliding(e, cover)} ref={ele => this.createDropTarget(ele, which, index)}>
                     {displayDoc(which)}
@@ -326,36 +351,54 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
 
         if (this.Document._layout_isFlashcard) {
             const side = this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 1 : 0;
-            // add text box when first created
 
+            // add text box to each side when comparison box is first created
             if (!(this.dataDoc[this.fieldKey + '_0'] || this.dataDoc[this.fieldKey + '_0'] == 'empty')) {
                 const dataSplit = StrCast(this.dataDoc.data).split('Answer');
                 const newDoc = Docs.Create.TextDocument(dataSplit[1]);
+                // if there is text from the pdf ai cards, put the question on the front side.
                 newDoc[DocData].text = dataSplit[1];
                 this.addDoc(newDoc, this.fieldKey + '_0');
             }
             if (!(this.dataDoc[this.fieldKey + '_1'] || this.dataDoc[this.fieldKey + '_1'] == 'empty')) {
                 const dataSplit = StrCast(this.dataDoc.data).split('Answer');
                 const newDoc = Docs.Create.TextDocument(dataSplit[0]);
-                newDoc[DocData].text = 'placeholder...';
+                // if there is text from the pdf ai cards, put the answer on the alternate side.
+                newDoc[DocData].text = dataSplit[0];
                 this.addDoc(newDoc, this.fieldKey + '_1');
             }
 
-            if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hide/reveal') {
-                {
-                    return (
-                        <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}`} style={{ display: 'flex', flexDirection: 'column' }}>
-                            {displayBox(`${this.fieldKey}_0`, side, this._props.PanelHeight() - 3)}
-                            {displayBox(`${this.fieldKey}_1`, 1, this._props.PanelHeight() - 3)}
+            // render the QuizCards
+            if (DocCast(this.Document.embedContainer) && DocCast(this.Document.embedContainer)[`filterOp`] == 'quiz') {
+                return (
+                    <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}`} style={{ display: 'flex', flexDirection: 'column' }}>
+                        <p style={{ color: 'white', padding: 10 }}>{StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text)}</p>
+                        {/* {StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text)} */}
+                        <div className={'input-box'}>
+                            {
+                                <textarea
+                                    value={this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? this.outputValue : this.inputValue}
+                                    onChange={this.handleInputChange}
+                                    readOnly={this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate'}
+                                />
+                            }
                         </div>
-                    );
-                }
-            } else {
+                        <div className="submit-button" style={{ display: this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 'none' : 'flex' }}>
+                            <button onClick={this.handleRenderGPTClick}>Submit</button>
+                        </div>
+                        <div className="submit-button" style={{ display: this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 'flex' : 'none' }}>
+                            <button onClick={this.handleRenderClick}>Edit Your Response</button>
+                        </div>
+                    </div>
+                );
+            }
+
+            // render a normal flashcard when not a QuizCard
+            else {
                 return (
                     <div
                         className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}`} /* change className to easily disable/enable pointer events in CSS */
-                        // style={{ display: 'flex', flexDirection: 'column' }}
-                        // style={{ position: 'absolute', top: '0px' }}
+                        style={{ display: 'flex', flexDirection: 'column' }}
                         onMouseEnter={() => {
                             this.hoverFlip('alternate');
                         }}
@@ -363,12 +406,12 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                             this.hoverFlip(undefined);
                         }}>
                         {displayBox(`${this.fieldKey}_${side === 0 ? 1 : 0}`, side, this._props.PanelWidth() - 3)}
-
                         {this.overlayAlternateIcon}
                     </div>
                 );
             }
         } else {
+            // render a comparison box that compares items side by side
             return (
                 <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}>
                     {displayBox(`${this.fieldKey}_2`, 1, this._props.PanelWidth() - 3)}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 26e5ec622..e1b501c5a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -48,6 +48,9 @@ import { LinkAnchorBox } from './LinkAnchorBox';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import { PresEffect, PresEffectDirection } from './trails';
 import { FieldsDropdown } from '../FieldsDropdown';
+import { RTFCast } from '../../../fields/Types';
+import { gptAPICall, GPTCallType } from '../../apis/gpt/GPT';
+
 interface Window {
     MediaRecorder: MediaRecorder;
 }
@@ -517,6 +520,21 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
         input.click();
     };
 
+    askGPT = async (): Promise<string | undefined> => {
+        const queryText = RTFCast(DocCast(this.dataDoc[this.props.fieldKey + '_1']).text)?.Text;
+        try {
+            let res = await gptAPICall('Question: ' + StrCast(queryText), GPTCallType.CHATCARD);
+            if (!res) {
+                console.error('GPT call failed');
+                return;
+            }
+            DocCast(this.dataDoc[this.props.fieldKey + '_0'])[DocData].text = res;
+            console.log(res);
+        } catch (err) {
+            console.error('GPT call failed');
+        }
+    };
+
     onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => {
         if (e && this.layoutDoc.layout_hideContextMenu && Doc.noviceMode) {
             e.preventDefault();
@@ -569,17 +587,19 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                 appearanceItems.splice(0, 0, { description: 'Open in Lightbox', event: () => LightboxView.Instance.SetLightboxDoc(this.Document), icon: 'external-link-alt' });
             }
             appearanceItems.push({ description: 'Pin', event: () => this._props.pinToPres(this.Document, {}), icon: 'eye' });
+            if (this.Document._layout_isFlashcard) {
+                appearanceItems.push({ description: 'Create ChatCard', event: () => this.askGPT(), icon: 'id-card' });
+            }
 
             !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template   ', event: () => this._props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
             !appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'compass' });
 
+            // creates menu for the user to select how to reveal the flashcards
             if (this.Document._layout_isFlashcard) {
                 const revealOptions = cm.findByDescription('Reveal Options');
                 const revealItems: ContextMenuProps[] = revealOptions && 'subitems' in revealOptions ? revealOptions.subitems : [];
                 revealItems.push({ description: 'Hover', event: () => (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'hover'), icon: 'hand-point-up' });
                 revealItems.push({ description: 'Flip', event: () => (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'flip'), icon: 'rotate' });
-                revealItems.push({ description: 'Hide & Reveal', event: () => (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'hide/reveal'), icon: 'eye-slash' });
-                //revealItems.push({ description: 'Bring to Front', event: () => SelectionManager.Views.forEach(dv => dv._props.bringToFront?.(dv.Document, false)), icon: 'arrow-up' });
                 !revealOptions && cm.addItem({ description: 'Reveal Options', addDivider: false, noexpand: true, subitems: revealItems, icon: 'layer-group' });
             }
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 4fce72cdc..3192ac537 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -178,6 +178,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     @observable
     private gptRes: string = '';
 
+    public makeAIFlashcards: () => void = unimplementedFunction;
+    public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined;
+
     public static PasteOnLoad: ClipboardEvent | undefined;
     private static SelectOnLoad: Doc | undefined;
     public static SetSelectOnLoad(doc: Doc) {
@@ -959,7 +962,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         const options = cm.findByDescription('Options...');
         const optionItems = options && 'subitems' in options ? options.subitems : [];
         optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' });
-        optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' });
+        optionItems.push({ description: `Ask GPT-3`, event: () => this.makeAIFlashcards(), icon: 'lightbulb' });
         this._props.renderDepth &&
             optionItems.push({
                 description: !this.Document._createDocOnCR ? 'Create New Doc on Carriage Return' : 'Allow Carriage Returns',
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 7cb6a20f4..a0c3cf487 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -76,7 +76,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
      * @param e pointer down event
      */
     gptSummarize = async (e: React.PointerEvent) => {
-        // move this logic to gptpopup, need to implement generate again
         GPTPopup.Instance.setVisible(true);
         GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY);
         GPTPopup.Instance.setLoading(true);
@@ -90,24 +89,28 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         GPTPopup.Instance.setLoading(false);
     };
 
+    /**
+     * Invokes the API with the selected text and stores it in the selected text.
+     * @param e pointer down event
+     */
     gptFlashcards = async (e: React.PointerEvent) => {
-        // move this logic to gptpopup, need to implement generate again
-        // GPTPopup.Instance.setVisible(true);
-        // GPTPopup.Instance.setMode(GPTPopupMode.FLASHCARD);
-        // GPTPopup.Instance.setLoading(true);
-
+        const queryText = this.selectedText;
         try {
-            const res = await gptAPICall(this.selectedText, GPTCallType.FLASHCARD);
-
+            const res = await gptAPICall(queryText, GPTCallType.FLASHCARD);
+            console.log(res);
             GPTPopup.Instance.setText(res || 'Something went wrong.');
-            this.transferToFlashcard(res);
+            this.transferToFlashcard(res || 'Something went wrong');
         } catch (err) {
             console.error(err);
         }
         GPTPopup.Instance.setLoading(false);
     };
 
+    /*
+     * Transfers the flashcard text generated by GPT on flashcards and creates a collection out them.
+     */
     transferToFlashcard = (text: string) => {
+        // put each question generated by GPT on the front of the flashcard
         const senArr = text.split('Question');
         const collectionArr: Doc[] = [];
         for (var i = 1; i < senArr.length; i++) {
@@ -116,10 +119,11 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             newDoc.text = senArr[i];
             collectionArr.push(newDoc);
         }
+        // create a new carousel collection of these flashcards
         const newCol = Docs.Create.CarouselDocument(collectionArr, {
-            _width: 200,
+            _width: 250,
             _height: 200,
-            _layout_fitWidth: true,
+            _layout_fitWidth: false,
             _layout_autoHeight: true,
         });
 
@@ -215,6 +219,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                             color={SettingsManager.userColor}
                         />
                     )}
+                    {/* Adds a create flashcards option to the anchor menu, which calls the gptFlashcard method. */}
                     <IconButton
                         tooltip="Create flashcards" //
                         onPointerDown={this.gptFlashcards}
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 32c1721ec..0b741c85e 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -144,15 +144,6 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
             _layout_autoHeight: true,
         });
         this.addDoc(newDoc, this.sidebarId);
-        // const arr = [newDoc];
-        // const newCol = Docs.Create.CarouselDocument(arr, {
-        //     _width: 200,
-        //     _height: 200,
-        //     _layout_fitWidth: true,
-        //     _layout_autoHeight: true,
-        // });
-        // this.addDoc(newDoc, this.sidebarId);
-        // this.addDoc(newCol, this.sidebarId);
         const anchor = AnchorMenu.Instance?.GetAnchor(undefined, false);
         if (anchor) {
             DocUtils.MakeLink(newDoc, anchor, {
@@ -161,25 +152,6 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         }
     };
 
-    // transferToFlashcard = () => {
-    //     const senArr = this.text.split('Question');
-    //     const collectionArr: Doc[] = [];
-    //     for (var i = 1; i < senArr.length; i++) {
-    //         console.log('Arr ' + i + ': ' + senArr[i]);
-    //         const newDoc = Docs.Create.ComparisonDocument(senArr[i], { _layout_isFlashcard: true, _width: 300, _height: 300 });
-    //         newDoc.text = senArr[i];
-    //         collectionArr.push(newDoc);
-    //     }
-    //     const newCol = Docs.Create.CarouselDocument(collectionArr, {
-    //         _width: 200,
-    //         _height: 200,
-    //         _layout_fitWidth: true,
-    //         _layout_autoHeight: true,
-    //     });
-    //     this.addDoc(newCol, this.sidebarId);
-    //     this.addToCollection?.(newCol);
-    // };
-
     /**
      * Transfers the image urls to actual image docs
      */
@@ -244,23 +216,6 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         );
     };
 
-    flashcardBox = () => {
-        // const textArr = this.text.split(".");
-        // textArr.forEach(function(sentence) {
-        //     console.log(sentence);
-
-        // });
-        // const newDoc = Docs.Create.ComparisonDocument();
-        // this.addToCollection?.(newDoc);
-        // // const newDoc = Docs.Create.ComparisonDocument();
-        // DocUtils.copyDragFactory(Doc.UserDoc().emptyFlashcard as Doc);
-        // // this.addToCollection?.(newDoc);
-        // // return newDoc;
-        // <ComparisonBox/>
-        const newDoc = Docs.Create.TextDocument('Hello there');
-        this.addDoc?.(newDoc);
-    };
-
     data = () => {
         return (
             <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index cecaf17ff..fe1ed8159 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -414,12 +414,10 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
             AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
         }
 
-        // Changing which document to add the annotation to (the currently selected PDF)
         GPTPopup.Instance.setSidebarId('data_sidebar');
         GPTPopup.Instance.addDoc = this._props.sidebarAddDoc;
+        // allows for creating collection
         AnchorMenu.Instance.addToCollection = this._props.DocumentView?.()._props.addDocument;
-        // const newDoc = Docs.Create.ComparisonDocument({ _layout_isFlashcard: true, _width: 300, _height: 300 });
-        // this.props.addDocument?.(newDoc);
     };
 
     @action
-- 
cgit v1.2.3-70-g09d2


From 526d556304d9f9a3cafded5a0c5d7b17e525d310 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 20 May 2024 15:45:58 -0400
Subject: reverted alt text text switch icon to include hover

---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 14 ++++----------
 1 file changed, 4 insertions(+), 10 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 542a68c3b..cf171c14b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1961,17 +1961,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             </div>
         );
     }
-    cycleAlternateText = () => {
-        if (this.layoutDoc._layout_enableAltContentUI) {
-            const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
-            this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : undefined;
-        }
+    cycleAlternateText = (skipHover?: boolean) => {
+        this.layoutDoc._layout_enableAltContentUI = true;
+        const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
+        this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' && !skipHover ? 'alternate:hover' : undefined;
     };
-    // cycleAlternateText = (skipHover?: boolean) => {
-    //     this.layoutDoc._layout_enableAltContentUI = true;
-    //     const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
-    //     this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' && !skipHover ? 'alternate:hover' : undefined;
-    // };
     @computed get overlayAlternateIcon() {
         const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
         return (
-- 
cgit v1.2.3-70-g09d2


From 46a2e250a40f30ff0bdfb4471f3905364e5ad803 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 20 May 2024 15:48:44 -0400
Subject: from last

---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index cf171c14b..9ccd95f24 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1971,21 +1971,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         return (
             <Tooltip
                 title={
-                    <div className="dash-tooltip">
-                        flip
-                        {/* 
                     <div className="dash-tooltip">
                         toggle (%/) between
                         <span style={{ color: usePath === undefined ? 'black' : undefined }}>
                             <em> primary </em>
-                        </span> and
+                        </span>
+                        and
                         <span style={{ color: usePath === 'alternate' ? 'black' : undefined }}>
                             <em>alternate </em>
                         </span>
                         and show
                         <span style={{ color: usePath === 'alternate:hover' ? 'black' : undefined }}>
                             <em> alternate on hover</em>
-                        </span> */}
+                        </span>
                     </div>
                 }>
                 <div
-- 
cgit v1.2.3-70-g09d2


From bb11e8c07f86b1ba0148f1e406a070c14e9abc7c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 20 May 2024 15:59:52 -0400
Subject: lint cleanup and some flashcard fixes

---
 src/client/apis/gpt/setup.ts                       |   1 +
 src/client/util/CurrentUserUtils.ts                |   3 +-
 src/client/views/nodes/ComparisonBox.tsx           | 162 +++++++++------------
 src/client/views/nodes/DocumentView.tsx            |   6 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   4 +-
 src/client/views/pdf/AnchorMenu.tsx                |  10 +-
 6 files changed, 80 insertions(+), 106 deletions(-)

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

diff --git a/src/client/apis/gpt/setup.ts b/src/client/apis/gpt/setup.ts
index 831c97eaa..7084f38bf 100644
--- a/src/client/apis/gpt/setup.ts
+++ b/src/client/apis/gpt/setup.ts
@@ -5,6 +5,7 @@ export enum GPTCallType {
     SUMMARY = 'summary',
     COMPLETION = 'completion',
     EDIT = 'edit',
+    FLASHCARD = 'flashcard',
 }
 
 export type GPTCallOpts = {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index b9c4d8b5f..e095bc659 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -354,8 +354,7 @@ pie title Minerals in my tap water
                           creator:(opts:DocumentOptions)=> any // how to create the empty thing if it doesn't exist
               }[] = [
             {key: "Note",       creator: opts => Docs.Create.TextDocument("", opts),        opts: { _width: 200, _layout_autoHeight: true }},
-            {key: "Flashcard", creator: opts => Docs.Create.ComparisonDocument("", opts), opts: { _layout_isFlashcard: true, _width: 300, _height: 300}},
-            // {key: "Flashcard",  creator: opts => Docs.Create.TextDocument("", opts),        opts: { _width: 200, _layout_autoHeight: true, _layout_enableAltContentUI: true}},
+            {key: "Flashcard",  creator: opts => Docs.Create.ComparisonDocument("", opts),  opts: { _layout_isFlashcard: true, _width: 300, _height: 300}},
             {key: "Image",      creator: opts => Docs.Create.ImageDocument("", opts),       opts: { _width: 400, _height:400 }},
             {key: "Equation",   creator: opts => Docs.Create.EquationDocument("",opts),     opts: { _width: 300, _height: 35, }},
             {key: "Noteboard",  creator: opts => Docs.Create.NoteTakingDocument([], opts),  opts: { _width: 250, _height: 200, _layout_fitWidth: true}},
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 84d14d4ef..5dd3cc9b0 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -170,9 +170,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
             e,
             moveEv => {
                 const de = new DragManager.DocumentDragData([DocCast(this.dataDoc[which])], dropActionType.move);
-                de.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
-                    return addDocument(doc);
-                };
+                de.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => addDocument(doc);
                 de.canEmbed = true;
                 DragManager.StartDocumentDrag([this._closeRef.current!], de, moveEv.clientX, moveEv.clientY);
                 return true;
@@ -235,7 +233,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
      * Changes the view option to hover for a flashcard.
      */
     hoverFlip = (side: string | undefined) => {
-        if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'hover') this.layoutDoc[`_${this._props.fieldKey}_usePath`] = side;
+        if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] === 'hover') this.layoutDoc[`_${this._props.fieldKey}_usePath`] = side;
     };
 
     /**
@@ -248,12 +246,12 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                 <div
                     className="formattedTextBox-alternateButton"
                     onPointerDown={e =>
-                        setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => {
+                        setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, () => {
                             console.log(this.layoutDoc[`_${this._props.fieldKey}_revealOp`]);
-                            if (!this.layoutDoc[`_${this._props.fieldKey}_revealOp`] || this.layoutDoc[`_${this._props.fieldKey}_revealOp`] == 'flip') {
+                            if (!this.layoutDoc[`_${this._props.fieldKey}_revealOp`] || this.layoutDoc[`_${this._props.fieldKey}_revealOp`] === 'flip') {
                                 this.flipFlashcard();
-                                console.log('Print Front of cards: ' + RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text);
-                                console.log('Print Back of cards: ' + RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text);
+                                console.log('Print Front of cards: ' + (RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text ?? ''));
+                                console.log('Print Back of cards: ' + (RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text ?? ''));
                             }
                         })
                     }
@@ -289,7 +287,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         const queryText = questionText + ' UserAnswer: ' + this.inputValue + '. ' + rubricText;
 
         try {
-            let res = await gptAPICall(queryText, GPTCallType.QUIZ);
+            const res = await gptAPICall(queryText, GPTCallType.QUIZ);
             if (!res) {
                 console.error('GPT call failed');
                 return;
@@ -302,19 +300,17 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
     };
 
     render() {
-        const clearButton = (which: string) => {
-            return (
-                <Tooltip title={<div className="dash-tooltip">remove</div>}>
-                    <div
-                        ref={this._closeRef}
-                        className={`clear-button ${which}`}
-                        onPointerDown={e => this.closeDown(e, which)} // prevent triggering slider movement in registerSliding
-                    >
-                        <FontAwesomeIcon className={`clear-button ${which}`} icon="times" size="sm" />
-                    </div>
-                </Tooltip>
-            );
-        };
+        const clearButton = (which: string) => (
+            <Tooltip title={<div className="dash-tooltip">remove</div>}>
+                <div
+                    ref={this._closeRef}
+                    className={`clear-button ${which}`}
+                    onPointerDown={e => this.closeDown(e, which)} // prevent triggering slider movement in registerSliding
+                >
+                    <FontAwesomeIcon className={`clear-button ${which}`} icon="times" size="sm" />
+                </div>
+            </Tooltip>
+        );
         const displayDoc = (whichSlot: string) => {
             const whichDoc = DocCast(this.dataDoc[whichSlot]);
             const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc);
@@ -334,9 +330,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                         moveDocument={whichSlot.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
                         removeDocument={whichSlot.endsWith('1') ? this.remDoc1 : this.remDoc2}
                         NativeWidth={() => NumCast(this.layoutDoc.width, 200)}
-                        NativeHeight={(): number => {
-                            return NumCast(this.layoutDoc.height, 200);
-                        }}
+                        NativeHeight={(): number => NumCast(this.layoutDoc.height, 200)}
                         isContentActive={emptyFunction}
                         isDocumentActive={returnFalse}
                         whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
@@ -358,112 +352,90 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
             </div>
         );
 
-        const displayBoxReveal = (which: string, which2: string, index: number, cover: number) => {
-            return (
-                <div style={{ display: 'flex', flexDirection: 'column' }}>
-                    <div
-                        className={`beforeBox-cont`}
-                        key={which}
-                        style={{ width: this._props.PanelWidth(), height: NumCast(this.layoutDoc.height, 200) / 2 }}
-                        onPointerDown={e => this.registerSliding(e, cover)}
-                        ref={ele => this.createDropTarget(ele, which, 0)}>
-                        {displayDoc(which)}
-                    </div>
-                    <div
-                        className={`afterBox-cont`}
-                        key={which2}
-                        style={{ width: this._props.PanelWidth(), height: NumCast(this.layoutDoc.height, 200) / 2 }}
-                        onPointerDown={e => this.registerSliding(e, cover)}
-                        ref={ele => this.createDropTarget(ele, which2, 1)}>
-                        {displayDoc(which2)}
-                    </div>
-                </div>
-            );
-        };
-
         if (this.Document._layout_isFlashcard) {
             const side = this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 1 : 0;
 
             // add text box to each side when comparison box is first created
-            if (!(this.dataDoc[this.fieldKey + '_0'] || this.dataDoc[this.fieldKey + '_0'] == 'empty')) {
+            if (!(this.dataDoc[this.fieldKey + '_0'] || this.dataDoc[this.fieldKey + '_0'] === 'empty')) {
                 const dataSplit = StrCast(this.dataDoc.data).split('Answer');
                 const newDoc = Docs.Create.TextDocument(dataSplit[1]);
                 // if there is text from the pdf ai cards, put the question on the front side.
+                // eslint-disable-next-line prefer-destructuring
                 newDoc[DocData].text = dataSplit[1];
                 this.addDoc(newDoc, this.fieldKey + '_0');
             }
-            if (!(this.dataDoc[this.fieldKey + '_1'] || this.dataDoc[this.fieldKey + '_1'] == 'empty')) {
+            if (!(this.dataDoc[this.fieldKey + '_1'] || this.dataDoc[this.fieldKey + '_1'] === 'empty')) {
                 const dataSplit = StrCast(this.dataDoc.data).split('Answer');
                 const newDoc = Docs.Create.TextDocument(dataSplit[0]);
                 // if there is text from the pdf ai cards, put the answer on the alternate side.
+                // eslint-disable-next-line prefer-destructuring
                 newDoc[DocData].text = dataSplit[0];
                 this.addDoc(newDoc, this.fieldKey + '_1');
             }
 
             // render the QuizCards
-            if (DocCast(this.Document.embedContainer) && DocCast(this.Document.embedContainer)[`filterOp`] == 'quiz') {
+            if (DocCast(this.Document.embedContainer) && DocCast(this.Document.embedContainer).filterOp === 'quiz') {
                 return (
                     <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}`} style={{ display: 'flex', flexDirection: 'column' }}>
                         <p style={{ color: 'white', padding: 10 }}>{StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text)}</p>
                         {/* {StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text)} */}
-                        <div className={'input-box'}>
-                            {
-                                <textarea
-                                    value={this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? this.outputValue : this.inputValue}
-                                    onChange={this.handleInputChange}
-                                    readOnly={this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate'}
-                                />
-                            }
+                        <div className="input-box">
+                            <textarea
+                                value={this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? this.outputValue : this.inputValue}
+                                onChange={this.handleInputChange}
+                                readOnly={this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate'}
+                            />
                         </div>
                         <div className="submit-button" style={{ display: this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 'none' : 'flex' }}>
-                            <button onClick={this.handleRenderGPTClick}>Submit</button>
+                            <button type="button" onClick={this.handleRenderGPTClick}>
+                                Submit
+                            </button>
                         </div>
                         <div className="submit-button" style={{ display: this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? 'flex' : 'none' }}>
-                            <button onClick={this.handleRenderClick}>Edit Your Response</button>
+                            <button type="button" onClick={this.handleRenderClick}>
+                                Edit Your Response
+                            </button>
                         </div>
                     </div>
                 );
             }
 
             // render a normal flashcard when not a QuizCard
-            else {
-                return (
-                    <div
-                        className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}`} /* change className to easily disable/enable pointer events in CSS */
-                        style={{ display: 'flex', flexDirection: 'column' }}
-                        onMouseEnter={() => {
-                            this.hoverFlip('alternate');
-                        }}
-                        onMouseLeave={() => {
-                            this.hoverFlip(undefined);
-                        }}>
-                        {displayBox(`${this.fieldKey}_${side === 0 ? 1 : 0}`, side, this._props.PanelWidth() - 3)}
-                        {this.overlayAlternateIcon}
-                    </div>
-                );
-            }
-        } else {
-            // render a comparison box that compares items side by side
             return (
-                <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}>
-                    {displayBox(`${this.fieldKey}_2`, 1, this._props.PanelWidth() - 3)}
-                    <div className="clip-div" style={{ width: this.clipWidth + '%', transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, 'gray') }}>
-                        {displayBox(`${this.fieldKey}_1`, 0, 0)}
-                    </div>
-
-                    <div
-                        className="slide-bar"
-                        style={{
-                            left: `calc(${this.clipWidth + '%'} - 0.5px)`,
-                            cursor: this.clipWidth < 5 ? 'e-resize' : this.clipWidth / 100 > (this._props.PanelWidth() - 5) / this._props.PanelWidth() ? 'w-resize' : undefined,
-                        }}
-                        onPointerDown={e => !this._isAnyChildContentActive && this.registerSliding(e, this._props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */
-                    >
-                        <div className="slide-handle" />
-                    </div>
+                <div
+                    className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}`} /* change className to easily disable/enable pointer events in CSS */
+                    style={{ display: 'flex', flexDirection: 'column' }}
+                    onMouseEnter={() => {
+                        this.hoverFlip('alternate');
+                    }}
+                    onMouseLeave={() => {
+                        this.hoverFlip(undefined);
+                    }}>
+                    {displayBox(`${this.fieldKey}_${side === 0 ? 1 : 0}`, side, this._props.PanelWidth() - 3)}
+                    {this.overlayAlternateIcon}
                 </div>
             );
         }
+        // render a comparison box that compares items side by side
+        return (
+            <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}>
+                {displayBox(`${this.fieldKey}_2`, 1, this._props.PanelWidth() - 3)}
+                <div className="clip-div" style={{ width: this.clipWidth + '%', transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, 'gray') }}>
+                    {displayBox(`${this.fieldKey}_1`, 0, 0)}
+                </div>
+
+                <div
+                    className="slide-bar"
+                    style={{
+                        left: `calc(${this.clipWidth + '%'} - 0.5px)`,
+                        cursor: this.clipWidth < 5 ? 'e-resize' : this.clipWidth / 100 > (this._props.PanelWidth() - 5) / this._props.PanelWidth() ? 'w-resize' : undefined,
+                    }}
+                    onPointerDown={e => !this._isAnyChildContentActive && this.registerSliding(e, this._props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */
+                >
+                    <div className="slide-handle" />
+                </div>
+            </div>
+        );
     }
 }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index fca6cda81..7a1f94948 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -504,7 +504,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     askGPT = async (): Promise<string | undefined> => {
         const queryText = RTFCast(DocCast(this.dataDoc[this.props.fieldKey + '_1']).text)?.Text;
         try {
-            let res = await gptAPICall('Question: ' + StrCast(queryText), GPTCallType.CHATCARD);
+            const res = await gptAPICall('Question: ' + StrCast(queryText), GPTCallType.CHATCARD);
             if (!res) {
                 console.error('GPT call failed');
                 return;
@@ -585,8 +585,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
             if (this.Document._layout_isFlashcard) {
                 const revealOptions = cm.findByDescription('Reveal Options');
                 const revealItems: ContextMenuProps[] = revealOptions && 'subitems' in revealOptions ? revealOptions.subitems : [];
-                revealItems.push({ description: 'Hover', event: () => (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'hover'), icon: 'hand-point-up' });
-                revealItems.push({ description: 'Flip', event: () => (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'flip'), icon: 'rotate' });
+                revealItems.push({ description: 'Hover', event: () => { this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'hover'; }, icon: 'hand-point-up' }); // prettier-ignore
+                revealItems.push({ description: 'Flip', event: () => { this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'flip'; }, icon: 'rotate' }); // prettier-ignore
                 !revealOptions && cm.addItem({ description: 'Reveal Options', addDivider: false, noexpand: true, subitems: revealItems, icon: 'layer-group' });
             }
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 9ccd95f24..9f2a9b8e1 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -925,7 +925,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             const usepath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
             appearanceItems.push({
                 description: (this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate:hover' ? 'no hover' : 'hover') + ' to show alt content',
-                event: () => (this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usepath === 'alternate' || usepath === undefined ? 'alternate:hover' : undefined),
+                event: () => {
+                    this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usepath === 'alternate' || usepath === undefined ? 'alternate:hover' : undefined;
+                },
                 icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye',
             });
         }
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index b7247a034..745e809db 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -78,13 +78,13 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
      * Invokes the API with the selected text and stores it in the summarized text.
      * @param e pointer down event
      */
-    gptSummarize = async (e: React.PointerEvent) => {
+    gptSummarize = async () => {
         GPTPopup.Instance.setVisible(true);
         GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY);
         GPTPopup.Instance.setLoading(true);
 
         try {
-            const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY);
+            const res = await gptAPICall(this._selectedText, GPTCallType.SUMMARY);
             GPTPopup.Instance.setText(res || 'Something went wrong.');
         } catch (err) {
             console.error(err);
@@ -100,8 +100,8 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
      * Invokes the API with the selected text and stores it in the selected text.
      * @param e pointer down event
      */
-    gptFlashcards = async (e: React.PointerEvent) => {
-        const queryText = this.selectedText;
+    gptFlashcards = async () => {
+        const queryText = this._selectedText;
         try {
             const res = await gptAPICall(queryText, GPTCallType.FLASHCARD);
             console.log(res);
@@ -120,7 +120,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         // put each question generated by GPT on the front of the flashcard
         const senArr = text.split('Question');
         const collectionArr: Doc[] = [];
-        for (var i = 1; i < senArr.length; i++) {
+        for (let i = 1; i < senArr.length; i++) {
             console.log('Arr ' + i + ': ' + senArr[i]);
             const newDoc = Docs.Create.ComparisonDocument(senArr[i], { _layout_isFlashcard: true, _width: 300, _height: 300 });
             newDoc.text = senArr[i];
-- 
cgit v1.2.3-70-g09d2

