aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/views/collections/CollectionCardDeckView.scss13
-rw-r--r--src/client/views/collections/CollectionCardDeckView.tsx81
-rw-r--r--src/client/views/collections/CollectionCarousel3DView.tsx72
-rw-r--r--src/client/views/collections/CollectionCarouselView.scss1
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx53
-rw-r--r--src/client/views/collections/CollectionSubView.tsx51
-rw-r--r--src/client/views/collections/FlashcardPracticeUI.scss2
-rw-r--r--src/client/views/collections/FlashcardPracticeUI.tsx27
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx2
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx18
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={