From e95d25eb8159bb7c753fa27e74e9baa8d3bffea6 Mon Sep 17 00:00:00 2001
From: alyssaf16 <alyssa_feinberg@brown.edu>
Date: Sat, 30 Mar 2024 00:06:27 -0400
Subject: working on making flashcards from pdf with ai

---
 .../views/collections/CollectionCarouselView.tsx   | 76 ++++++++++++++++++++++
 1 file changed, 76 insertions(+)

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

diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index dae16bafb..7f5176123 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -36,11 +36,67 @@ export class CollectionCarouselView extends CollectionSubView() {
     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
+            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 the practice filter is selected
+        if (this.layoutDoc[`_${this._props.fieldKey}_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;
+        }
     };
     goback = (e: React.MouseEvent) => {
         e.stopPropagation();
         this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.childLayoutPairs.length) % 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
+            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 the practice filter is selected
+        if (this.layoutDoc[`_${this._props.fieldKey}_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;
+        }
+    };
+
+    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.layout[`${this.fieldKey}_star`] == undefined) curDoc.layout[`${this.fieldKey}_star`] = true;
+        else curDoc.layout[`${this.fieldKey}_star`] = !curDoc.layout[`${this.fieldKey}_star`];
+    };
+
+    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;
     };
+
     captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<FieldViewProps>, property: string): any => {
         // first look for properties on the document in the carousel, then fallback to properties on the container
         const childValue = doc?.['caption-' + property] ? this._props.styleProvider?.(doc, captionProps, property) : undefined;
@@ -106,6 +162,15 @@ export class CollectionCarouselView extends CollectionSubView() {
                 <div key="fwd" className="carouselView-fwd" onClick={this.advance}>
                     <FontAwesomeIcon icon={'chevron-right'} size={'2x'} />
                 </div>
+                <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' }}>
+                    <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' }}>
+                    <FontAwesomeIcon icon={'check'} color={'green'} size={'1x'} />
+                </div>
             </>
         );
     }
@@ -120,6 +185,17 @@ export class CollectionCarouselView extends CollectionSubView() {
                     color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color),
                 }}>
                 {this.content}
+                <p
+                    style={{
+                        color: 'red',
+                        zIndex: '999',
+                        position: 'relative',
+                        left: '10px',
+                        top: '10px',
+                        visibility: this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)].layout[`${this.fieldKey}_missed`] == 'missed' ? 'visible' : 'hidden',
+                    }}>
+                    Recently missed!
+                </p>
                 {this.Document._chromeHidden ? null : this.buttons}
             </div>
         );
-- 
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/collections/CollectionCarouselView.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 deb55ee338269d51d001f1120cd8d0d6a2c4bc6a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 20 May 2024 14:18:41 -0400
Subject: lint fixes

