aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/CollectionCarouselView.tsx
diff options
context:
space:
mode:
authorNathan-SR <144961007+Nathan-SR@users.noreply.github.com>2024-06-03 13:33:37 -0400
committerNathan-SR <144961007+Nathan-SR@users.noreply.github.com>2024-06-03 13:33:37 -0400
commit9e77f980e7704999ef0a1c1845d660bccb13ff8a (patch)
tree14ca0da5915e4382a7bcb15f7d0b241941c8291f /src/client/views/collections/CollectionCarouselView.tsx
parent1be63695875c9242fba43d580465e8765cf3991d (diff)
parent202e994515392892676f8f080852db1e32b8dbd3 (diff)
Merge branch 'master' into nathan-starter
Diffstat (limited to 'src/client/views/collections/CollectionCarouselView.tsx')
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx128
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>
);