diff options
Diffstat (limited to 'src/client/views/collections/CollectionCarouselView.tsx')
| -rw-r--r-- | src/client/views/collections/CollectionCarouselView.tsx | 191 |
1 files changed, 60 insertions, 131 deletions
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 8b3a699ed..aa447c7bf 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -1,64 +1,33 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx'; +import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { StopEvent, returnOne, returnZero } from '../../../ClientUtils'; import { Doc, Opt } from '../../../fields/Doc'; import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; -import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; -import { ContextMenu } from '../ContextMenu'; import { StyleProp } from '../StyleProp'; -import { TagItem } from '../TagsView'; import { DocumentView } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import './CollectionCarouselView.scss'; import { CollectionSubView, SubCollectionViewProps } 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 "#star"; } // prettier-ignore _fadeTimer: NodeJS.Timeout | undefined; - _resetter: IReactionDisposer | undefined; + @observable _last_index = this.carouselIndex; + @observable _last_opacity = 1; constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } - @observable _last_index = this.carouselIndex; - @observable _last_opacity = 1; - - componentDidMount() { - this._resetter = reaction( - // automatically reset practice fields when all cards have been marked as correct - () => this.carouselItems.length, - itemsCount => { - if (this.layoutDoc.filterOp === cardMode.PRACTICE && !itemsCount) { - 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[this.practiceField] = undefined; - }); - } - } // prettier-ignore - ); - } componentWillUnmount() { this._dropDisposer?.(); - this._resetter?.(); } protected createDashEventsTarget = (ele: HTMLDivElement | null) => { @@ -68,30 +37,24 @@ export class CollectionCarouselView extends CollectionSubView() { } }; - @computed get marginX() { return NumCast(this.layoutDoc.caption_xMargin, 50); } // prettier-ignore + @computed get captionMarginX(){ return NumCast(this.layoutDoc.caption_xMargin, 50); } // prettier-ignore @computed get carouselIndex() { return NumCast(this.layoutDoc._carousel_index) % this.carouselItems.length; } // prettier-ignore - @computed get carouselItems() { - return this.childDocs - .filter(doc => doc.type !== DocumentType.LINK) - .filter(doc => { - switch (StrCast(this.layoutDoc.filterOp)) { - case cardMode.STAR: return !!doc[this.starField]; // show only cards that are starred - case cardMode.PRACTICE: return doc[this.practiceField] !== practiceVal.CORRECT;// show only cards that aren't marked as correct - default: return true; - } // prettier-ignore - }); - } + @computed get carouselItems() { return this.childLayoutPairs.filter(pair => !pair.layout.layout_isSvg); } // prettier-ignore + /** + * Move forward or backward the specified number of Docs + * @param dir signed number indicating Docs to move forward or backward + */ move = action((dir: number) => { this._last_index = this.carouselIndex; - this.layoutDoc._carousel_index = (this.carouselIndex + dir + this.carouselItems.length) % this.carouselItems.length; + this.layoutDoc._carousel_index = this.carouselItems.length ? (this.carouselIndex + dir + this.carouselItems.length) % this.carouselItems.length : 0; }); /** * Goes to the next Doc in the stack subject to the currently selected filter option. */ - advance = (e: React.MouseEvent) => { - e.stopPropagation(); + advance = (e?: React.MouseEvent) => { + e?.stopPropagation(); this.move(1); }; @@ -103,48 +66,23 @@ export class CollectionCarouselView extends CollectionSubView() { this.move(-1); }; - /* - * Stars the document when the star button is pressed. - */ - star = (e: React.MouseEvent) => { - e.stopPropagation(); - const curDoc = this.carouselItems[this.carouselIndex]; - if (curDoc) { - if (TagItem.docHasTag(curDoc, this.starField)) TagItem.removeTagFromDoc(curDoc, this.starField); - else TagItem.addTagToDoc(curDoc, this.starField); - } - }; - - /* - * 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[this.carouselIndex]; - curDoc && (curDoc[this.practiceField] = val); - this.advance(e); - }; + curDoc = () => this.carouselItems[this.carouselIndex]?.layout; captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<FieldViewProps>, property: string) => { // 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; return childValue ?? this._props.styleProvider?.(this.layoutDoc, captionProps, property); }; - panelHeight = () => this._props.PanelHeight() - (StrCast(this.layoutDoc._layout_showCaption) ? 50 : 0); + contentPanelWidth = () => this._props.PanelWidth() - 2 * NumCast(this.layoutDoc.xMargin); + contentPanelHeight = () => this._props.PanelHeight() - (StrCast(this.layoutDoc._layout_showCaption) ? 50 : 0) - 2 * NumCast(this.layoutDoc.yMargin); 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' }); - }; - + captionWidth = () => this._props.PanelWidth() - 2 * this.captionMarginX; + contentScreenToLocalXf = () => + this._props + .ScreenToLocalTransform() + .translate(-NumCast(this.layoutDoc.xMargin), -NumCast(this.layoutDoc.yMargin)) + .scale(this._props.NativeDimScaling?.() || 1); isChildContentActive = () => this._props.isContentActive?.() === false ? false @@ -153,9 +91,6 @@ export class CollectionCarouselView extends CollectionSubView() { : this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false ? false : undefined; - - childScreenToLocalXf = () => this._props.ScreenToLocalTransform().scale(this._props.NativeDimScaling?.() || 1); - renderDoc = (doc: Doc, showCaptions: boolean, overlayFunc?: (r: DocumentView | null) => void) => { return ( <DocumentView @@ -165,10 +100,10 @@ export class CollectionCarouselView extends CollectionSubView() { NativeWidth={returnZero} NativeHeight={returnZero} fitWidth={this._props.childLayoutFitWidth} + hideFilterStatus={true} showTags={BoolCast(this.layoutDoc.showChildTags)} containerViewPath={this.childContainerViewPath} setContentViewBox={undefined} - ScreenToLocalTransform={this.childScreenToLocalXf} onDoubleClickScript={this.onContentDoubleClick} onClickScript={this.onContentClick} isDocumentActive={this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this._props.isContentActive} @@ -178,8 +113,13 @@ export class CollectionCarouselView extends CollectionSubView() { LayoutTemplate={this._props.childLayoutTemplate} LayoutTemplateString={this._props.childLayoutString} TemplateDataDocument={DocCast(Doc.Layout(doc).resolvedDataDoc)} - xPadding={35} - PanelHeight={this.panelHeight} + childFilters={this.childDocFilters} + hideDecorations={BoolCast(this.layoutDoc.layout_hideDecorations)} + addDocument={this._props.addDocument} + ScreenToLocalTransform={this.contentScreenToLocalXf} + PanelWidth={this.contentPanelWidth} + PanelHeight={this.contentPanelHeight} + screenXPadding={this.screenXPadding} /> ); }; @@ -188,9 +128,9 @@ export class CollectionCarouselView extends CollectionSubView() { */ @computed get overlay() { const fadeTime = 500; - const lastDoc = this.carouselItems?.[this._last_index]; + const lastDoc = this.carouselItems?.[this._last_index]?.layout; return !lastDoc || this.carouselIndex === this._last_index ? null : ( - <div className="collectionCarouselView-image" style={{ opacity: this._last_opacity, position: 'absolute', top: 0, left: 0, transition: `opacity ${fadeTime}ms` }}> + <div className="collectionCarouselView-image" style={{ opacity: this._last_opacity, transition: `opacity ${fadeTime}ms` }}> {this.renderDoc( lastDoc, false, // hide captions if the carousel is configured to show the captions @@ -211,15 +151,18 @@ export class CollectionCarouselView extends CollectionSubView() { </div> ); } + @computed get renderedDoc() { + const carouselShowsCaptions = StrCast(this.layoutDoc._layout_showCaption); + return this.renderDoc(this.curDoc(), !!carouselShowsCaptions); + } + @computed get content() { - const index = this.carouselIndex; - 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 ? null : ( + return !this.curDoc() ? null : ( <> <div className="collectionCarouselView-image" key="image"> - {this.renderDoc(curDoc, !!carouselShowsCaptions)} + {this.renderedDoc} {this.overlay} </div> {!carouselShowsCaptions ? null : ( @@ -229,68 +172,54 @@ export class CollectionCarouselView extends CollectionSubView() { onWheel={StopEvent} style={{ borderRadius: this._props.styleProvider?.(this.layoutDoc, captionProps, StyleProp.BorderRounding) as string, - marginRight: this.marginX, - marginLeft: this.marginX, - width: `calc(100% - ${this.marginX * 2}px)`, + marginRight: this.captionMarginX, + marginLeft: this.captionMarginX, + width: `calc(100% - ${this.captionMarginX * 2}px)`, }}> - <FormattedTextBox key={index} xPadding={10} yPadding={10} {...captionProps} fieldKey={carouselShowsCaptions} styleProvider={this.captionStyleProvider} Document={curDoc} TemplateDataDocument={undefined} /> + <FormattedTextBox xPadding={10} yPadding={10} {...captionProps} fieldKey={carouselShowsCaptions} styleProvider={this.captionStyleProvider} Document={this.curDoc()} TemplateDataDocument={undefined} /> </div> )} </> ); } - @computed get buttons() { - if (!this.carouselItems?.[this.carouselIndex]) return null; - return ( + + @computed get navButtons() { + return !this.curDoc() ? null : ( <> - <div key="back" className="carouselView-back" onClick={this.goback}> + <div key="back" className="carouselView-back" style={{ transform: `scale(${this.uiBtnScaling})` }} onClick={this.goback}> <FontAwesomeIcon icon="chevron-left" size="2x" /> </div> - <div key="fwd" className="carouselView-fwd" onClick={this.advance}> + <div key="fwd" className="carouselView-fwd" style={{ transform: `scale(${this.uiBtnScaling})` }} onClick={this.advance}> <FontAwesomeIcon icon="chevron-right" size="2x" /> </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> </> ); } - /** - * Prompts user to add more flashcaards if they are in practice mode but there are no flashcards - */ - renderAddFlashcards = () => <p - className="collectionCarouselView-addFlashcards" - style={{display: !this.carouselItems?.[this.carouselIndex] && this.layoutDoc.filterOp === cardMode.PRACTICE ? 'flex' : 'none'}}> - Add flashcards! - </p> // prettier-ignore - - /** - * Displays message that a flashcard was recently missed if it had previously been marked as wrong. - * */ - renderRecentlyMissed = () => <p - className="collectionCarouselView-recentlyMissed" - style={{display: this.carouselItems?.[this.carouselIndex]?.[this.practiceField] === practiceVal.MISSED ? 'block' : 'none'}}> - Recently missed! - </p> // prettier-ignore + docViewProps = () => ({ + ...this._props, // + isDocumentActive: this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this._props.isContentActive, + isContentActive: this.isChildContentActive, + ScreenToLocalTransform: this.contentScreenToLocalXf, + }); + answered = () => this.advance(); 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, + width: `calc(100% - ${NumCast(this.layoutDoc._xMargin)}px)`, + height: `calc(100% - ${NumCast(this.layoutDoc._yMargin)}px)`, + left: NumCast(this.layoutDoc._xMargin), + top: NumCast(this.layoutDoc._yMargin), }}> {this.content} - {this.renderAddFlashcards()} - {this.renderRecentlyMissed()} - {this.Document._chromeHidden ? null : this.buttons} + {this.flashCardUI(this.curDoc, this.docViewProps, this.answered)} + {this.navButtons} </div> ); } |
