From 31b1872be1e8d010d2cbe08e92c589505e86d293 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 22 Jul 2024 15:26:27 -0400 Subject: adding type fixes to avoid 'any's --- src/client/views/collections/CollectionCarousel3DView.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'src/client/views/collections/CollectionCarousel3DView.tsx') diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index 38f681e87..c799eb3c8 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -1,5 +1,3 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; @@ -15,7 +13,7 @@ import { StyleProp } from '../StyleProp'; import { DocumentView } from '../nodes/DocumentView'; import { FocusViewOptions } from '../nodes/FocusViewOptions'; import './CollectionCarousel3DView.scss'; -import { CollectionSubView } from './CollectionSubView'; +import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; // eslint-disable-next-line @typescript-eslint/no-var-requires const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = require('../global/globalCssVariables.module.scss'); @@ -25,7 +23,7 @@ export class CollectionCarousel3DView extends CollectionSubView() { @computed get scrollSpeed() { return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; // default scroll speed } - constructor(props: any) { + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } @@ -181,8 +179,8 @@ export class CollectionCarousel3DView extends CollectionSubView() { className="collectionCarousel3DView-outer" ref={this.createDashEventsTarget} style={{ - background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor), - color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color), + 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} -- cgit v1.2.3-70-g09d2 From 4f2ee4a8642a93fb399b979750078374b317af32 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 17 Sep 2024 12:29:48 -0400 Subject: fixed carouselfView to fade. cleaned up code a bit --- .../views/collections/CollectionCarousel3DView.tsx | 1 + .../views/collections/CollectionCarouselView.scss | 15 ++ .../views/collections/CollectionCarouselView.tsx | 223 ++++++++++++--------- src/client/views/collections/TabDocView.tsx | 4 +- 4 files changed, 151 insertions(+), 92 deletions(-) (limited to 'src/client/views/collections/CollectionCarousel3DView.tsx') diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index c799eb3c8..54cc02825 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -78,6 +78,7 @@ export class CollectionCarousel3DView extends CollectionSubView() { NativeWidth={returnZero} NativeHeight={returnZero} fitWidth={undefined} + containerViewPath={this.childContainerViewPath} onDoubleClickScript={this.onChildDoubleClick} renderDepth={this._props.renderDepth + 1} LayoutTemplate={this._props.childLayoutTemplate} diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss index f115bb40a..01b20d6d3 100644 --- a/src/client/views/collections/CollectionCarouselView.scss +++ b/src/client/views/collections/CollectionCarouselView.scss @@ -12,6 +12,21 @@ user-select: none; } } +.collectionCarouselView-addFlashcards { + justify-content: center; + align-items: center; + height: 100%; + z-index: -1; + pointer-events: none; +} +.collectionCarouselView-recentlyMissed { + color: red; + z-index: 999; + position: relative; + left: 10px; + top: 10px; + pointer-events: none; +} .carouselView-back, .carouselView-fwd, .carouselView-star, diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 4bec2d963..5d71177c3 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -1,12 +1,11 @@ /* eslint-disable react/jsx-props-no-spreading */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { computed, makeObservable } from 'mobx'; +import { IReactionDisposer, action, computed, makeObservable, observable, reaction } 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 { Doc, Opt } from '../../../fields/Doc'; -import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { StopEvent, returnOne, returnZero } from '../../../ClientUtils'; +import { Doc, DocListCast, 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'; @@ -32,13 +31,34 @@ export class CollectionCarouselView extends CollectionSubView() { get practiceField() { return this.fieldKey + "_practice"; } // prettier-ignore get starField() { return this.fieldKey + "_star"; } // prettier-ignore + _fadeTimer: NodeJS.Timeout | undefined; + _resetter: IReactionDisposer | undefined; + 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) => { @@ -48,43 +68,24 @@ export class CollectionCarouselView extends CollectionSubView() { } }; + @computed get marginX() { 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.childLayoutPairs.filter(pair => pair.layout.type !== DocumentType.LINK); - } - @computed get marginX() { - return NumCast(this.layoutDoc.caption_xMargin, 50); + return DocListCast(this.childDocList) + .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 + }); } - 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 - }; + move = action((dir: number) => { + this._last_index = this.carouselIndex; + this.layoutDoc._carousel_index = (this.carouselIndex + dir + this.carouselItems.length) % this.carouselItems.length; + }); /** * Goes to the next Doc in the stack subject to the currently selected filter option. @@ -107,8 +108,8 @@ export class CollectionCarouselView extends CollectionSubView() { */ 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; + const curDoc = this.carouselItems[this.carouselIndex]; + curDoc && (curDoc[this.starField] = curDoc[this.starField] ? undefined : true); }; /* @@ -116,8 +117,8 @@ export class CollectionCarouselView extends CollectionSubView() { */ setPracticeVal = (e: React.MouseEvent, val: string) => { e.stopPropagation(); - const curDoc = this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)]; - curDoc.layout[this.practiceField] = val; + const curDoc = this.carouselItems[this.carouselIndex]; + curDoc && (curDoc[this.practiceField] = val); this.advance(e); }; @@ -132,7 +133,6 @@ export class CollectionCarouselView extends CollectionSubView() { 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 @@ -141,32 +141,78 @@ export class CollectionCarouselView extends CollectionSubView() { 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' }); }; + + isChildContentActive = () => + this._props.isContentActive?.() === false + ? false + : this._props.isDocumentActive?.() && (this._props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive)) + ? true + : this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false + ? false + : undefined; + + renderDoc = (doc: Doc, showCaptions: boolean, overlayFunc?: (r: DocumentView | null) => void) => { + return ( + + ); + }; + /** + * Display an overlay of the previous card that crossfades to the next card + */ + @computed get overlay() { + const fadeTime = 500; + const lastDoc = this.carouselItems?.[this._last_index]; + return !lastDoc || this.carouselIndex === this._last_index ? null : ( +
+ {this.renderDoc( + lastDoc, + false, // hide captions if the carousel is configured to show the captions + action((r: DocumentView | null) => { + if (r) { + this._fadeTimer && clearTimeout(this._fadeTimer); + this._last_opacity = 0; + this._fadeTimer = setTimeout( + action(() => { + this._last_index = -1; + this._last_opacity = 1; + }), + fadeTime + ); + } + }) + )} +
+ ); + } @computed get content() { - const index = NumCast(this.layoutDoc._carousel_index); + 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?.layout instanceof Doc) ? null : ( + return !curDoc ? null : ( <>
- + {this.renderDoc(curDoc, !!carouselShowsCaptions)} + {this.overlay}
{!carouselShowsCaptions ? null : (
- +
)} ); } @computed get buttons() { - if (!this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)]) return null; + if (!this.carouselItems?.[this.carouselIndex]) return null; return ( <>
@@ -196,7 +242,7 @@ export class CollectionCarouselView extends CollectionSubView() {
- +
this.setPracticeVal(e, practiceVal.MISSED)} style={{ visibility: this.layoutDoc.filterOp === cardMode.PRACTICE ? 'visible' : 'hidden' }}> @@ -208,6 +254,24 @@ export class CollectionCarouselView extends CollectionSubView() { ); } + /** + * Prompts user to add more flashcaards if they are in practice mode but there are no flashcards + */ + renderAddFlashcards = () =>

