diff options
| author | Nathan-SR <144961007+Nathan-SR@users.noreply.github.com> | 2024-06-03 13:33:37 -0400 |
|---|---|---|
| committer | Nathan-SR <144961007+Nathan-SR@users.noreply.github.com> | 2024-06-03 13:33:37 -0400 |
| commit | 9e77f980e7704999ef0a1c1845d660bccb13ff8a (patch) | |
| tree | 14ca0da5915e4382a7bcb15f7d0b241941c8291f /src/client/views/collections/CollectionCarouselView.tsx | |
| parent | 1be63695875c9242fba43d580465e8765cf3991d (diff) | |
| parent | 202e994515392892676f8f080852db1e32b8dbd3 (diff) | |
Merge branch 'master' into nathan-starter
Diffstat (limited to 'src/client/views/collections/CollectionCarouselView.tsx')
| -rw-r--r-- | src/client/views/collections/CollectionCarouselView.tsx | 128 |
1 files changed, 122 insertions, 6 deletions
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 45b64d3e6..2adad68e0 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -5,12 +5,14 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { StopEvent, returnFalse, returnOne, returnTrue, returnZero } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; -import { StopEvent, returnFalse, returnOne, returnZero } from '../../../ClientUtils'; 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'; @@ -18,9 +20,20 @@ import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import './CollectionCarouselView.scss'; import { CollectionSubView } from './CollectionSubView'; +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); @@ -41,15 +54,76 @@ 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 => { + 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; + } + 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 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 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 + this.carouselItems.forEach(item => { // reset all the practice values + item.layout[this.practiceField] = undefined; + }); + } + break; + default: moveToCardWithField(returnTrue); + } // prettier-ignore + }; + + /** + * 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; + this.move(1); }; + + /** + * 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; + this.move(-1); + }; + + /* + * Stars the document when the star button is pressed. + */ + star = (e: React.MouseEvent) => { + e.stopPropagation(); + const curDoc = this.carouselItems[NumCast(this.layoutDoc._carousel_index)]; + 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. + */ + setPracticeVal = (e: React.MouseEvent, val: string) => { + e.stopPropagation(); + const curDoc = this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)]; + curDoc.layout[this.practiceField] = val; + this.advance(e); + }; + 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; @@ -58,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]; @@ -107,6 +189,7 @@ export class CollectionCarouselView extends CollectionSubView() { ); } @computed get buttons() { + if (!this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)]) return null; return ( <> <div key="back" className="carouselView-back" onClick={this.goback}> @@ -115,6 +198,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.carouselItems?.[NumCast(this.layoutDoc._carousel_index)].layout[this.starField] ? 'yellow' : 'gray'} size="1x" /> + </div> + <div key="remove" className="carouselView-remove" onClick={e => this.setPracticeVal(e, practiceVal.MISSED)} style={{ visibility: this.layoutDoc.filterOp === cardMode.PRACTICE ? 'visible' : 'hidden' }}> + <FontAwesomeIcon icon="xmark" color="red" size="1x" /> + </div> + <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> </> ); } @@ -124,11 +216,35 @@ export class CollectionCarouselView extends CollectionSubView() { <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), }}> {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.carouselItems?.[NumCast(this.layoutDoc._carousel_index)] && this.layoutDoc.filterOp === cardMode.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', + zIndex: '999', + position: 'relative', + left: '10px', + top: '10px', + 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> {this.Document._chromeHidden ? null : this.buttons} </div> ); |