---
 package-lock.json                                  |  3 +-
 src/client/apis/gpt/GPT.ts                         |  2 +-
 .../views/collections/CollectionCarouselView.tsx   | 54 +++++++++++-----------
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  6 +--
 4 files changed, 31 insertions(+), 34 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index 9500b1488..3a4ca6668 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -39228,8 +39228,7 @@
         "node_modules/text-table": {
             "version": "0.2.0",
             "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
-            "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
-            "dev": true
+            "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="
         },
         "node_modules/textarea-caret": {
             "version": "3.1.0",
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 9efe4ec39..cca9d58f3 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -64,7 +64,7 @@ let lastResp = '';
  * @returns AI Output
  */
 const gptAPICall = async (inputTextIn: string, callType: GPTCallType, prompt?: any) => {
-    const inputText = callType === GPTCallType.SUMMARY || callType == GPTCallType.FLASHCARD || GPTCallType.QUIZ ? inputTextIn + '.' : inputTextIn;
+    const inputText = [GPTCallType.SUMMARY, GPTCallType.FLASHCARD, GPTCallType.QUIZ].includes(callType) ? inputTextIn + '.' : inputTextIn;
     const opts: GPTCallOpts = callTypeMap[callType];
     if (lastCall === inputText) return lastResp;
     try {
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index b736c7ced..95425a217 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -49,35 +49,35 @@ export class CollectionCarouselView extends CollectionSubView() {
     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;
+        let startInd = this.layoutDoc._carousel_index;
 
         // if the star filter is selected
-        if (this.layoutDoc[`filterOp`] == 'star') {
+        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) {
+            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';
+                this.layoutDoc.filterOp = 'all';
             }
         }
 
         // if the practice filter is selected
-        if (this.layoutDoc[`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) {
+            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';
+            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++) {
+                for (let i = 0; i < this.childLayoutPairs.length; i++) {
                     const curDoc = this.childLayoutPairs?.[NumCast(i)];
                     curDoc.layout[`${this.fieldKey}_missed`] = undefined;
                 }
@@ -93,35 +93,35 @@ export class CollectionCarouselView extends CollectionSubView() {
         e.stopPropagation();
         this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length;
 
-        var startInd = this.layoutDoc._carousel_index;
+        let startInd = this.layoutDoc._carousel_index;
 
         // if the star filter is selected
-        if (this.layoutDoc[`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) {
+            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';
+                this.layoutDoc.filterOp = 'all';
             }
         }
 
         // if the practice filter is selected
-        if (this.layoutDoc[`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) {
+            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';
+            if (this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] === 'correct') {
+                this.layoutDoc.filterOp = 'all';
 
-                for (var i = 0; i < this.childLayoutPairs.length; i++) {
+                for (let i = 0; i < this.childLayoutPairs.length; i++) {
                     const curDoc = this.childLayoutPairs?.[NumCast(i)];
                     curDoc.layout[`${this.fieldKey}_missed`] = undefined;
                 }
@@ -136,7 +136,7 @@ export class CollectionCarouselView extends CollectionSubView() {
         e.stopPropagation();
         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;
+        if (curDoc.layout[`${this.fieldKey}_star`] === undefined) curDoc.layout[`${this.fieldKey}_star`] = true;
         else curDoc.layout[`${this.fieldKey}_star`] = !curDoc.layout[`${this.fieldKey}_star`];
     };
 
@@ -207,7 +207,7 @@ export class CollectionCarouselView extends CollectionSubView() {
         );
     }
     @computed get buttons() {
-        if (!this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)]) return;
+        if (!this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)]) return null;
         return (
             <>
                 <div key="back" className="carouselView-back" onClick={this.goback}>
@@ -217,13 +217,13 @@ export class CollectionCarouselView extends CollectionSubView() {
                     <FontAwesomeIcon icon="chevron-right" size="2x" />
                 </div>
                 <div key="star" className="carouselView-star" onClick={this.star}>
-                    <FontAwesomeIcon icon={'star'} color={this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)].layout[`${this.fieldKey}_star`] ? 'yellow' : 'gray'} size={'1x'} />
+                    <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[`filterOp`] == 'practice' ? 'visible' : 'hidden' }}>
-                    <FontAwesomeIcon icon={'xmark'} color={'red'} size={'1x'} />
+                <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[`filterOp`] == 'practice' ? 'visible' : 'hidden' }}>
-                    <FontAwesomeIcon icon={'check'} color={'green'} size={'1x'} />
+                <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>
             </>
         );
@@ -242,7 +242,7 @@ export class CollectionCarouselView extends CollectionSubView() {
                 {/* 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',
+                        display: !this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)] && this.layoutDoc.filterOp === 'practice' ? 'flex' : 'none',
                         justifyContent: 'center',
                         alignItems: 'center',
                         height: '100%',
@@ -258,7 +258,7 @@ export class CollectionCarouselView extends CollectionSubView() {
                         position: 'relative',
                         left: '10px',
                         top: '10px',
-                        display: this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)] ? (this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)].layout[`${this.fieldKey}_missed`] == 'missed' ? 'block' : 'none') : 'none',
+                        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/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 0bcaa06de..034a38e9c 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -240,16 +240,14 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         if (screenXf) {
             DocumentView.DeselectAll();
             if (topDoc.z) {
-                const spt = screenXf.inverse().transformPoint(NumCast(topDoc.x), NumCast(topDoc.y));
+                [topDoc.x, topDoc.y] = screenXf.inverse().transformPoint(NumCast(topDoc.x), NumCast(topDoc.y));
                 topDoc.z = 0;
-                [topDoc.x, topDoc.y] = spt;
                 this._props.removeDocument?.(topDoc);
                 this._props.addDocTab(topDoc, OpenWhere.inParentFromScreen);
             } else {
                 const spt = this.screenToLocalTransform().inverse().transformPoint(0, 0);
-                const fpt = screenXf.transformPoint(spt[0], spt[1]);
+                [topDoc.x, topDoc.y] = screenXf.transformPoint(spt[0], spt[1]);
                 topDoc.z = 1;
-                [topDoc.x, topDoc.y] = fpt;
             }
             setTimeout(() => DocumentView.SelectView(DocumentView.getDocumentView(topDoc, containerDocView), false), 0);
         }
-- 
cgit v1.2.3-70-g09d2


From c378cd57b3ee3aa516cba817fe3e59345271d3f2 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 20 May 2024 14:25:08 -0400
Subject: carouselfview cleanup

---
 .../views/collections/CollectionCarouselView.tsx   | 48 +++++++++++-----------
 1 file changed, 24 insertions(+), 24 deletions(-)

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

diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 95425a217..c3ba04aa8 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -48,18 +48,18 @@ export class CollectionCarouselView extends CollectionSubView() {
      */
     advance = (e: React.MouseEvent) => {
         e.stopPropagation();
-        this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 1) % this.childLayoutPairs.length;
+        this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 1) % this.carouselItems.length;
         let startInd = this.layoutDoc._carousel_index;
 
         // if the star filter is selected
         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;
+            while (!this.carouselItems?.[NumCast(startInd)].layout[`${this.fieldKey}_star`] && (startInd + 1) % this.carouselItems.length !== this.layoutDoc._carousel_index) {
+                startInd = (startInd + 1) % this.carouselItems.length;
             }
             this.layoutDoc._carousel_index = startInd;
             // if there aren't any starred, show all cards
-            if (!this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_star`]) {
+            if (!this.carouselItems?.[NumCast(startInd)].layout[`${this.fieldKey}_star`]) {
                 this.layoutDoc.filterOp = 'all';
             }
         }
@@ -67,18 +67,18 @@ export class CollectionCarouselView extends CollectionSubView() {
         // if the practice filter is selected
         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;
+            while (this.carouselItems?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] === 'correct' && (startInd + 1) % this.carouselItems.length !== this.layoutDoc._carousel_index) {
+                startInd = (startInd + 1) % this.carouselItems.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') {
+            if (this.carouselItems?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] === 'correct') {
                 this.layoutDoc.filterOp = 'all';
 
                 // set all the cards to missed
-                for (let i = 0; i < this.childLayoutPairs.length; i++) {
-                    const curDoc = this.childLayoutPairs?.[NumCast(i)];
+                for (let i = 0; i < this.carouselItems.length; i++) {
+                    const curDoc = this.carouselItems?.[NumCast(i)];
                     curDoc.layout[`${this.fieldKey}_missed`] = undefined;
                 }
             }
@@ -91,19 +91,19 @@ export class CollectionCarouselView extends CollectionSubView() {
      */
     goback = (e: React.MouseEvent) => {
         e.stopPropagation();
-        this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length;
+        this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.carouselItems.length) % this.carouselItems.length;
 
         let startInd = this.layoutDoc._carousel_index;
 
         // if the star filter is selected
         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;
+            while (!this.carouselItems?.[NumCast(startInd)].layout[`${this.fieldKey}_star`] && (startInd - 1 + this.carouselItems.length) % this.carouselItems.length !== this.layoutDoc._carousel_index) {
+                startInd = (startInd - 1 + this.carouselItems.length) % this.carouselItems.length;
             }
             this.layoutDoc._carousel_index = startInd;
             // if there aren't any starred, show all cards
-            if (!this.childLayoutPairs?.[NumCast(startInd)].layout[`${this.fieldKey}_star`]) {
+            if (!this.carouselItems?.[NumCast(startInd)].layout[`${this.fieldKey}_star`]) {
                 this.layoutDoc.filterOp = 'all';
             }
         }
@@ -111,18 +111,18 @@ export class CollectionCarouselView extends CollectionSubView() {
         // if the practice filter is selected
         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;
+            while (this.carouselItems?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] === 'correct' && (startInd - 1 + this.carouselItems.length) % this.carouselItems.length !== this.layoutDoc._carousel_index) {
+                startInd = (startInd - 1 + this.carouselItems.length) % this.carouselItems.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') {
+            if (this.carouselItems?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] === 'correct') {
                 this.layoutDoc.filterOp = 'all';
 
-                for (let i = 0; i < this.childLayoutPairs.length; i++) {
-                    const curDoc = this.childLayoutPairs?.[NumCast(i)];
+                for (let i = 0; i < this.carouselItems.length; i++) {
+                    const curDoc = this.carouselItems?.[NumCast(i)];
                     curDoc.layout[`${this.fieldKey}_missed`] = undefined;
                 }
             }
@@ -134,7 +134,7 @@ export class CollectionCarouselView extends CollectionSubView() {
      */
     star = (e: React.MouseEvent) => {
         e.stopPropagation();
-        const curDoc = this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)];
+        const curDoc = this.carouselItems?.[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`];
@@ -145,7 +145,7 @@ export class CollectionCarouselView extends CollectionSubView() {
      */
     missed = (e: React.MouseEvent, val: string) => {
         e.stopPropagation();
-        const curDoc = this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)];
+        const curDoc = this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)];
         curDoc.layout[`${this.fieldKey}_missed`] = val;
         this.advance(e);
     };
@@ -207,7 +207,7 @@ export class CollectionCarouselView extends CollectionSubView() {
         );
     }
     @computed get buttons() {
-        if (!this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)]) return null;
+        if (!this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)]) return null;
         return (
             <>
                 <div key="back" className="carouselView-back" onClick={this.goback}>
@@ -217,7 +217,7 @@ export class CollectionCarouselView extends CollectionSubView() {
                     <FontAwesomeIcon icon="chevron-right" size="2x" />
                 </div>
                 <div key="star" className="carouselView-star" onClick={this.star}>
-                    <FontAwesomeIcon icon="star" color={this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)].layout[`${this.fieldKey}_star`] ? 'yellow' : 'gray'} size="1x" />
+                    <FontAwesomeIcon icon="star" color={this.carouselItems?.[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.filterOp === 'practice' ? 'visible' : 'hidden' }}>
                     <FontAwesomeIcon icon="xmark" color="red" size="1x" />
@@ -242,7 +242,7 @@ export class CollectionCarouselView extends CollectionSubView() {
                 {/* 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',
+                        display: !this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)] && this.layoutDoc.filterOp === 'practice' ? 'flex' : 'none',
                         justifyContent: 'center',
                         alignItems: 'center',
                         height: '100%',
@@ -258,7 +258,7 @@ export class CollectionCarouselView extends CollectionSubView() {
                         position: 'relative',
                         left: '10px',
                         top: '10px',
-                        display: this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)] ? (this.childLayoutPairs?.[NumCast(this.layoutDoc._carousel_index)].layout[`${this.fieldKey}_missed`] === 'missed' ? 'block' : 'none') : 'none',
+                        display: this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)] ? (this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)].layout[`${this.fieldKey}_missed`] === 'missed' ? 'block' : 'none') : 'none',
                     }}>
                     Recently missed!
                 </p>
-- 
cgit v1.2.3-70-g09d2


From d335e4694e5d45f6b273410664622ea1891073c8 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 20 May 2024 15:10:54 -0400
Subject: fixing flash card carousel code organization.  lint fixes.

---
 .../views/collections/CollectionCarouselView.tsx   | 130 ++++++++-------------
 src/client/views/collections/CollectionView.tsx    |  14 ---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  81 ++++++-------
 3 files changed, 84 insertions(+), 141 deletions(-)

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

diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index c3ba04aa8..282ac90fe 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -6,7 +6,7 @@ import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { emptyFunction } from '../../../Utils';
-import { StopEvent, returnFalse, returnOne, returnZero } from '../../../ClientUtils';
+import { StopEvent, returnFalse, returnOne, returnTrue, returnZero } from '../../../ClientUtils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -17,6 +17,8 @@ import { FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import './CollectionCarouselView.scss';
 import { CollectionSubView } from './CollectionSubView';
+import { ContextMenu } from '../ContextMenu';
+import { ContextMenuProps } from '../ContextMenuItem';
 
 @observer
 export class CollectionCarouselView extends CollectionSubView() {
@@ -42,91 +44,49 @@ export class CollectionCarouselView extends CollectionSubView() {
         return this.childLayoutPairs.filter(pair => pair.layout.type !== DocumentType.LINK);
     }
 
+    move = (dir: number) => {
+        const moveToCardWithField = (match: (doc: Doc) => boolean): boolean => {
+            let startInd = (NumCast(this.layoutDoc._carousel_index) + dir) % this.carouselItems.length;
+            while (!match(this.carouselItems?.[startInd].layout) && (startInd + dir + this.carouselItems.length) % this.carouselItems.length !== this.layoutDoc._carousel_index) {
+                startInd = (startInd + dir + this.carouselItems.length) % this.carouselItems.length;
+            }
+            this.layoutDoc._carousel_index = startInd;
+            return match(this.carouselItems?.[startInd].layout);
+        };
+        switch (StrCast(this.layoutDoc.filterOp)) {
+            case 'star':  // go to a flashcard that is starred, skip the ones that aren't
+                if (!moveToCardWithField((doc: Doc) => !!doc[`${this.fieldKey}_star`])) {
+                    this.layoutDoc.filterOp = undefined;  // if there aren't any starred, show all cards
+                }
+                break;
+            case 'practice': // go to a new index that is missed, skip the ones that are correct
+                if (!moveToCardWithField((doc: Doc) => doc[`${this.fieldKey}_missed`] !== 'correct')) {
+                    this.layoutDoc.filterOp = undefined;  // if all of the cards are correct, show all cards and exit practice mode
+
+                    // set all the cards to missed
+                    this.carouselItems.forEach(item => {
+                        item.layout[`${this.fieldKey}_missed`] = undefined;
+                    });
+                }
+                break;
+            default: moveToCardWithField( returnTrue);
+        } // prettier-ignore
+    };
+
     /**
-     * Goes to the next flashcard in the stack and filters
-     * based on the the currently selected option.
+     * Goes to the next Doc in the stack subject to the currently selected filter option.
      */
     advance = (e: React.MouseEvent) => {
         e.stopPropagation();
-        this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 1) % this.carouselItems.length;
-        let startInd = this.layoutDoc._carousel_index;
-
-        // if the star filter is selected
-        if (this.layoutDoc.filterOp === 'star') {
-            // go to a flashcard that is starred, skip the ones that aren't
-            while (!this.carouselItems?.[NumCast(startInd)].layout[`${this.fieldKey}_star`] && (startInd + 1) % this.carouselItems.length !== this.layoutDoc._carousel_index) {
-                startInd = (startInd + 1) % this.carouselItems.length;
-            }
-            this.layoutDoc._carousel_index = startInd;
-            // if there aren't any starred, show all cards
-            if (!this.carouselItems?.[NumCast(startInd)].layout[`${this.fieldKey}_star`]) {
-                this.layoutDoc.filterOp = 'all';
-            }
-        }
-
-        // if the practice filter is selected
-        if (this.layoutDoc.filterOp === 'practice') {
-            // go to a new index that is missed, skip the ones that are correct
-            while (this.carouselItems?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] === 'correct' && (startInd + 1) % this.carouselItems.length !== this.layoutDoc._carousel_index) {
-                startInd = (startInd + 1) % this.carouselItems.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.carouselItems?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] === 'correct') {
-                this.layoutDoc.filterOp = 'all';
-
-                // set all the cards to missed
-                for (let i = 0; i < this.carouselItems.length; i++) {
-                    const curDoc = this.carouselItems?.[NumCast(i)];
-                    curDoc.layout[`${this.fieldKey}_missed`] = undefined;
-                }
-            }
-        }
+        this.move(1);
     };
 
     /**
-     * Goes to the previous flashcard in the stack and filters
-     * based on the the currently selected option.
+     * Goes to the previous Doc in the stack subject to the currently selected filter option.
      */
     goback = (e: React.MouseEvent) => {
         e.stopPropagation();
-        this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.carouselItems.length) % this.carouselItems.length;
-
-        let startInd = this.layoutDoc._carousel_index;
-
-        // if the star filter is selected
-        if (this.layoutDoc.filterOp === 'star') {
-            // go to a new index that is starred, skip the ones that aren't
-            while (!this.carouselItems?.[NumCast(startInd)].layout[`${this.fieldKey}_star`] && (startInd - 1 + this.carouselItems.length) % this.carouselItems.length !== this.layoutDoc._carousel_index) {
-                startInd = (startInd - 1 + this.carouselItems.length) % this.carouselItems.length;
-            }
-            this.layoutDoc._carousel_index = startInd;
-            // if there aren't any starred, show all cards
-            if (!this.carouselItems?.[NumCast(startInd)].layout[`${this.fieldKey}_star`]) {
-                this.layoutDoc.filterOp = 'all';
-            }
-        }
-
-        // if the practice filter is selected
-        if (this.layoutDoc.filterOp === 'practice') {
-            // go to a new index that is missed, skip the ones that are correct
-            while (this.carouselItems?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] === 'correct' && (startInd - 1 + this.carouselItems.length) % this.carouselItems.length !== this.layoutDoc._carousel_index) {
-                startInd = (startInd - 1 + this.carouselItems.length) % this.carouselItems.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.carouselItems?.[NumCast(startInd)].layout[`${this.fieldKey}_missed`] === 'correct') {
-                this.layoutDoc.filterOp = 'all';
-
-                for (let i = 0; i < this.carouselItems.length; i++) {
-                    const curDoc = this.carouselItems?.[NumCast(i)];
-                    curDoc.layout[`${this.fieldKey}_missed`] = undefined;
-                }
-            }
-        }
+        this.move(-1);
     };
 
     /*
@@ -134,10 +94,8 @@ export class CollectionCarouselView extends CollectionSubView() {
      */
     star = (e: React.MouseEvent) => {
         e.stopPropagation();
-        const curDoc = this.carouselItems?.[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`];
+        const curDoc = this.carouselItems[NumCast(this.layoutDoc._carousel_index)];
+        curDoc.layout[`${this.fieldKey}_star`] = curDoc.layout[`${this.fieldKey}_star`] ? undefined : true;
     };
 
     /*
@@ -228,12 +186,24 @@ export class CollectionCarouselView extends CollectionSubView() {
             </>
         );
     }
+    specificMenu = (): void => {
+        const cm = ContextMenu.Instance;
+
+        const revealOptions = cm.findByDescription('Filter Flashcards');
+        const revealItems: ContextMenuProps[] = revealOptions && 'subitems' in revealOptions ? revealOptions.subitems : [];
+        revealItems.push({description: 'All',           event: () => {this.layoutDoc.filterOp = undefined;},  icon: 'layer-group',}); // prettier-ignore
+        revealItems.push({description: 'Star',          event: () => {this.layoutDoc.filterOp = 'star';},     icon: 'star',}); // prettier-ignore
+        revealItems.push({description: 'Practice Mode', event: () => {this.layoutDoc.filterOp = 'practice';}, icon: 'check',}); // prettier-ignore
+        revealItems.push({description: 'Quiz Cards',    event: () => {this.layoutDoc.filterOp = 'quiz';},     icon: 'pencil',}); // prettier-ignore
+        !revealOptions && cm.addItem({ description: 'Filter Flashcards', addDivider: false, noexpand: true, subitems: revealItems, icon: 'layer-group' });
+    };
 
     render() {
         return (
             <div
                 className="collectionCarouselView-outer"
                 ref={this.createDashEventsTarget}
+                onContextMenu={this.specificMenu}
                 style={{
                     background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor),
                     color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color),
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index d084a2aec..5c304b4a9 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -52,7 +52,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
         super(props);
         makeObservable(this);
         this._annotationKeySuffix = returnEmptyString;
-        this.layoutDoc[`filterOp`] = 'all';
     }
 
     componentDidMount() {
@@ -151,19 +150,6 @@ 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[`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' });
-
-            // 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 : [];
             !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.Document.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => {this.Document.forceActive = !this.Document.forceActive}, icon: 'project-diagram' }) : null; // prettier-ignore
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 110f7816c..be96b914c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -517,6 +517,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                             setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, hit !== -1, false);
                         }
                         break;
+                    default: 
                 }
             }
         }
@@ -767,10 +768,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     insideEraserOutline = (eraserOutline: InkData, point: { X: number; Y: number }) => {
         let isInside = false;
         if (!isNaN(eraserOutline[0].X) && !isNaN(eraserOutline[0].Y)) {
-            let minX = eraserOutline[0].X,
-                maxX = eraserOutline[0].X;
-            let minY = eraserOutline[0].Y,
-                maxY = eraserOutline[0].Y;
+            let [minX, minY] = [eraserOutline[0].X, eraserOutline[0].Y];
+            let [maxX, maxY] = [eraserOutline[0].X, eraserOutline[0].Y];
             for (let i = 1; i < eraserOutline.length; i++) {
                 const currPoint: { X: number; Y: number } = eraserOutline[i];
                 minX = Math.min(currPoint.X, minX);
@@ -784,7 +783,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             }
 
             for (let i = 0, j = eraserOutline.length - 1; i < eraserOutline.length; j = i, i++) {
-                if (eraserOutline[i].Y > point.Y != eraserOutline[j].Y > point.Y && point.X < ((eraserOutline[j].X - eraserOutline[i].X) * (point.Y - eraserOutline[i].Y)) / (eraserOutline[j].Y - eraserOutline[i].Y) + eraserOutline[i].X) {
+                if (eraserOutline[i].Y > point.Y !== eraserOutline[j].Y > point.Y && point.X < ((eraserOutline[j].X - eraserOutline[i].X) * (point.Y - eraserOutline[i].Y)) / (eraserOutline[j].Y - eraserOutline[i].Y) + eraserOutline[i].X) {
                     isInside = !isInside;
                 }
             }
@@ -845,7 +844,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     getRadiusEraserIntersections = (lastPoint: { X: number; Y: number }, currPoint: { X: number; Y: number }) => {
         // set distance of the eraser's bounding box based on the zoom
         let boundingBoxDist = ActiveEraserWidth() + 5;
-        this.zoomScaling() < 1 ? (boundingBoxDist = boundingBoxDist / (this.zoomScaling() * 1.5)) : (boundingBoxDist *= this.zoomScaling());
+        this.zoomScaling() < 1 ? (boundingBoxDist /= this.zoomScaling() * 1.5) : (boundingBoxDist *= this.zoomScaling());
 
         const eraserMin = { X: Math.min(lastPoint.X, currPoint.X) - boundingBoxDist, Y: Math.min(lastPoint.Y, currPoint.Y) - boundingBoxDist };
         const eraserMax = { X: Math.max(lastPoint.X, currPoint.X) + boundingBoxDist, Y: Math.max(lastPoint.Y, currPoint.Y) + boundingBoxDist };
@@ -961,29 +960,25 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     } else if (segmentTs[j] === Math.floor(inkData.length / 4) + 1) {
                         // the last end
                         break;
+                    } else if (!continueErasing) {
+                        currSegment.push(inkBezier.split(0, segmentTs[j] - currCurveT));
+                        continueErasing = true;
                     } else {
-                        if (!continueErasing) {
-                            currSegment.push(inkBezier.split(0, segmentTs[j] - currCurveT));
-                            continueErasing = true;
-                        } else {
-                            // we've reached the end of the part to take out...
-                            continueErasing = false;
-                            if (currSegment.length > 0) {
-                                segments.push(currSegment); // ...so we add it to the list and reset currSegment
-                                if (firstSegment.length === 0) {
-                                    firstSegment = currSegment;
-                                }
-                                currSegment = [];
+                        // we've reached the end of the part to take out...
+                        continueErasing = false;
+                        if (currSegment.length > 0) {
+                            segments.push(currSegment); // ...so we add it to the list and reset currSegment
+                            if (firstSegment.length === 0) {
+                                firstSegment = currSegment;
                             }
-                            currSegment.push(inkBezier.split(segmentTs[j] - currCurveT, 1));
+                            currSegment = [];
                         }
+                        currSegment.push(inkBezier.split(segmentTs[j] - currCurveT, 1));
                     }
                 }
-            } else {
-                if (!continueErasing) {
-                    // push the bezier piece if not in the eraser circle
-                    currSegment.push(inkBezier);
-                }
+            } else if (!continueErasing) {
+                // push the bezier piece if not in the eraser circle
+                currSegment.push(inkBezier);
             }
         }
 
@@ -1078,13 +1073,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         segment2.push(inkSegment.split(intersections[closestTs[1]] - currCurveT, 1));
                         continueErasing = false;
                         hasSplit = true;
-                    } else {
-                        if (!continueErasing && !hasSplit) {
-                            // segment doesn't get pushed if continueErasing is true
-                            segment1.push(inkSegment);
-                        } else if (!continueErasing && hasSplit) {
-                            segment2.push(inkSegment);
-                        }
+                    } else if (!continueErasing && !hasSplit) {
+                        // segment doesn't get pushed if continueErasing is true
+                        segment1.push(inkSegment);
+                    } else if (!continueErasing && hasSplit) {
+                        segment2.push(inkSegment);
                     }
                 } else if (splitSegment1 === -1) {
                     // case where first end is erased
@@ -1096,13 +1089,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                             segment2.push(inkSegment.split(intersections[closestTs[1]] - currCurveT, 1));
                         }
                         hasSplit = true;
-                    } else {
-                        if (isClosedCurve && currCurveT === segmentIndexes.lastElement()) {
-                            segment2.push(inkSegment.split(0, intersections.lastElement() - currCurveT));
-                            continueErasing = true;
-                        } else if (hasSplit && !continueErasing) {
-                            segment2.push(inkSegment);
-                        }
+                    } else if (isClosedCurve && currCurveT === segmentIndexes.lastElement()) {
+                        segment2.push(inkSegment.split(0, intersections.lastElement() - currCurveT));
+                        continueErasing = true;
+                    } else if (hasSplit && !continueErasing) {
+                        segment2.push(inkSegment);
                     }
                 } else {
                     // case where last end is erased
@@ -1118,10 +1109,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         segment1.push(inkSegment.split(0, intersections[closestTs[0]] - currCurveT));
                         hasSplit = true;
                         continueErasing = true;
-                    } else {
-                        if ((isClosedCurve && hasSplit && !continueErasing) || (!isClosedCurve && !hasSplit)) {
-                            segment1.push(inkSegment);
-                        }
+                    } else if ((isClosedCurve && hasSplit && !continueErasing) || (!isClosedCurve && !hasSplit)) {
+                        segment1.push(inkSegment);
                     }
                 }
             }
@@ -1165,12 +1154,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 } else {
                     return this.getClosestTs(tVals, excludeT, mid + 1, endIndex);
                 }
+            } else if (mid - 1 >= startIndex && tVals[mid - 1] < excludeT) {
+                return [mid - 1, mid];
             } else {
-                if (mid - 1 >= startIndex && tVals[mid - 1] < excludeT) {
-                    return [mid - 1, mid];
-                } else {
-                    return this.getClosestTs(tVals, excludeT, startIndex, mid - 1);
-                }
+                return this.getClosestTs(tVals, excludeT, startIndex, mid - 1);
             }
         }
     };
-- 
cgit v1.2.3-70-g09d2


From 2d9c797baf96efb54220b668c1a76fa4106030b9 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 20 May 2024 15:39:11 -0400
Subject: upated carousel modes

---
 .../views/collections/CollectionCarouselView.tsx   | 83 +++++++++++++---------
 1 file changed, 48 insertions(+), 35 deletions(-)

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

diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 282ac90fe..2adad68e0 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -5,24 +5,35 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { emptyFunction } from '../../../Utils';
 import { StopEvent, returnFalse, returnOne, returnTrue, returnZero } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DragManager } from '../../util/DragManager';
+import { ContextMenu } from '../ContextMenu';
+import { ContextMenuProps } from '../ContextMenuItem';
 import { StyleProp } from '../StyleProp';
 import { DocumentView } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import './CollectionCarouselView.scss';
 import { CollectionSubView } from './CollectionSubView';
-import { ContextMenu } from '../ContextMenu';
-import { ContextMenuProps } from '../ContextMenuItem';
 
+enum cardMode {
+    PRACTICE = 'practice',
+    STAR = 'star',
+    QUIZ = 'quiz',
+}
+enum practiceVal {
+    MISSED = 'missed',
+    CORRECT = 'correct',
+}
 @observer
 export class CollectionCarouselView extends CollectionSubView() {
     private _dropDisposer?: DragManager.DragDropDisposer;
+    get practiceField() { return this.fieldKey + "_practice"; } // prettier-ignore
+    get starField()     { return this.fieldKey + "_star"; } // prettier-ignore
 
     constructor(props: any) {
         super(props);
@@ -43,6 +54,9 @@ export class CollectionCarouselView extends CollectionSubView() {
     @computed get carouselItems() {
         return this.childLayoutPairs.filter(pair => pair.layout.type !== DocumentType.LINK);
     }
+    @computed get marginX() {
+        return NumCast(this.layoutDoc.caption_xMargin, 50);
+    }
 
     move = (dir: number) => {
         const moveToCardWithField = (match: (doc: Doc) => boolean): boolean => {
@@ -50,26 +64,28 @@ export class CollectionCarouselView extends CollectionSubView() {
             while (!match(this.carouselItems?.[startInd].layout) && (startInd + dir + this.carouselItems.length) % this.carouselItems.length !== this.layoutDoc._carousel_index) {
                 startInd = (startInd + dir + this.carouselItems.length) % this.carouselItems.length;
             }
-            this.layoutDoc._carousel_index = startInd;
-            return match(this.carouselItems?.[startInd].layout);
+            if (match(this.carouselItems?.[startInd].layout)) {
+                this.layoutDoc._carousel_index = startInd;
+                return true;
+            }
+            return match(this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)].layout);
         };
         switch (StrCast(this.layoutDoc.filterOp)) {
-            case 'star':  // go to a flashcard that is starred, skip the ones that aren't
-                if (!moveToCardWithField((doc: Doc) => !!doc[`${this.fieldKey}_star`])) {
+            case cardMode.STAR:  // go to a flashcard that is starred, skip the ones that aren't
+                if (!moveToCardWithField((doc: Doc) => !!doc[this.starField])) {
                     this.layoutDoc.filterOp = undefined;  // if there aren't any starred, show all cards
                 }
                 break;
-            case 'practice': // go to a new index that is missed, skip the ones that are correct
-                if (!moveToCardWithField((doc: Doc) => doc[`${this.fieldKey}_missed`] !== 'correct')) {
+            case cardMode.PRACTICE: // go to a new index that is missed, skip the ones that are correct
+                if (!moveToCardWithField((doc: Doc) => doc[this.practiceField] !== practiceVal.CORRECT)) {
                     this.layoutDoc.filterOp = undefined;  // if all of the cards are correct, show all cards and exit practice mode
 
-                    // set all the cards to missed
-                    this.carouselItems.forEach(item => {
-                        item.layout[`${this.fieldKey}_missed`] = undefined;
+                    this.carouselItems.forEach(item => { // reset all the practice values
+                        item.layout[this.practiceField] = undefined;
                     });
                 }
                 break;
-            default: moveToCardWithField( returnTrue);
+            default: moveToCardWithField(returnTrue);
         } // prettier-ignore
     };
 
@@ -95,16 +111,16 @@ export class CollectionCarouselView extends CollectionSubView() {
     star = (e: React.MouseEvent) => {
         e.stopPropagation();
         const curDoc = this.carouselItems[NumCast(this.layoutDoc._carousel_index)];
-        curDoc.layout[`${this.fieldKey}_star`] = curDoc.layout[`${this.fieldKey}_star`] ? undefined : true;
+        curDoc.layout[this.starField] = curDoc.layout[this.starField] ? undefined : true;
     };
 
     /*
      * 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) => {
+    setPracticeVal = (e: React.MouseEvent, val: string) => {
         e.stopPropagation();
         const curDoc = this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)];
-        curDoc.layout[`${this.fieldKey}_missed`] = val;
+        curDoc.layout[this.practiceField] = val;
         this.advance(e);
     };
 
@@ -116,10 +132,18 @@ export class CollectionCarouselView extends CollectionSubView() {
     panelHeight = () => this._props.PanelHeight() - (StrCast(this.layoutDoc._layout_showCaption) ? 50 : 0);
     onContentDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
     onContentClick = () => ScriptCast(this.layoutDoc.onChildClick);
-    @computed get marginX() {
-        return NumCast(this.layoutDoc.caption_xMargin, 50);
-    }
     captionWidth = () => this._props.PanelWidth() - 2 * this.marginX;
+    specificMenu = (): void => {
+        const cm = ContextMenu.Instance;
+
+        const revealOptions = cm.findByDescription('Filter Flashcards');
+        const revealItems: ContextMenuProps[] = revealOptions && 'subitems' in revealOptions ? revealOptions.subitems : [];
+        revealItems.push({description: 'All',           event: () => {this.layoutDoc.filterOp = undefined;},         icon: 'layer-group',}); // prettier-ignore
+        revealItems.push({description: 'Star',          event: () => {this.layoutDoc.filterOp = cardMode.STAR;},     icon: 'star',}); // prettier-ignore
+        revealItems.push({description: 'Practice Mode', event: () => {this.layoutDoc.filterOp = cardMode.PRACTICE;}, icon: 'check',}); // prettier-ignore
+        revealItems.push({description: 'Quiz Cards',    event: () => {this.layoutDoc.filterOp = cardMode.QUIZ;},     icon: 'pencil',}); // prettier-ignore
+        !revealOptions && cm.addItem({ description: 'Filter Flashcards', addDivider: false, noexpand: true, subitems: revealItems, icon: 'layer-group' });
+    };
     @computed get content() {
         const index = NumCast(this.layoutDoc._carousel_index);
         const curDoc = this.carouselItems?.[index];
@@ -175,28 +199,17 @@ export class CollectionCarouselView extends CollectionSubView() {
                     <FontAwesomeIcon icon="chevron-right" size="2x" />
                 </div>
                 <div key="star" className="carouselView-star" onClick={this.star}>
-                    <FontAwesomeIcon icon="star" color={this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)].layout[`${this.fieldKey}_star`] ? 'yellow' : 'gray'} size="1x" />
+                    <FontAwesomeIcon icon="star" color={this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)].layout[this.starField] ? 'yellow' : 'gray'} size="1x" />
                 </div>
-                <div key="remove" className="carouselView-remove" onClick={e => this.missed(e, 'missed')} style={{ visibility: this.layoutDoc.filterOp === 'practice' ? 'visible' : 'hidden' }}>
+                <div key="remove" className="carouselView-remove" onClick={e => this.setPracticeVal(e, practiceVal.MISSED)} style={{ visibility: this.layoutDoc.filterOp === cardMode.PRACTICE ? 'visible' : 'hidden' }}>
                     <FontAwesomeIcon icon="xmark" color="red" size="1x" />
                 </div>
-                <div key="check" className="carouselView-check" onClick={e => this.missed(e, 'correct')} style={{ visibility: this.layoutDoc.filterOp === 'practice' ? 'visible' : 'hidden' }}>
+                <div key="check" className="carouselView-check" onClick={e => this.setPracticeVal(e, practiceVal.CORRECT)} style={{ visibility: this.layoutDoc.filterOp === cardMode.PRACTICE ? 'visible' : 'hidden' }}>
                     <FontAwesomeIcon icon="check" color="green" size="1x" />
                 </div>
             </>
         );
     }
-    specificMenu = (): void => {
-        const cm = ContextMenu.Instance;
-
-        const revealOptions = cm.findByDescription('Filter Flashcards');
-        const revealItems: ContextMenuProps[] = revealOptions && 'subitems' in revealOptions ? revealOptions.subitems : [];
-        revealItems.push({description: 'All',           event: () => {this.layoutDoc.filterOp = undefined;},  icon: 'layer-group',}); // prettier-ignore
-        revealItems.push({description: 'Star',          event: () => {this.layoutDoc.filterOp = 'star';},     icon: 'star',}); // prettier-ignore
-        revealItems.push({description: 'Practice Mode', event: () => {this.layoutDoc.filterOp = 'practice';}, icon: 'check',}); // prettier-ignore
-        revealItems.push({description: 'Quiz Cards',    event: () => {this.layoutDoc.filterOp = 'quiz';},     icon: 'pencil',}); // prettier-ignore
-        !revealOptions && cm.addItem({ description: 'Filter Flashcards', addDivider: false, noexpand: true, subitems: revealItems, icon: 'layer-group' });
-    };
 
     render() {
         return (
@@ -212,7 +225,7 @@ export class CollectionCarouselView extends CollectionSubView() {
                 {/* 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.carouselItems?.[NumCast(this.layoutDoc._carousel_index)] && this.layoutDoc.filterOp === 'practice' ? 'flex' : 'none',
+                        display: !this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)] && this.layoutDoc.filterOp === cardMode.PRACTICE ? 'flex' : 'none',
                         justifyContent: 'center',
                         alignItems: 'center',
                         height: '100%',
@@ -228,7 +241,7 @@ export class CollectionCarouselView extends CollectionSubView() {
                         position: 'relative',
                         left: '10px',
                         top: '10px',
-                        display: this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)] ? (this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)].layout[`${this.fieldKey}_missed`] === 'missed' ? 'block' : 'none') : 'none',
+                        display: this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)] ? (this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)].layout[this.practiceField] === practiceVal.MISSED ? 'block' : 'none') : 'none',
                     }}>
                     Recently missed!
                 </p>
-- 
cgit v1.2.3-70-g09d2

