import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; import { MultiToggle, Type } from '@dash/components'; import { computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { returnFalse, returnZero, setupMoveUpEvents } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { BoolCast, NumCast, StrCast } from '../../../fields/Types'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { DocumentView } from '../nodes/DocumentView'; import './FlashcardPracticeUI.scss'; import { StyleProp } from '../StyleProp'; import { FieldViewProps } from '../nodes/FieldView'; import { DocumentViewProps } from '../nodes/DocumentContentsView'; export enum practiceMode { PRACTICE = 'practice', QUIZ = 'quiz', } enum practiceVal { MISSED = 'missed', CORRECT = 'correct', } export enum flashcardRevealOp { FLIP = 'flip', SLIDE = 'slide', } interface PracticeUIProps { fieldKey: string; layoutDoc: Doc; filteredChildDocs: () => Doc[]; allChildDocs: () => Doc[]; curDoc: () => Doc | undefined; advance?: (correct: boolean) => void; renderDepth: number; sideBtnWidth: number; uiBtnScaling: number; ScreenToLocalBoxXf: () => Transform; docViewProps: () => DocumentViewProps; setFilterFunc: (func?: (doc: Doc) => boolean) => void; } @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 hasFlashcards() { return this._props.allChildDocs().some(doc => doc._layout_flashcardType); } // prettier-ignore @computed get practiceMode() { return this.hasFlashcards ? StrCast(this._props.layoutDoc.practiceMode) : ''; } // prettier-ignore btnHeight = () => NumCast(this.filterDoc?.height); btnWidth = () => (!this.filterDoc ? 1 : NumCast(this.filterDoc._width)); /** * 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.allChildDocs().map(doc => (doc[this.practiceField] = undefined)); }; @computed get emptyMessage() { const cardCount = this._props.filteredChildDocs().length; const practiceMessage = this.practiceMode && !Doc.hasDocFilter(this._props.layoutDoc, 'tags', Doc.FilterAny) && !cardCount ? '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(); this._props.advance?.(val === practiceVal.CORRECT); curDoc && (curDoc[this.practiceField] = val); }; return this.practiceMode == practiceMode.PRACTICE && this._props.curDoc() ? (
setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => setPracticeVal(e, practiceVal.MISSED))}>
setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => setPracticeVal(e, practiceVal.CORRECT))}>
) : null; } @computed get practiceModesMenu() { const setColor = (mode: practiceMode) => (StrCast(this.practiceMode) === mode ? 'white' : 'lightgray'); const togglePracticeMode = (mode: practiceMode) => this.setPracticeMode(mode === this.practiceMode ? undefined : mode); return !this.hasFlashcards ? null : (
({ icon: , tooltip: tooltip, val: item, }))} selectedItems={this.practiceMode} onSelectionChange={(val: (string | number) | (string | number)[]) => togglePracticeMode(val as practiceMode)} /> ({ icon: , tooltip: tooltip, val: item, }))} selectedItems={this._props.layoutDoc.revealOp_hover ? ['reveal', 'trigger'] : 'reveal'} onSelectionChange={(val: (string | number) | (string | number)[]) => { if (val === 'reveal') this._props.layoutDoc.revealOp = this._props.layoutDoc.revealOp === flashcardRevealOp.SLIDE ? flashcardRevealOp.FLIP : flashcardRevealOp.SLIDE; if (val === 'trigger') this._props.layoutDoc.revealOp_hover = !this._props.layoutDoc.revealOp_hover; }} />
); } childStyleProvider = (doc: Doc | undefined, props: Opt, property: string) => { if (doc instanceof Doc && property === StyleProp.BackgroundColor) { return SnappingManager.userVariantColor; } return this._props.docViewProps().styleProvider?.(doc, props, property); }; tryFilterOut = (doc: Doc) => (this.practiceMode && doc?._layout_flashcardType && doc[this.practiceField] === practiceVal.CORRECT ? true : false); // show only cards that aren't marked as correct render() { return (
{this.emptyMessage} {this.practiceButtons} {this._props.layoutDoc._chromeHidden ? null : (
{!this.filterDoc ? null : (
)} {this.practiceModesMenu}
)}
); } }