+ Add flashcards! +

// prettier-ignore + + /** + * Displays message that a flashcard was recently missed if it had previously been marked as wrong. + * */ + renderRecentlyMissed = () =>

+ Recently missed! +

// prettier-ignore + render() { return (
{this.content} - {/* Displays a message to the user to add more flashcards if they are in practice mode and no flashcards are there. */} -

- Add flashcards! -

- {/* Displays a message to the user that a flashcard was recently missed if they had previously gotten it wrong. */} -

- Recently missed! -

+ {this.renderAddFlashcards()} + {this.renderRecentlyMissed()} {this.Document._chromeHidden ? null : this.buttons}
); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index def1ea731..f56ea9d76 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -506,13 +506,13 @@ export class TabDocView extends ObservableReactComponent { } // eslint-disable-next-line @typescript-eslint/no-explicit-any - onActiveContentItemChanged = (contentItem: any) => { + onActiveContentItemChanged = action((contentItem: any) => { if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) { this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab; if (!this._view && this.tab?.contentItem?.config?.props?.panelName !== TabDocView.DontSelectOnActivate) setTimeout(() => DocumentView.SelectView(this._view, false)); !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one. } - }; + }); // adds a tab to the layout based on the locaiton parameter which can be: // close[:{left,right,top,bottom}] - e.g., "close" will close the tab, "close:left" will close the left tab, -- cgit v1.2.3-70-g09d2 From 0da361d49f063d43a0ad87d13665595f9e383e13 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 24 Sep 2024 17:44:49 -0400 Subject: fixed screen to local for carousel views (especially when in lightbox) --- .../views/collections/CollectionCarousel3DView.tsx | 35 +++++++++++++++++----- .../views/collections/CollectionCarouselView.tsx | 3 ++ 2 files changed, 30 insertions(+), 8 deletions(-) (limited to 'src/client/views/collections/CollectionCarousel3DView.tsx') diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index 54cc02825..139aebb02 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -14,6 +14,7 @@ import { DocumentView } from '../nodes/DocumentView'; import { FocusViewOptions } from '../nodes/FocusViewOptions'; import './CollectionCarousel3DView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; +import { Transform } from '../../util/Transform'; // eslint-disable-next-line @typescript-eslint/no-var-requires const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = require('../global/globalCssVariables.module.scss'); @@ -46,15 +47,32 @@ export class CollectionCarousel3DView extends CollectionSubView() { } centerScale = Number(CAROUSEL3D_CENTER_SCALE); + sideScale = Number(CAROUSEL3D_SIDE_SCALE); panelWidth = () => this._props.PanelWidth() / 3; - panelHeight = () => this._props.PanelHeight() * Number(CAROUSEL3D_SIDE_SCALE); + panelHeight = () => this._props.PanelHeight() * this.sideScale; onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick); isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive(); isChildContentActive = () => !!this.isContentActive(); - childScreenToLocal = () => - this._props // document's left is the panel shifted by the doc's index * panelWidth/#docs. But it scales by centerScale around its center, so it's left moves left by the distance of the left from the center (panelwidth/2) * the scale delta (centerScale-1) - .ScreenToLocalTransform() // the top behaves the same way ecept it's shifted by the 'top' amount specified for the panel in css and then by the scale factor. - .translate(-this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2, -((Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2) + childScreenLeftToLocal = () => + this._props + .ScreenToLocalTransform() + .scale(this._props.NativeDimScaling?.() || 1) + .translate(-(this.panelWidth() - this.panelWidth() * this.sideScale) / 2, -(this.panelHeight() - this.panelHeight() * this.sideScale) / 2 - (Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + .scale(1 / this.sideScale); + childScreenRightToLocal = () => + this._props + .ScreenToLocalTransform() + .scale(this._props.NativeDimScaling?.() || 1) + .translate(-2 * this.panelWidth() - (this.panelWidth() - this.panelWidth() * this.sideScale) / 2, -(this.panelHeight() - this.panelHeight() * this.sideScale) / 2 - (Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + .scale(1 / this.sideScale); + childCenterScreenToLocal = () => + this._props + .ScreenToLocalTransform() + .scale(this._props.NativeDimScaling?.() || 1) + .translate( + -this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2, // Focused Doc is shifted right by 1/3 panel width then left by increased size percent of center * 1/2 * panel width / 3 + -((Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2 + ) // top is top margin % of panelHeight - increased size percent of center * panelHeight / 2 .scale(1 / this.centerScale); focus = (anchor: Doc, options: FocusViewOptions): Opt => { @@ -66,9 +84,10 @@ export class CollectionCarousel3DView extends CollectionSubView() { index !== -1 && (this.layoutDoc._carousel_index = index); return undefined; }; + @computed get content() { const currentIndex = NumCast(this.layoutDoc._carousel_index); - const displayDoc = (childPair: { layout: Doc; data: Doc }) => ( + const displayDoc = (childPair: { layout: Doc; data: Doc }, dxf: () => Transform) => ( (
- {displayDoc(childPair)} + {displayDoc(childPair, index < currentIndex ? this.childScreenLeftToLocal : index === currentIndex ? this.childCenterScreenToLocal : this.childScreenRightToLocal)}
)); } diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 974cd3e36..75886b30d 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -154,6 +154,8 @@ export class CollectionCarouselView extends CollectionSubView() { ? false : undefined; + childScreenToLocalXf = () => this._props.ScreenToLocalTransform().scale(this._props.NativeDimScaling?.() || 1); + renderDoc = (doc: Doc, showCaptions: boolean, overlayFunc?: (r: DocumentView | null) => void) => { return (