diff options
Diffstat (limited to 'src/client/views/collections/CollectionCarouselView.tsx')
-rw-r--r-- | src/client/views/collections/CollectionCarouselView.tsx | 241 |
1 files changed, 189 insertions, 52 deletions
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 4bec2d963..ba7c944a0 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -1,6 +1,7 @@ /* eslint-disable react/jsx-props-no-spreading */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { computed, makeObservable } from 'mobx'; +import { Tooltip } from '@mui/material'; +import { action, computed, makeObservable, observable, trace } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { StopEvent, returnFalse, returnOne, returnTrue, returnZero } from '../../../ClientUtils'; @@ -8,8 +9,8 @@ import { emptyFunction } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { DocumentType } from '../../documents/DocumentTypes'; +import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; -import { ContextMenu } from '../ContextMenu'; import { StyleProp } from '../StyleProp'; import { DocumentView } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; @@ -18,9 +19,15 @@ import './CollectionCarouselView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; enum cardMode { - PRACTICE = 'practice', + // PRACTICE = 'practice', STAR = 'star', + // QUIZ = 'quiz', + ALL = 'all', +} +enum practiceMode { + PRACTICE = 'practice', QUIZ = 'quiz', + NORMAL = 'normal', } enum practiceVal { MISSED = 'missed', @@ -29,12 +36,22 @@ enum practiceVal { @observer export class CollectionCarouselView extends CollectionSubView() { private _dropDisposer?: DragManager.DragDropDisposer; + @observable private _practiceMessage: string | undefined; + @observable private _filterMessage: string | undefined; get practiceField() { return this.fieldKey + "_practice"; } // prettier-ignore - get starField() { return this.fieldKey + "_star"; } // prettier-ignore + get sideField() { return "_" + this.fieldKey + "_usePath"; } // prettier-ignore + get starField() { return "star"; } // prettier-ignore constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); + // this.setModes(); + this.layoutDoc.filterOp = cardMode.ALL; + Doc.setDocFilter(this.Document, 'star', undefined, 'match'); + this.layoutDoc.practiceMode = practiceMode.NORMAL; + this.layoutDoc._carousel_index = 0; + this.carouselItems.forEach(item => { item.layout[this.practiceField] = undefined}); //prettier-ignore + console.log(this.carouselItems.length); } componentWillUnmount() { @@ -49,16 +66,33 @@ export class CollectionCarouselView extends CollectionSubView() { }; @computed get carouselItems() { + this.childLayoutPairs.map(pair => { + pair.layout.embedContainer = this.Document; + }); return this.childLayoutPairs.filter(pair => pair.layout.type !== DocumentType.LINK); } @computed get marginX() { return NumCast(this.layoutDoc.caption_xMargin, 50); } + @action setPracticeMessage = (mes: string | undefined) => { + this._practiceMessage = mes; + }; + @action setFilterMessage = (mes: string | undefined) => { + this._filterMessage = mes; + }; + + setModes = () => { + this.layoutDoc.filterOp = cardMode.ALL; + Doc.setDocFilter(this.Document, 'data_star', undefined, 'match'); + this.layoutDoc.practiceMode = practiceMode.NORMAL; + this.layoutDoc._carousel_index = 0; + }; + 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) { + while (!match(this.carouselItems?.[startInd].layout) && (startInd + 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)) { @@ -67,23 +101,28 @@ export class CollectionCarouselView extends CollectionSubView() { } 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 + + switch (this.layoutDoc.practiceMode && this.layoutDoc.filterOp) { + case practiceMode.PRACTICE && cardMode.ALL: + if (!moveToCardWithField((doc: Doc) => doc[this.practiceField] !== practiceVal.CORRECT)) { + this._practiceMessage = 'Finished! Unselect practice mode to view all flashcards.'; + this.carouselItems.forEach(item => { item.layout[this.practiceField] = undefined}); //prettier-ignore + } + break; + case !practiceMode.PRACTICE && cardMode.STAR: if (!moveToCardWithField((doc: Doc) => !!doc[this.starField])) { - this.layoutDoc.filterOp = undefined; // if there aren't any starred, show all cards + this._filterMessage = 'No starred items. Unselect this view to see all flashcards and star them.'; } 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; - }); + case practiceMode.PRACTICE && cardMode.STAR: + if (!moveToCardWithField((doc: Doc) => doc[this.practiceField] !== practiceVal.CORRECT && doc[this.starField] === true)) { + this._filterMessage = 'No flashcards to show! Unselect mode to view all flashcards.'; + this._practiceMessage = undefined; } break; - default: moveToCardWithField(returnTrue); - } // prettier-ignore + default: + moveToCardWithField(returnTrue); + } }; /** @@ -109,6 +148,8 @@ export class CollectionCarouselView extends CollectionSubView() { e.stopPropagation(); const curDoc = this.carouselItems[NumCast(this.layoutDoc._carousel_index)]; curDoc.layout[this.starField] = curDoc.layout[this.starField] ? undefined : true; + // if (!curDoc.layout[this.starField]) this.move(1); + // this.layoutDoc._carousel_index = undefined; }; /* @@ -130,22 +171,43 @@ export class CollectionCarouselView extends CollectionSubView() { onContentDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick); onContentClick = () => ScriptCast(this.layoutDoc.onChildClick); captionWidth = () => this._props.PanelWidth() - 2 * this.marginX; - specificMenu = (): void => { - const cm = ContextMenu.Instance; - - const revealOptions = cm.findByDescription('Filter Flashcards'); - const revealItems = 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' }); + + setPracticeMode = (mode: practiceMode) => { + this.layoutDoc.practiceMode = mode; + this.carouselItems?.map(doc => (doc.layout[this.practiceField] = undefined)); + switch (mode) { + case practiceMode.QUIZ: + this.carouselItems?.map(doc => (doc.layout[this.sideField] = undefined)); + break; + case practiceMode.NORMAL: + this.setPracticeMessage(undefined); + break; + } }; + + setFilterMode = (mode: cardMode) => { + this.layoutDoc.filterOp = mode; + switch (mode) { + case cardMode.STAR: + // Doc.setDocFilter(this.Document, 'data_star', true, 'match'); + this.move(1); + break; + default: + this.setFilterMessage(undefined); // prettier-ignore + // Doc.setDocFilter(this.Document, 'data_star', true, 'remove'); + } + }; + @computed get content() { + trace(); + if (this.layoutDoc._carousel_index === this.carouselItems.length && this.layoutDoc._carousel_index !== 0) { + this.move(1); + } const index = NumCast(this.layoutDoc._carousel_index); const curDoc = this.carouselItems?.[index]; const captionProps = { ...this._props, NativeScaling: returnOne, PanelWidth: this.captionWidth, fieldKey: 'caption', setHeight: undefined, setContentView: undefined }; const carouselShowsCaptions = StrCast(this.layoutDoc._layout_showCaption); + return !(curDoc?.layout instanceof Doc) ? null : ( <> <div className="collectionCarouselView-image" key="image"> @@ -155,10 +217,12 @@ export class CollectionCarouselView extends CollectionSubView() { NativeHeight={returnZero} fitWidth={undefined} setContentViewBox={undefined} + childFilters={this.childDocFilters} onDoubleClickScript={this.onContentDoubleClick} onClickScript={this.onContentClick} isDocumentActive={this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this._props.isContentActive} isContentActive={(this._props.childContentsActive ?? this._props.isContentActive() === false) ? returnFalse : emptyFunction} + addDocument={this._props.addDocument} hideCaptions={!!carouselShowsCaptions} // hide captions if the carousel is configured to show the captions renderDepth={this._props.renderDepth + 1} LayoutTemplate={this._props.childLayoutTemplate} @@ -185,6 +249,25 @@ export class CollectionCarouselView extends CollectionSubView() { </> ); } + + containsDifTypes = (): boolean => { + return this.carouselItems.filter(doc => !doc.layout._layout_isFlashcard).length !== 0; + }; + + addFlashcard() { + const newDoc = Docs.Create.ComparisonDocument('', { _layout_isFlashcard: true, _width: 300, _height: 300 }); + this.addDocument?.(newDoc); + // DocUtils.copyDragFactory(newDoc); + // this._props.addDocument?.(); + // newDoc.layout = this.layoutDoc; + // newDoc.data = this.dataDoc; + // Doc.AddDocToList() + // this._props.parent._props.addDocument(); + // this.childLayoutPairs.push({ newDoc.layout, newDoc.data}); + // this._props.addDocument?.(newDoc); + // console.log('HERE'); + } + @computed get buttons() { if (!this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)]) return null; return ( @@ -195,54 +278,108 @@ 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> + {!this.containsDifTypes() ? ( + <div> + <Tooltip title="star"> + <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> + </Tooltip> + {/* <Tooltip title="add new flashcard to pile"> + <div key="add" className="carouselView-add" onClick={this.addFlashcard}> + <FontAwesomeIcon icon="plus" color={this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)].layout[this.starField] ? 'yellow' : 'gray'} size="1x" /> + </div> + </Tooltip> */} + </div> + ) : null} + {this.layoutDoc.practiceMode === practiceMode.PRACTICE ? ( + <div> + <Tooltip title="Incorrect. View again later."> + <div key="remove" className="carouselView-remove" onClick={e => this.setPracticeVal(e, practiceVal.MISSED)}> + <FontAwesomeIcon icon="xmark" color="red" size="1x" /> + </div> + </Tooltip> + <Tooltip title="Correct"> + <div key="check" className="carouselView-check" onClick={e => this.setPracticeVal(e, practiceVal.CORRECT)}> + <FontAwesomeIcon icon="check" color="green" size="1x" /> + </div> + </Tooltip> + </div> + ) : null} </> ); } + togglePracticeMode = (mode: practiceMode) => { + if (mode === this.layoutDoc.practiceMode) { + this.setPracticeMode(practiceMode.NORMAL); + // this.setPracticeMessage("undefined"); + } else this.setPracticeMode(mode); + }; + toggleFilterMode = () => { this.layoutDoc.filterOp === cardMode.STAR ? this.setFilterMode(cardMode.ALL) : this.setFilterMode(cardMode.STAR)}; //prettier-ignore + setColor = (mode: practiceMode | cardMode, which: string) => { return which === mode ? 'white' : 'light gray'}; //prettier-ignore + + @computed get menu() { + return ( + <div className="carouselView-menu"> + <Tooltip title="Practice flashcards using GPT"> + <div key="back" className="carouselView-quiz" onClick={e => this.togglePracticeMode(practiceMode.QUIZ)}> + <FontAwesomeIcon icon="file-pen" color={this.setColor(practiceMode.QUIZ, StrCast(this.layoutDoc.practiceMode))} size="1x" /> + </div> + </Tooltip> + <Tooltip title={this.layoutDoc.practiceMode === practiceMode.PRACTICE ? 'Exit practice mode' : 'Practice flashcards manually'}> + <div key="back" className="carouselView-practice" onClick={e => this.togglePracticeMode(practiceMode.PRACTICE)}> + <FontAwesomeIcon icon="check" color={this.setColor(practiceMode.PRACTICE, StrCast(this.layoutDoc.practiceMode))} size="1x" /> + </div> + </Tooltip> + <Tooltip title={this.layoutDoc.filterOp === cardMode.STAR ? 'Show all cards' : 'Show only starred cards'}> + <div key="back" className="carouselView-starFilter" onClick={e => this.toggleFilterMode()}> + <FontAwesomeIcon icon="filter" color={this.setColor(cardMode.STAR, StrCast(this.layoutDoc.filterOp))} size="1x" /> + </div> + </Tooltip> + </div> + ); + } + render() { return ( <div className="collectionCarouselView-outer" ref={this.createDashEventsTarget} - onContextMenu={this.specificMenu} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string, color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string, }}> - {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. */} + {!this._practiceMessage && !this._filterMessage ? ( + this.content + ) : ( + <p className="message"> + {this._filterMessage} + {'\n'} + {this._practiceMessage} + </p> + )} + {!this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)] && this.layoutDoc.practiceMode === practiceMode.PRACTICE ? <p className="message">Add flashcards </p> : null} <p + className="missed-message" style={{ color: 'red', + fontWeight: 'bold', 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', + width: '10px', + display: this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)] + ? this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)].layout[this.practiceField] === practiceVal.MISSED && this.layoutDoc.practiceMode === practiceMode.PRACTICE && !this._practiceMessage + ? 'block' + : 'none' + : 'none', }}> Recently missed! </p> - {this.Document._chromeHidden ? null : this.buttons} + {!this.containsDifTypes() && this.carouselItems.length !== 0 ? this.menu : null} + {this.Document._chromeHidden || (!this._filterMessage && !this._practiceMessage) ? this.buttons : null} </div> ); } |