From 972839216c14baa5c9eaf80e1fb2fb2694bbb72c Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 8 Oct 2024 22:51:46 -0400 Subject: modified how buttons are laid out on carousel and comparison views so that text boxes can reflow around them. extracted flashcard pratice into its own component and applied it to carousel3D and carousel --- .../views/collections/FlashcardPracticeUI.tsx | 172 +++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 src/client/views/collections/FlashcardPracticeUI.tsx (limited to 'src/client/views/collections/FlashcardPracticeUI.tsx') diff --git a/src/client/views/collections/FlashcardPracticeUI.tsx b/src/client/views/collections/FlashcardPracticeUI.tsx new file mode 100644 index 000000000..032a405bf --- /dev/null +++ b/src/client/views/collections/FlashcardPracticeUI.tsx @@ -0,0 +1,172 @@ +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Tooltip } from '@mui/material'; +import { computed, makeObservable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { returnZero } from '../../../ClientUtils'; +import { Doc, DocListCast } from '../../../fields/Doc'; +import { BoolCast, NumCast, StrCast } from '../../../fields/Types'; +import { Transform } from '../../util/Transform'; +import { ObservableReactComponent } from '../ObservableReactComponent'; +import { DocumentView, DocumentViewProps } from '../nodes/DocumentView'; +import './FlashcardPracticeUI.scss'; + +enum practiceMode { + PRACTICE = 'practice', + QUIZ = 'quiz', +} +enum practiceVal { + MISSED = 'missed', + CORRECT = 'correct', +} + +interface PracticeUIProps { + fieldKey: string; + layoutDoc: Doc; + carouselItems: () => Doc[]; + childDocs: Doc[]; + curDoc: () => Doc; + advance: (correct: boolean) => void; + renderDepth: number; + sideBtnWidth: number; + uiBtnScaleTransform: number; + ScreenToLocalBoxXf: () => Transform; + maxWidgetScale: number; + docViewProps: () => DocumentViewProps; + setFilterFunc: (func?: (doc: Doc) => boolean) => void; + practiceBtnOffset?: number; +} +@observer +export class FlashcardPracticeUI extends ObservableReactComponent { + constructor(props: PracticeUIProps) { + super(props); + makeObservable(this); + this._props.setFilterFunc(this.tryFilterOut); + } + + componentWillUnmount(): void { + this._props.setFilterFunc(undefined); + } + + get practiceField() { return this._props.fieldKey + "_practice"; } // prettier-ignore + + @computed get filterDoc() { return DocListCast(Doc.MyContextMenuBtns.data).find(doc => doc.title === 'Filter'); } // prettier-ignore + @computed get practiceMode() { return this._props.childDocs.some(doc => doc._layout_isFlashcard) ? StrCast(this._props.layoutDoc.practiceMode) : ''; } // prettier-ignore + + btnHeight = () => NumCast(this.filterDoc?.height) * Math.min(1, this._props.ScreenToLocalBoxXf().Scale); + btnWidth = () => (!this.filterDoc ? 1 : (this.btnHeight() * NumCast(this.filterDoc._width)) / NumCast(this.filterDoc._height)); + + /** + * Sets the practice mode answer style for flashcards + * @param mode practiceMode or undefined for no practice + */ + setPracticeMode = (mode: practiceMode | undefined) => { + this._props.layoutDoc.practiceMode = mode; + this._props.carouselItems().map(doc => (doc[this.practiceField] = undefined)); + }; + + @computed get emptyMessage() { + const cardCount = this._props.carouselItems().length; + const practiceMessage = this.practiceMode && !Doc.hasDocFilter(this._props.layoutDoc, 'tags', Doc.FilterAny) && !this._props.carouselItems().length ? 'Finished! Click here to view all flashcards.' : ''; + const filterMessage = practiceMessage + ? '' + : Doc.hasDocFilter(this._props.layoutDoc, 'tags', Doc.FilterAny) && !cardCount + ? 'No tagged items. Click here to view all flash cards.' + : this.practiceMode && !cardCount + ? 'No flashcards to show! Click here to leave practice mode' + : ''; + return !practiceMessage && !filterMessage ? null : ( +

{ + if (filterMessage || practiceMessage) { + this.setPracticeMode(undefined); + Doc.setDocFilter(this._props.layoutDoc, 'tags', Doc.FilterAny, 'remove'); + } + }}> + {filterMessage || practiceMessage} +

+ ); + } + + @computed get practiceButtons() { + /* + * Sets a flashcard to either missed or correct depending on if they got the question right in practice mode. + */ + const setPracticeVal = (e: React.MouseEvent, val: string) => { + e.stopPropagation(); + const curDoc = this._props.curDoc(); + curDoc && (curDoc[this.practiceField] = val); + this._props.advance?.(val === practiceVal.CORRECT); + }; + + return this.practiceMode == practiceMode.PRACTICE ? ( +
+ +
setPracticeVal(e, practiceVal.MISSED)}> + +
+
+ +
setPracticeVal(e, practiceVal.CORRECT)}> + +
+
+
+ ) : null; + } + @computed get practiceModesMenu() { + const setColor = (mode: practiceMode) => (StrCast(this.practiceMode) === mode ? 'white' : 'light gray'); + const togglePracticeMode = (mode: practiceMode) => this.setPracticeMode(mode === this.practiceMode ? undefined : mode); + + return !this._props.curDoc()?._layout_isFlashcard ? null : ( +
+ +
togglePracticeMode(practiceMode.QUIZ)}> + +
+
+ +
togglePracticeMode(practiceMode.PRACTICE)}> + +
+
+
+ ); + } + tryFilterOut = (doc: Doc) => (this.practiceMode && BoolCast(doc?._layout_isFlashcard) && doc[this.practiceField] === practiceVal.CORRECT ? true : false); // show only cards that aren't marked as correct + render() { + return ( + <> + {this.emptyMessage} + {this.practiceButtons} +
+ {!this.filterDoc || this._props.layoutDoc._chromeHidden ? null : ( + + )} + {this.practiceModesMenu} +
+ + ); + } +} -- cgit v1.2.3-70-g09d2