diff options
Diffstat (limited to 'src/client/views/collections/CollectionCardDeckView.tsx')
| -rw-r--r-- | src/client/views/collections/CollectionCardDeckView.tsx | 138 |
1 files changed, 78 insertions, 60 deletions
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 3122aa587..286df30aa 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -1,11 +1,14 @@ import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; +import { computedFn } from 'mobx-utils'; import * as React from 'react'; -import { ClientUtils, DashColor, returnFalse, returnZero } from '../../../ClientUtils'; +import { ClientUtils, DashColor, imageUrlToBase64, returnFalse, returnNever, returnZero } from '../../../ClientUtils'; +import { emptyFunction } from '../../../Utils'; import { Doc } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; +import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, DateCast, DocCast, NumCast, RTFCast, ScriptCast, StrCast } from '../../../fields/Types'; import { URLField } from '../../../fields/URLField'; import { gptImageLabel } from '../../apis/gpt/GPT'; @@ -17,11 +20,10 @@ import { Transform } from '../../util/Transform'; import { undoable } from '../../util/UndoManager'; import { StyleProp } from '../StyleProp'; import { TagItem } from '../TagsView'; -import { DocumentView } from '../nodes/DocumentView'; +import { DocumentView, DocumentViewProps } from '../nodes/DocumentView'; import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup'; import './CollectionCardDeckView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; -import { computedFn } from 'mobx-utils'; enum cardSortings { Time = 'time', @@ -45,29 +47,14 @@ export class CollectionCardView extends CollectionSubView() { private _disposers: { [key: string]: IReactionDisposer } = {}; 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) + private _clickScript = () => ScriptField.MakeScript('scriptContext._curDoc=this', { scriptContext: 'any' })!; @observable _forceChildXf = 0; @observable _hoveredNodeIndex = -1; @observable _docRefs = new ObservableMap<Doc, DocumentView>(); @observable _maxRowCount = 10; @observable _docDraggedIndex: number = -1; - - static imageUrlToBase64 = async (imageUrl: string): Promise<string> => { - try { - const response = await fetch(imageUrl); - const blob = await response.blob(); - - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.readAsDataURL(blob); - reader.onloadend = () => resolve(reader.result as string); - reader.onerror = error => reject(error); - }); - } catch (error) { - console.error('Error:', error); - throw error; - } - }; + @observable _curDoc: Doc | undefined = undefined; constructor(props: SubCollectionViewProps) { super(props); @@ -133,25 +120,25 @@ 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); + @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(); } /** * When in quiz mode, randomly selects a document */ - quizMode = () => { + quizMode = action(() => { const randomIndex = Math.floor(Math.random() * this.childDocs.length); - DocumentView.getDocumentView(this.childDocs[randomIndex])?.select(false); - }; + this._curDoc = this.childDocs[randomIndex]; + }); /** * Number of rows of cards to be rendered @@ -294,7 +281,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 + ); } /** @@ -344,14 +336,16 @@ export class CollectionCardView extends CollectionSubView() { return docs; }; - 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 + isChildContentActive = computedFn( + (doc: Doc) => () => + this._props.isContentActive?.() === false ? false - : undefined; + : this._props.isDocumentActive?.() && this._curDoc === doc + ? true + : this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false + ? false + : undefined + ); // prettier-ignore displayDoc = (doc: Doc, screenToLocalTransform: () => Transform) => ( <DocumentView @@ -370,11 +364,14 @@ export class CollectionCardView extends CollectionSubView() { isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive} PanelWidth={this.childPanelWidth} PanelHeight={this.childPanelHeight} + waitForDoubleClickToClick={returnNever} + scriptContext={this} + onClickScript={this._curDoc === doc ? undefined : this._clickScript} dontCenter="y" // Don't center it vertically, because the grid it's in is already doing that and we don't want to do it twice. dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType} showTags={BoolCast(this.layoutDoc.showChildTags)} whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} - isContentActive={this.isChildContentActive} + isContentActive={this.isChildContentActive(doc)} dontHideOnDrag /> ); @@ -442,12 +439,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); }; @@ -462,8 +461,8 @@ export class CollectionCardView extends CollectionSubView() { const hrefParts = href.split('.'); const hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`; try { - const hrefBase64 = await CollectionCardView.imageUrlToBase64(hrefComplete); - const response = await gptImageLabel(hrefBase64); + const hrefBase64 = await imageUrlToBase64(hrefComplete); + const response = await gptImageLabel(hrefBase64, 'Give three to five labels to describe this image.'); image[DocData].description = response.trim(); return response; // Return the response from gptImageLabel } catch (error) { @@ -561,7 +560,7 @@ export class CollectionCardView extends CollectionSubView() { cardPointerUp = action((doc: Doc) => { // if a card doc has just moved, or a card is selected and in front, then ignore this event - if (this.isSelected(doc) || this._dropped) { + if (this._curDoc === doc || this._dropped) { this._dropped = false; } else { // otherwise, turn off documentDecorations becase we're in a selection transition and want to avoid artifacts. @@ -581,21 +580,16 @@ export class CollectionCardView extends CollectionSubView() { * Actually renders all the cards */ @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> - ); + if (!this.childCards.length) { + return null; } // Map sorted documents to their rendered components return this.sortedDocs.map((doc, index) => { const calcRowIndex = this.overflowIndexCalc(index); const amCards = this.overflowAmCardsCalc(index); - const view = DocumentView.getDocumentView(doc, this.DocumentView?.()); - const childScreenToLocal = this.childScreenToLocal(doc, index, calcRowIndex, !!view?.IsContentActive, amCards); + const childScreenToLocal = this.childScreenToLocal(doc, index, calcRowIndex, doc === this._curDoc, amCards); const translateIfSelected = () => { const indexInRow = index % this._maxRowCount; @@ -610,15 +604,15 @@ export class CollectionCardView extends CollectionSubView() { return ( <div key={doc[Id]} - className={`card-item${view?.IsContentActive ? '-active' : this.isAnyChildContentActive() ? '-inactive' : ''}`} + className={`card-item${doc === this._curDoc ? '-active' : this.isAnyChildContentActive() ? '-inactive' : ''}`} onPointerUp={() => this.cardPointerUp(doc)} style={{ width: this.childPanelWidth(), height: 'max-content', - transform: `translateY(${this.calculateTranslateY(!!view?.IsContentActive, index, amCards, calcRowIndex)}px) - translateX(calc(${view?.IsContentActive ? translateIfSelected() : 0}% + ${this.translateOverflowX(index, amCards)}px)) - rotate(${!view?.IsContentActive ? this.rotate(amCards, calcRowIndex) : 0}deg) - scale(${view?.IsContentActive ? `${Math.min(hscale, vscale) * 100}%` : this._hoveredNodeIndex === index ? 1.1 : 1})`, + transform: `translateY(${this.calculateTranslateY(doc === this._curDoc, index, amCards, calcRowIndex)}px) + translateX(calc(${doc === this._curDoc ? translateIfSelected() : 0}% + ${this.translateOverflowX(index, amCards)}px)) + rotate(${doc !== this._curDoc ? this.rotate(amCards, calcRowIndex) : 0}deg) + scale(${doc === this._curDoc ? `${Math.min(hscale, vscale) * 100}%` : this._hoveredNodeIndex === index ? 1.1 : 1})`, }} // prettier-ignore onPointerEnter={() => this.setHoveredNodeIndex(index)} onPointerLeave={() => this.setHoveredNodeIndex(-1)}> @@ -628,13 +622,35 @@ export class CollectionCardView extends CollectionSubView() { }); } - render() { - const isEmpty = this.childDocsWithoutLinks.length === 0; + contentScreenToLocalXf = () => this._props.ScreenToLocalTransform().scale(this._props.NativeDimScaling?.() || 1); + docViewProps = (): DocumentViewProps => ({ + ...this._props, // + isDocumentActive: this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this._props.isContentActive, + isContentActive: emptyFunction, + ScreenToLocalTransform: this.contentScreenToLocalXf, + }); + answered = action(() => { + this._curDoc = this.curDoc ? this.filteredChildDocs()[(this.filteredChildDocs().findIndex(this.curDoc) + 1) % (this.filteredChildDocs().length || 1)] : undefined; + }); + curDoc = () => this._curDoc; + render() { + const isEmpty = this.childCards.length === 0; return ( <div className="collectionCardView-outer" ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)} + onPointerDown={action(() => { + this._curDoc = undefined; + SnappingManager.SetHideDecorations(true); + setTimeout( + action(() => { + SnappingManager.SetHideDecorations(false); + this._forceChildXf++; + }), + 1000 + ); + })} onPointerLeave={action(() => (this._docDraggedIndex = -1))} onPointerMove={e => this.onPointerMove(...this._props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY))} onDrop={this.onExternalDrop.bind(this)} @@ -646,11 +662,13 @@ 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> + {this.flashCardUI(this.curDoc, this.docViewProps, this.answered)} </div> ); } |
