diff options
Diffstat (limited to 'src')
10 files changed, 113 insertions, 207 deletions
diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss index 0520e38d4..0637cd4e9 100644 --- a/src/client/views/collections/CollectionCardDeckView.scss +++ b/src/client/views/collections/CollectionCardDeckView.scss @@ -6,6 +6,7 @@ position: relative; background-color: white; overflow: hidden; + display: flex; button { border-radius: 50%; @@ -48,15 +49,3 @@ .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 7272b22e2..8f351d8a7 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -22,7 +22,6 @@ 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', @@ -47,8 +46,6 @@ 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>(); @@ -119,15 +116,15 @@ 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).filter(doc => !this._filterFunc?.(doc)); + @computed get childCards() { + return this.childLayoutPairs.filter(pair => !pair.layout.layout_isSvg); } /** * how much to scale down the contents of the view so that everything will fit */ @computed get fitContentScale() { - const length = Math.min(this.childDocsWithoutLinks.length, this._maxRowCount); + const length = Math.min(this.childCards.length, this._maxRowCount); return (this.childPanelWidth() * length) / this._props.PanelWidth(); } @@ -280,7 +277,12 @@ export class CollectionCardView extends CollectionSubView() { ); @computed get sortedDocs() { - return this.sort(this.childDocsWithoutLinks, this.cardSort, BoolCast(this.Document.cardSort_isDesc), this._docDraggedIndex); + return this.sort( + this.childCards.map(card => card.layout), + this.cardSort, + BoolCast(this.Document.cardSort_isDesc), + this._docDraggedIndex + ); } /** @@ -428,12 +430,14 @@ export class CollectionCardView extends CollectionSubView() { default: return StrCast(doc.title); } // prettier-ignore }; - const docTextPromises = this.childDocsWithoutLinks.map(async doc => { - const docText = (await docToText(doc)) ?? ''; - doc.gptInputText = docText; - this._textToDoc.set(docText.replace(/\n/g, ' ').trim(), doc); - return `======${docText.replace(/\n/g, ' ').trim()}======`; - }); + const docTextPromises = this.childCards + .map(pair => pair.layout) + .map(async doc => { + const docText = (await docToText(doc)) ?? ''; + doc.gptInputText = docText; + this._textToDoc.set(docText.replace(/\n/g, ' ').trim(), doc); + return `======${docText.replace(/\n/g, ' ').trim()}======`; + }); return Promise.all<string>(docTextPromises); }; @@ -567,7 +571,7 @@ export class CollectionCardView extends CollectionSubView() { * Actually renders all the cards */ @computed get renderCards() { - if (!this.childDocsWithoutLinks.length) { + if (!this.childCards.length) { return null; } @@ -611,36 +615,16 @@ export class CollectionCardView extends CollectionSubView() { } contentScreenToLocalXf = () => this._props.ScreenToLocalTransform().scale(this._props.NativeDimScaling?.() || 1); + curDoc = () => this.childCards.find(card => DocumentView.getDocumentView(card.layout, this.DocumentView?.())?.IsSelected)?.layout; 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; - + const isEmpty = this.childCards.length === 0; return ( <div className="collectionCardView-outer" @@ -661,31 +645,8 @@ export class CollectionCardView extends CollectionSubView() { 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> + {this.flashCardUI(this.curDoc, this.docViewProps)} </div> ); } diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index 3bcf3450f..9ccac0e0f 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, makeObservable, observable } from 'mobx'; +import { computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { returnZero } from '../../../ClientUtils'; @@ -15,26 +15,19 @@ import { DocumentView } from '../nodes/DocumentView'; import { FocusViewOptions } from '../nodes/FocusViewOptions'; import './CollectionCarousel3DView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; -import { FlashcardPracticeUI } from './FlashcardPracticeUI'; // eslint-disable-next-line @typescript-eslint/no-require-imports const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = require('../global/globalCssVariables.module.scss'); @observer export class CollectionCarousel3DView extends CollectionSubView() { - @computed get scrollSpeed() { - return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; // default scroll speed - } - _sideBtnWidth = 35; - @observable _filterFunc: ((doc: Doc) => boolean) | undefined = undefined; + private _dropDisposer?: DragManager.DragDropDisposer; constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } - private _dropDisposer?: DragManager.DragDropDisposer; - componentWillUnmount() { this._dropDisposer?.(); } @@ -46,8 +39,11 @@ export class CollectionCarousel3DView extends CollectionSubView() { } }; + @computed get scrollSpeed() { + return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; // default scroll speed + } @computed get carouselItems() { - return this.childLayoutPairs.filter(pair => pair.layout.type !== DocumentType.LINK).filter(pair => !this._filterFunc?.(pair.layout)); + return this.childLayoutPairs.filter(pair => !pair.layout.layout_isSvg); } centerScale = Number(CAROUSEL3D_CENTER_SCALE); @@ -86,11 +82,11 @@ export class CollectionCarousel3DView extends CollectionSubView() { @computed get content() { const currentIndex = NumCast(this.layoutDoc._carousel_index); - const displayDoc = (childPair: { layout: Doc; data: Doc }, dxf: () => Transform) => ( + const displayDoc = (child: Doc, dxf: () => Transform) => ( <DocumentView {...this._props} - Document={childPair.layout} - TemplateDataDocument={childPair.data} + Document={child} + TemplateDataDocument={undefined} // suppressSetHeight={true} NativeWidth={returnZero} NativeHeight={returnZero} @@ -109,9 +105,9 @@ export class CollectionCarousel3DView extends CollectionSubView() { /> ); - return this.carouselItems.map((childPair, index) => ( - <div key={childPair.layout[Id]} className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`} style={{ width: this.panelWidth() }}> - {displayDoc(childPair, index < currentIndex ? this.childScreenLeftToLocal : index === currentIndex ? this.childCenterScreenToLocal : this.childScreenRightToLocal)} + return this.carouselItems.map((child, index) => ( + <div key={child.layout[Id]} className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`} style={{ width: this.panelWidth() }}> + {displayDoc(child.layout, index < currentIndex ? this.childScreenLeftToLocal : index === currentIndex ? this.childCenterScreenToLocal : this.childScreenRightToLocal)} </div> )); } @@ -191,35 +187,14 @@ export class CollectionCarousel3DView extends CollectionSubView() { return this.panelWidth() * (1 - index); } - /** - * 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 - screenXPadding = () => (this.uiBtnScaleTransform * this._sideBtnWidth - NumCast(this.layoutDoc.xMargin)) / this._props.ScreenToLocalTransform().Scale; - + curDoc = () => this.carouselItems[NumCast(this.layoutDoc._carousel_index)]?.layout; + answered = (correct: boolean) => (!correct || !this.curDoc()) && this.changeSlide(1); docViewProps = () => ({ ...this._props, // isDocumentActive: this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this._props.isContentActive, isContentActive: this.isChildContentActive, ScreenToLocalTransform: this.contentScreenToLocalXf, }); - carouselItemsFunc = () => this.carouselItems.map(pair => pair.layout); - @action setFilterFunc = (func?: (doc: Doc) => boolean) => { this._filterFunc = func; }; // prettier-ignore - answered = (correct: boolean) => (!correct || !this.curDoc()) && this.changeSlide(1); - curDoc = () => this.carouselItems[NumCast(this.layoutDoc._carousel_index)]?.layout; - render() { return ( <div @@ -233,25 +208,10 @@ export class CollectionCarousel3DView extends CollectionSubView() { {this.content} </div> {this.buttons} - <div className="dot-bar" style={{ transform: `scale(${this.uiBtnScaleTransform})` }}> + <div className="dot-bar" style={{ transform: `scale(${this.uiBtnScaling})` }}> {this.dots} </div> - <FlashcardPracticeUI - setFilterFunc={this.setFilterFunc} - fieldKey={this.fieldKey} - sideBtnWidth={this._sideBtnWidth} - carouselItems={this.carouselItemsFunc} - childDocs={this.childDocs} - advance={this.answered} - curDoc={this.curDoc} - practiceBtnOffset={this._sideBtnWidth * 4} - layoutDoc={this.layoutDoc} - maxWidgetScale={this.maxWidgetScale} - uiBtnScaleTransform={this.uiBtnScaleTransform} - ScreenToLocalBoxXf={this.ScreenToLocalBoxXf} - renderDepth={this._props.renderDepth} - docViewProps={this.docViewProps} - /> + {this.flashCardUI(this.curDoc, this.docViewProps, this.answered)} </div> ); } diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss index 757072453..544b3e262 100644 --- a/src/client/views/collections/CollectionCarouselView.scss +++ b/src/client/views/collections/CollectionCarouselView.scss @@ -2,6 +2,7 @@ height: 100%; position: relative; overflow: hidden; + display: flex; .collectionCarouselView-caption { height: 50; display: inline-block; diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 64ddaac79..538eba356 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -5,7 +5,6 @@ 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 { StyleProp } from '../StyleProp'; import { DocumentView } from '../nodes/DocumentView'; @@ -13,15 +12,12 @@ import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import './CollectionCarouselView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; -import { FlashcardPracticeUI } from './FlashcardPracticeUI'; @observer export class CollectionCarouselView extends CollectionSubView() { private _dropDisposer?: DragManager.DragDropDisposer; _fadeTimer: NodeJS.Timeout | undefined; - _sideBtnWidth = 35; - @observable _filterFunc: ((doc: Doc) => boolean) | undefined = undefined; @observable _last_index = this.carouselIndex; @observable _last_opacity = 1; @@ -43,28 +39,7 @@ export class CollectionCarouselView extends CollectionSubView() { @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 => !this._filterFunc?.(doc)) - } // prettier-ignore - - /** - * 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 - screenXPadding = () => (this.uiBtnScaleTransform * this._sideBtnWidth - NumCast(this.layoutDoc.xMargin)) / this._props.ScreenToLocalTransform().Scale; + @computed get carouselItems() { return this.childLayoutPairs.filter(pair => !pair.layout.layout_isSvg); } // prettier-ignore /** * Move forward or backward the specified number of Docs @@ -91,7 +66,7 @@ export class CollectionCarouselView extends CollectionSubView() { this.move(-1); }; - curDoc = () => this.carouselItems[this.carouselIndex]; + 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 @@ -153,7 +128,7 @@ 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, transition: `opacity ${fadeTime}ms` }}> {this.renderDoc( @@ -211,10 +186,10 @@ export class CollectionCarouselView extends CollectionSubView() { @computed get navButtons() { return this.Document._chromeHidden || !this.curDoc() ? null : ( <> - <div key="back" className="carouselView-back" style={{ transform: `scale(${this.uiBtnScaleTransform})` }} 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" style={{ transform: `scale(${this.uiBtnScaleTransform})` }} onClick={this.advance}> + <div key="fwd" className="carouselView-fwd" style={{ transform: `scale(${this.uiBtnScaling})` }} onClick={this.advance}> <FontAwesomeIcon icon="chevron-right" size="2x" /> </div> </> @@ -227,9 +202,7 @@ export class CollectionCarouselView extends CollectionSubView() { isContentActive: this.isChildContentActive, ScreenToLocalTransform: this.contentScreenToLocalXf, }); - carouselItemsFunc = () => this.carouselItems; answered = () => this.advance(); - @action setFilterFunc = (func?: (doc: Doc) => boolean) => { this._filterFunc = func; }; // prettier-ignore render() { return ( @@ -245,21 +218,7 @@ export class CollectionCarouselView extends CollectionSubView() { top: NumCast(this.layoutDoc._yMargin), }}> {this.content} - <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} - /> + {this.flashCardUI(this.curDoc, this.docViewProps, this.answered)} {this.navButtons} </div> ); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 581201a20..c057d2402 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -9,7 +9,7 @@ import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types'; +import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { WebField } from '../../../fields/URLField'; import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; import { GestureUtils } from '../../../pen-gestures/GestureUtils'; @@ -25,7 +25,8 @@ import { SnappingManager } from '../../util/SnappingManager'; import { UndoManager } from '../../util/UndoManager'; import { ViewBoxBaseComponent } from '../DocComponent'; import { FieldViewProps } from '../nodes/FieldView'; -import { DocumentView } from '../nodes/DocumentView'; +import { DocumentView, DocumentViewProps } from '../nodes/DocumentView'; +import { FlashcardPracticeUI } from './FlashcardPracticeUI'; export interface CollectionViewProps extends React.PropsWithChildren<FieldViewProps> { isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc) @@ -119,7 +120,8 @@ export function CollectionSubView<X>() { pair => // filter out any documents that have a proto that we don't have permissions to !pair.layout?.hidden && pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate)) - ); + ) + .filter(pair => !this._filterFunc?.(pair.layout!)); return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types } /** @@ -515,6 +517,49 @@ export function CollectionSubView<X>() { alert('Document upload failed - possibly an unsupported file type.'); } }; + + protected _sideBtnWidth = 35; + @observable _filterFunc: ((doc: Doc) => boolean) | undefined = undefined; + /** + * How much the content of the collection 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 in collection coordinates based on not wanting the widget to visually obscure too much of the collection + * This takes the desired screen space size and converts into collection coordinates. It then returns the smaller of the converted + * size or a fraction of the collection view. + */ + @computed get maxWidgetSize() { return Math.min(this._sideBtnWidth * this.contentScaling, 0.25 * NumCast(this.layoutDoc.width, 1)); } // prettier-ignore + /** + * This computes a scale factor for UI elements so that they shrink and grow as the collection does in screen space. + * Note, the scale factor does not allow for elements to grow larger than their native screen space size. + */ + @computed get uiBtnScaling() { return this.maxWidgetSize / this._sideBtnWidth; } // prettier-ignore + + screenXPadding = () => (this.uiBtnScaling * this._sideBtnWidth - NumCast(this.layoutDoc.xMargin)) / this._props.ScreenToLocalTransform().Scale; + filteredChildDocs = () => this.childLayoutPairs.map(pair => pair.layout); + childDocsFunc = () => this.childDocs; + @action setFilterFunc = (func?: (doc: Doc) => boolean) => { this._filterFunc = func; }; // prettier-ignore + + public flashCardUI = (curDoc: () => Doc | undefined, docViewProps: () => DocumentViewProps, answered?: (correct: boolean) => void) => { + return ( + <FlashcardPracticeUI + setFilterFunc={this.setFilterFunc} + fieldKey={this.fieldKey} + sideBtnWidth={this._sideBtnWidth} + allChildDocs={this.childDocsFunc} + filteredChildDocs={this.filteredChildDocs} + advance={answered} + curDoc={curDoc} + practiceBtnOffset={this._sideBtnWidth * 4} + layoutDoc={this.layoutDoc} + uiBtnScaling={this.uiBtnScaling} + ScreenToLocalBoxXf={this.ScreenToLocalBoxXf} + renderDepth={this._props.renderDepth} + docViewProps={docViewProps} + /> + ); + }; } return CollectionSubViewInternal; diff --git a/src/client/views/collections/FlashcardPracticeUI.scss b/src/client/views/collections/FlashcardPracticeUI.scss index 2f99500f8..c5252bbfa 100644 --- a/src/client/views/collections/FlashcardPracticeUI.scss +++ b/src/client/views/collections/FlashcardPracticeUI.scss @@ -50,7 +50,7 @@ height: 20px; align-items: center; margin: auto; - padding: 3px; + // padding: 3px; &:hover { color: white; } diff --git a/src/client/views/collections/FlashcardPracticeUI.tsx b/src/client/views/collections/FlashcardPracticeUI.tsx index a643c95b0..7bf4d86d1 100644 --- a/src/client/views/collections/FlashcardPracticeUI.tsx +++ b/src/client/views/collections/FlashcardPracticeUI.tsx @@ -23,15 +23,14 @@ enum practiceVal { interface PracticeUIProps { fieldKey: string; layoutDoc: Doc; - carouselItems: () => Doc[]; - childDocs: Doc[]; + filteredChildDocs: () => Doc[]; + allChildDocs: () => Doc[]; curDoc: () => Doc | undefined; - advance: (correct: boolean) => void; + advance?: (correct: boolean) => void; renderDepth: number; sideBtnWidth: number; - uiBtnScaleTransform: number; + uiBtnScaling: number; ScreenToLocalBoxXf: () => Transform; - maxWidgetScale: number; docViewProps: () => DocumentViewProps; setFilterFunc: (func?: (doc: Doc) => boolean) => void; practiceBtnOffset?: number; @@ -51,7 +50,7 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp get practiceField() { return this._props.fieldKey + "_practice"; } // prettier-ignore @computed get filterDoc() { return DocListCast(Doc.MyContextMenuBtns.data).find(doc => doc.title === 'Filter'); } // prettier-ignore - @computed get practiceMode() { return this._props.childDocs.some(doc => doc._layout_isFlashcard) ? StrCast(this._props.layoutDoc.practiceMode) : ''; } // prettier-ignore + @computed get practiceMode() { return this._props.allChildDocs().some(doc => doc._layout_isFlashcard) ? StrCast(this._props.layoutDoc.practiceMode) : ''; } // prettier-ignore btnHeight = () => NumCast(this.filterDoc?.height) * Math.min(1, this._props.ScreenToLocalBoxXf().Scale); btnWidth = () => (!this.filterDoc ? 1 : (this.btnHeight() * NumCast(this.filterDoc._width)) / NumCast(this.filterDoc._height)); @@ -62,12 +61,12 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp */ setPracticeMode = (mode: practiceMode | undefined) => { this._props.layoutDoc.practiceMode = mode; - this._props.carouselItems().map(doc => (doc[this.practiceField] = undefined)); + this._props.allChildDocs().map(doc => (doc[this.practiceField] = undefined)); }; @computed get emptyMessage() { - const cardCount = this._props.carouselItems().length; - const practiceMessage = this.practiceMode && !Doc.hasDocFilter(this._props.layoutDoc, 'tags', Doc.FilterAny) && !this._props.carouselItems().length ? 'Finished! Click here to view all flashcards.' : ''; + const cardCount = this._props.filteredChildDocs().length; + const practiceMessage = this.practiceMode && !Doc.hasDocFilter(this._props.layoutDoc, 'tags', Doc.FilterAny) && !cardCount ? 'Finished! Click here to view all flashcards.' : ''; const filterMessage = practiceMessage ? '' : Doc.hasDocFilter(this._props.layoutDoc, 'tags', Doc.FilterAny) && !cardCount @@ -78,7 +77,7 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp return !practiceMessage && !filterMessage ? null : ( <p className="FlashcardPracticeUI-message" - style={{ transform: `scale(${this._props.uiBtnScaleTransform})` }} + style={{ transform: `scale(${this._props.uiBtnScaling})` }} onClick={() => { if (filterMessage || practiceMessage) { this.setPracticeMode(undefined); @@ -102,7 +101,7 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp }; 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` }}> + <div className="FlashcardPracticeUI-practice" style={{ transform: `scale(${this._props.uiBtnScaling})`, 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" /> @@ -120,12 +119,12 @@ 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.childDocs.some(doc => doc._layout_isFlashcard) ? null : ( + return !this._props.allChildDocs().some(doc => doc._layout_isFlashcard) ? null : ( <div className="FlashcardPracticeUI-practiceModes" style={{ transformOrigin: `0px ${-this.btnHeight()}px`, - transform: `scale(${Math.max(1, 1 / this._props.ScreenToLocalBoxXf().Scale / this._props.maxWidgetScale)})`, + transform: `scale(${Math.max(1, 1 / this._props.ScreenToLocalBoxXf().Scale)})`, }}> <Tooltip title="Practice flashcards using GPT"> <div key="back" className="FlashcardPracticeUI-quiz" style={{ width: this.btnWidth(), height: this.btnHeight() }} onClick={() => togglePracticeMode(practiceMode.QUIZ)}> @@ -146,7 +145,7 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp <> {this.emptyMessage} {this.practiceButtons} - <div className="FlashcardPracticeUI-menu" style={{ height: this.btnHeight(), width: this.btnHeight(), transform: `scale(${this._props.uiBtnScaleTransform})` }}> + <div className="FlashcardPracticeUI-menu" style={{ height: this.btnHeight(), width: this.btnHeight(), transform: `scale(${this._props.uiBtnScaling})` }}> {!this.filterDoc || this._props.layoutDoc._chromeHidden ? null : ( <DocumentView {...this._props.docViewProps()} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index d8678eebc..0cc63d632 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -138,7 +138,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine); } @computed get childPointerEvents() { - return falseSnappingManager.IsResizing + return SnappingManager.IsResizing ? 'none' : (this._props.childPointerEvents?.() ?? (this._props.viewDefDivClick || // diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 81e223028..a8c91aae8 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -87,34 +87,26 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() ); } - _sideBtnWidth = 30; + _sideBtnWidth = 35; /** * How much the content of the 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); - } + @computed get viewScaling() { return this.ScreenToLocalBoxXf().Scale; } // 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.25 * Math.min(NumCast(this.Document.width), NumCast(this.Document.height))); - return Math.max(maxWidgetSize / this._sideBtnWidth, 1); - } + @computed get maxWidgetSize() { return Math.min(this._sideBtnWidth * this.viewScaling, 0.25 * Math.min(NumCast(this.Document.width), NumCast(this.Document.height))); } // prettier-ignore /** * 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); - } + @computed get uiBtnScaling() { return Math.max(this.maxWidgetSize / this._sideBtnWidth, 1) * Math.min(1, this.viewScaling)* (this._props.NativeDimScaling?.() ?? 1); } // prettier-ignore @computed get flashcardMenu() { return ( - <div className="comparisonBox-bottomMenu" style={{ transform: `scale(${this.uiBtnScaleTransform})` }}> + <div className="comparisonBox-bottomMenu" style={{ transform: `scale(${this.uiBtnScaling})` }}> {this.overlayAlternateIcon} {!this._props.isContentActive() ? null : ( <> - {' '} {!this._frontSide ? null : ( <Tooltip title={ |