diff options
4 files changed, 81 insertions, 11 deletions
diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss index d1731c244..0520e38d4 100644 --- a/src/client/views/collections/CollectionCardDeckView.scss +++ b/src/client/views/collections/CollectionCardDeckView.scss @@ -48,3 +48,15 @@ .card-item-active { z-index: 100; } + +.collectionCardDeckView-flashcards { + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + display: flex; + transform-origin: top left; + pointer-events: none; + z-index: 100; +} diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 5d39dc1ca..7272b22e2 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -22,6 +22,7 @@ import { DocumentView } from '../nodes/DocumentView'; import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup'; import './CollectionCardDeckView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; +import { FlashcardPracticeUI } from './FlashcardPracticeUI'; enum cardSortings { Time = 'time', @@ -46,6 +47,8 @@ export class CollectionCardView extends CollectionSubView() { private _textToDoc = new Map<string, Doc>(); private _dropped = false; // set when a card doc has just moved and the drop method has been called - prevents the pointerUp method from hiding doc decorations (which needs to be done when clicking on a card to animate it to front/center) + _sideBtnWidth = 35; + @observable _filterFunc: ((doc: Doc) => boolean) | undefined = undefined; @observable _forceChildXf = 0; @observable _hoveredNodeIndex = -1; @observable _docRefs = new ObservableMap<Doc, DocumentView>(); @@ -117,7 +120,7 @@ export class CollectionCardView extends CollectionSubView() { * The child documents to be rendered-- everything other than ink/link docs (which are marks as being svg's) */ @computed get childDocsWithoutLinks() { - return this.childDocs.filter(l => !l.layout_isSvg); + return this.childDocs.filter(l => !l.layout_isSvg).filter(doc => !this._filterFunc?.(doc)); } /** @@ -565,11 +568,7 @@ export class CollectionCardView extends CollectionSubView() { */ @computed get renderCards() { if (!this.childDocsWithoutLinks.length) { - return ( - <span className="no-card-span" style={{ width: ` ${this._props.PanelWidth()}px`, height: ` ${this._props.PanelHeight()}px` }}> - Sorry ! There are no cards in this group - </span> - ); + return null; } // Map sorted documents to their rendered components @@ -611,6 +610,34 @@ export class CollectionCardView extends CollectionSubView() { }); } + contentScreenToLocalXf = () => this._props.ScreenToLocalTransform().scale(this._props.NativeDimScaling?.() || 1); + docViewProps = () => ({ + ...this._props, // + isDocumentActive: this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this._props.isContentActive, + isContentActive: this.isChildContentActive, + ScreenToLocalTransform: this.contentScreenToLocalXf, + }); + carouselItemsFunc = () => this.childDocsWithoutLinks; + @action setFilterFunc = (func?: (doc: Doc) => boolean) => { this._filterFunc = func; }; // prettier-ignore + answered = (correct: boolean) => !correct || !this.curDoc(); + curDoc = () => this.sortedDocs.find(doc => DocumentView.getDocumentView(doc, this.DocumentView?.())?.IsSelected); + /** + * How much the content of the carousel view is being scaled based on its nesting and its fit-to-width settings + */ + @computed get contentScaling() { return this.ScreenToLocalBoxXf().Scale * (this._props.NativeDimScaling?.() ?? 1); } // prettier-ignore + + /** + * The maximum size a UI widget can be scaled so that it won't be bigger in screen pixels than its normal 35 pixel size. + */ + @computed get maxWidgetScale() { + const maxWidgetSize = Math.min(this._sideBtnWidth * this.contentScaling, 0.1 * NumCast(this.layoutDoc.width, 1)); + return Math.max(maxWidgetSize / this._sideBtnWidth, 1); + } + /** + * How much to reactively scale a UI element so that it is as big as it can be (up to its normal 35pixel size) without being too big for the Doc content + */ + @computed get uiBtnScaleTransform() { return this.maxWidgetScale * Math.min(1, this.contentScaling); } // prettier-ignore + render() { const isEmpty = this.childDocsWithoutLinks.length === 0; @@ -629,10 +656,35 @@ export class CollectionCardView extends CollectionSubView() { className="card-wrapper" style={{ ...(!isEmpty && { transform: `scale(${1 / this.fitContentScale})` }), - ...(!isEmpty && { height: `${100 * this.fitContentScale}%` }), + ...{ height: `${100 * (isEmpty ? 1 : this.fitContentScale)}%` }, + ...{ width: `${100 * (isEmpty ? 1 : this.fitContentScale)}%` }, gridAutoRows: `${100 / this.numRows}%`, }}> {this.renderCards} + <div + className="collectionCardDeckView-flashcards" + style={{ + transform: `scale(${this.fitContentScale || 1})`, + width: `${100 / (this.fitContentScale || 1)}%`, + height: `${100 / (this.fitContentScale || 1)}%`, + pointerEvents: !this.childDocsWithoutLinks.length ? 'unset' : undefined, + }}> + <FlashcardPracticeUI + setFilterFunc={this.setFilterFunc} + fieldKey={this.fieldKey} + sideBtnWidth={this._sideBtnWidth} + carouselItems={this.carouselItemsFunc} + childDocs={this.childDocs} + advance={this.answered} + curDoc={this.curDoc} + layoutDoc={this.layoutDoc} + maxWidgetScale={this.maxWidgetScale} + uiBtnScaleTransform={this.uiBtnScaleTransform} + ScreenToLocalBoxXf={this.ScreenToLocalBoxXf} + renderDepth={this._props.renderDepth} + docViewProps={this.docViewProps} + /> + </div> </div> </div> ); diff --git a/src/client/views/collections/FlashcardPracticeUI.scss b/src/client/views/collections/FlashcardPracticeUI.scss index 53c26ad34..2f99500f8 100644 --- a/src/client/views/collections/FlashcardPracticeUI.scss +++ b/src/client/views/collections/FlashcardPracticeUI.scss @@ -13,6 +13,11 @@ color: white; } } +.FlashcardPracticeUI-practice { + position: absolute; + width: 100%; + pointer-events: all; +} .FlashcardPracticeUI-remove { left: 52%; } @@ -30,6 +35,7 @@ transform-origin: top left; border-radius: 5px; color: rgba(255, 255, 255, 0.5); + pointer-events: all; background: rgba(0, 0, 0, 0.1); .FlashcardPracticeUI-practiceModes { width: 100%; diff --git a/src/client/views/collections/FlashcardPracticeUI.tsx b/src/client/views/collections/FlashcardPracticeUI.tsx index 032a405bf..072a2edef 100644 --- a/src/client/views/collections/FlashcardPracticeUI.tsx +++ b/src/client/views/collections/FlashcardPracticeUI.tsx @@ -25,7 +25,7 @@ interface PracticeUIProps { layoutDoc: Doc; carouselItems: () => Doc[]; childDocs: Doc[]; - curDoc: () => Doc; + curDoc: () => Doc | undefined; advance: (correct: boolean) => void; renderDepth: number; sideBtnWidth: number; @@ -100,8 +100,8 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp this._props.advance?.(val === practiceVal.CORRECT); }; - return this.practiceMode == practiceMode.PRACTICE ? ( - <div style={{ transform: `scale(${this._props.uiBtnScaleTransform})`, bottom: `${this._props.practiceBtnOffset ?? this._props.sideBtnWidth}px`, height: `${this._props.sideBtnWidth}px`, position: 'absolute', width: `100%` }}> + return this.practiceMode == practiceMode.PRACTICE && this._props.curDoc() ? ( + <div className="FlashcardPracticeUI-practice" style={{ transform: `scale(${this._props.uiBtnScaleTransform})`, bottom: `${this._props.practiceBtnOffset ?? this._props.sideBtnWidth}px`, height: `${this._props.sideBtnWidth}px` }}> <Tooltip title="Incorrect. View again later."> <div key="remove" className="FlashcardPracticeUI-remove" onClick={e => setPracticeVal(e, practiceVal.MISSED)}> <FontAwesomeIcon icon="xmark" color="red" size="1x" /> @@ -119,7 +119,7 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp 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 : ( + return !this._props.childDocs.some(doc => doc._layout_isFlashcard) ? null : ( <div className="FlashcardPracticeUI-practiceModes" style={{ |