diff options
author | bobzel <zzzman@gmail.com> | 2024-10-16 17:31:12 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2024-10-16 17:31:12 -0400 |
commit | 9779182e74e427d3a8a2004d0efd01fac8387d55 (patch) | |
tree | f708b6582f8e39b6353770bf007b504fd1d60d67 | |
parent | 29b83f023442c313ca5cf95f70f6430f101060e6 (diff) |
major fixes to cardDeck view to simplify code and to make arch follow a true circle arc and to fix doc sizing when fitwidth/lightbox/etc. fixes to flashcard UI for advancing to next Doc in cardView and carousel3D.
-rw-r--r-- | src/client/documents/Documents.ts | 4 | ||||
-rw-r--r-- | src/client/views/DocumentDecorations.tsx | 4 | ||||
-rw-r--r-- | src/client/views/collections/CollectionCardDeckView.scss | 24 | ||||
-rw-r--r-- | src/client/views/collections/CollectionCardDeckView.tsx | 336 | ||||
-rw-r--r-- | src/client/views/collections/CollectionCarousel3DView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/collections/CollectionCarouselView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/collections/FlashcardPracticeUI.scss | 6 | ||||
-rw-r--r-- | src/client/views/collections/FlashcardPracticeUI.tsx | 6 | ||||
-rw-r--r-- | src/client/views/global/globalScripts.ts | 26 | ||||
-rw-r--r-- | src/client/views/nodes/ComparisonBox.tsx | 25 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentContentsView.tsx | 1 |
11 files changed, 230 insertions, 206 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 99af1f1a9..e539e3c65 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -508,8 +508,8 @@ export class DocumentOptions { userBackgroundColor?: STRt = new StrInfo('background color associated with a Dash user (seen in header fields of shared documents)'); userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)'); - cardSort?: STRt = new StrInfo('way cards are sorted in deck view'); - cardSort_isDesc?: BOOLt = new BoolInfo('whether the cards are sorted ascending or descending'); + card_sort?: STRt = new StrInfo('way cards are sorted in deck view'); + card_sort_isDesc?: BOOLt = new BoolInfo('whether the cards are sorted ascending or descending'); } export const DocOptions = new DocumentOptions(); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 62f2de776..5a48b6c62 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -10,7 +10,7 @@ import { lightOrDark, returnFalse, setupMoveUpEvents } from '../../ClientUtils'; import { Utils, emptyFunction, numberValue } from '../../Utils'; import { DateField } from '../../fields/DateField'; import { Doc, DocListCast, Field, FieldType, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc'; -import { AclAdmin, AclAugment, AclEdit, DocData } from '../../fields/DocSymbols'; +import { AclAdmin, AclAugment, AclEdit, Animation, DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { InkField } from '../../fields/InkField'; import { ScriptField } from '../../fields/ScriptField'; @@ -650,7 +650,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora this._forceRender; const { b, r, x, y } = this.Bounds; const seldocview = DocumentView.Selected().lastElement(); - if (SnappingManager.IsDragging || r - x < 1 || x === Number.MAX_VALUE || !seldocview || SnappingManager.HideDecorations || isNaN(r) || isNaN(b) || isNaN(x) || isNaN(y)) { + if (SnappingManager.IsDragging || r - x < 1 || x === Number.MAX_VALUE || !seldocview || seldocview?.Document[Animation] || SnappingManager.HideDecorations || isNaN(r) || isNaN(b) || isNaN(x) || isNaN(y)) { setTimeout( action(() => { this._editingTitle = false; diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss index 0637cd4e9..5283601bf 100644 --- a/src/client/views/collections/CollectionCardDeckView.scss +++ b/src/client/views/collections/CollectionCardDeckView.scss @@ -7,23 +7,31 @@ background-color: white; overflow: hidden; display: flex; + .collectionCardView-inner { + display: flex; + transform-origin: top left; + align-items: center; + } button { border-radius: 50%; } } -.card-wrapper { - display: grid; - grid-template-columns: repeat(10, 1fr); +.collectionCardView-flashcardUI { + top: 0; + position: absolute; + width: 100%; + height: 100%; transform-origin: top left; +} - position: absolute; +.collectionCardView-cardwrapper { + display: grid; + grid-template-columns: repeat(10, 1fr); + transform-origin: left 50%; align-items: center; - justify-items: center; - justify-content: center; - - transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955); + z-index: 0; // so that setting z-index of active card doesn't make it land on top of things outside of the card-wrapper } .no-card-span { diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 14ce9d2af..5faabacf4 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import { ClientUtils, DashColor, imageUrlToBase64, returnFalse, returnNever, returnZero } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; import { Doc } from '../../../fields/Doc'; -import { DocData } from '../../../fields/DocSymbols'; +import { Animation, DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { ScriptField } from '../../../fields/ScriptField'; @@ -96,7 +96,7 @@ export class CollectionCardView extends CollectionSubView() { if (isVis) { this.openChatPopup(); } else { - this.Document.cardSort = this.cardSort === cardSortings.Chat ? '' : this.Document.cardSort; + this.Document.card_sort = this.cardSort === cardSortings.Chat ? '' : this.Document.card_sort; } } ); @@ -119,7 +119,25 @@ export class CollectionCardView extends CollectionSubView() { } @computed get cardSort() { - return StrCast(this.Document.cardSort) as cardSortings; + return StrCast(this.Document.card_sort) as cardSortings; + } + /** + * Number of rows of cards to be rendered + */ + @computed get numRows() { + return Math.ceil(this.sortedDocs.length / this._maxRowCount); + } + /** + * Circle arc size, in radians, to layout cards + */ + @computed get archAngle() { + return NumCast(this.layoutDoc.card_arch, 90) * (Math.PI / 180) * (this.childCards.length < this._maxRowCount ? this.childCards.length / this._maxRowCount : 1); + } + /** + * Spacing card rows as a percent of Doc size. 100 means rows spread out to fill 100% of the Doc vertically. Default is 60% + */ + @computed get cardSpacing() { + return NumCast(this.layoutDoc.card_spacing, 60); } /** @@ -137,6 +155,10 @@ export class CollectionCardView extends CollectionSubView() { return (this.childPanelWidth() * length) / this._props.PanelWidth(); } + @computed get nativeScaling() { + return this._props.NativeDimScaling?.() || 1; + } + /** * When in quiz mode, randomly selects a document */ @@ -145,99 +167,45 @@ export class CollectionCardView extends CollectionSubView() { this._curDoc = this.childDocs[randomIndex]; }); - /** - * Number of rows of cards to be rendered - */ - @computed get numRows() { - return Math.ceil(this.sortedDocs.length / this._maxRowCount); - } - - @action - setHoveredNodeIndex = (index: number) => { + setHoveredNodeIndex = action((index: number) => { if (!SnappingManager.IsDragging) this._hoveredNodeIndex = index; - }; + }); isSelected = (doc: Doc) => this._docRefs.get(doc)?.IsSelected; - childPanelWidth = () => NumCast(this.layoutDoc.childPanelWidth, this._props.PanelWidth() / 2); + childPanelWidth = () => NumCast(this.layoutDoc.childPanelWidth, Math.max(150, this._props.PanelWidth() / (this.childCards.length > this._maxRowCount ? this._maxRowCount : this.childCards.length) / this.nativeScaling)); childPanelHeight = () => this._props.PanelHeight() * this.fitContentScale; onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick); isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this.isAnyChildContentActive(); isAnyChildContentActive = this._props.isAnyChildContentActive; /** - * Returns the degree to rotate a card dependind on the amount of cards in their row and their index in said row - * @param amCards - * @param index - * @returns - */ - rotate = (amCards: number, index: number) => { - if (amCards == 1) return 0; - - const possRotate = -30 + index * (30 / ((amCards - (amCards % 2)) / 2)); - if (amCards % 2 === 0) { - if (possRotate === 0) { - return possRotate + Math.abs(-30 + (index - 1) * (30 / (amCards / 2))); - } - if (index > (amCards + 1) / 2) { - const stepMag = Math.abs(-30 + (amCards / 2 - 1) * (30 / ((amCards - (amCards % 2)) / 2))); - return possRotate + stepMag; - } - } - - return possRotate; - }; - /** - * Returns the degree to which a card should be translated in the y direction for the arch effect - */ - translateY = (amCards: number, index: number, realIndex: number) => { - const evenOdd = amCards % 2; - const apex = (amCards - evenOdd) / 2; - const Magnitude = this.childPanelWidth() / 2; // 400 - const stepMag = Magnitude / 2 / ((amCards - evenOdd) / 2) + Math.abs((apex - index) * 25); - - let rowOffset = 0; - if (realIndex > this._maxRowCount - 1) { - rowOffset = Magnitude * ((realIndex - (realIndex % this._maxRowCount)) / this._maxRowCount); - } - if (evenOdd === 1 || index < apex - 1) { - return Math.abs(stepMag * (apex - index)) - rowOffset; - } - if (index === apex || index === apex - 1) { - return 0 - rowOffset; - } - - return Math.abs(stepMag * (apex - index - 1)) - rowOffset; - }; - - /** * When dragging a card, determines the index the card should be set to if dropped * @param mouseX mouse's x location * @param mouseY mouses' y location * @returns the card's new index */ findCardDropIndex = (mouseX: number, mouseY: number) => { - const amCardsTotal = this.sortedDocs.length; + const cardCount = this.sortedDocs.length; let index = 0; - const cardWidth = amCardsTotal < this._maxRowCount ? this._props.PanelWidth() / amCardsTotal : this._props.PanelWidth() / this._maxRowCount; + const cardWidth = cardCount < this._maxRowCount ? this._props.PanelWidth() / cardCount : this._props.PanelWidth() / this._maxRowCount; // Calculate the adjusted X position accounting for the initial offset let adjustedX = mouseX; - const amRows = Math.ceil(amCardsTotal / this._maxRowCount); - const rowHeight = this._props.PanelHeight() / amRows; + const rowHeight = this._props.PanelHeight() / this.numRows; const currRow = Math.floor((mouseY - 100) / rowHeight); //rows start at 0 if (adjustedX < 0) { return 0; // Before the first column } - if (amCardsTotal < this._maxRowCount) { + if (cardCount < this._maxRowCount) { index = Math.floor(adjustedX / cardWidth); - } else if (currRow != amRows - 1) { + } else if (currRow != this.numRows - 1) { index = Math.floor(adjustedX / cardWidth) + currRow * this._maxRowCount; } else { - const rowAmCards = amCardsTotal - currRow * this._maxRowCount; - const offset = ((this._maxRowCount - rowAmCards) / 2) * cardWidth; + const cardsInRow = cardCount - currRow * this._maxRowCount; + const offset = ((this._maxRowCount - cardsInRow) / 2) * cardWidth; adjustedX = mouseX - offset; index = Math.floor(adjustedX / cardWidth) + currRow * this._maxRowCount; @@ -246,11 +214,14 @@ export class CollectionCardView extends CollectionSubView() { }; /** - * Checks to see if a card is being dragged and calls the appropriate methods if so + * if pointer moves over cardDeck while dragging a Doc that is in the Deck or that can be dropped in the deck, + * then this sets the card index where the dragged card would be added. */ @action onPointerMove = (x: number, y: number) => { - this._docDraggedIndex = DragManager.docsBeingDragged.length ? this.findCardDropIndex(x, y) : -1; + if (DragManager.docsBeingDragged.some(doc => this.sortedDocs.includes(doc)) || SnappingManager.CanEmbed) { + this._docDraggedIndex = this.findCardDropIndex(x, y); + } }; /** @@ -269,7 +240,7 @@ export class CollectionCardView extends CollectionSubView() { const sorted = this.sortedDocs; const originalIndex = sorted.findIndex(doc => doc === draggedDoc); - this.Document.cardSort = ''; + this.Document.card_sort = ''; originalIndex !== -1 && sorted.splice(originalIndex, 1); sorted.splice(dragIndex, 0, draggedDoc); if (de.complete.docDragData.removeDocument?.(draggedDoc)) { @@ -285,15 +256,6 @@ export class CollectionCardView extends CollectionSubView() { '' ); - @computed get sortedDocs() { - return this.sort( - this.childCards.map(card => card.layout), - this.cardSort, - BoolCast(this.Document.cardSort_isDesc), - this._docDraggedIndex - ); - } - /** * Used to determine how to sort cards based on tags. The leftmost tags are given lower values while cards to the right are * given higher values. Decimals are used to determine placement for cards with multiple tags @@ -341,6 +303,15 @@ export class CollectionCardView extends CollectionSubView() { return docs; }; + @computed get sortedDocs() { + return this.sort( + this.childCards.map(card => card.layout), + this.cardSort, + BoolCast(this.Document.card_sort_isDesc), + this._docDraggedIndex + ); + } + isChildContentActive = computedFn( (doc: Doc) => () => this._props.isContentActive?.() === false @@ -359,74 +330,99 @@ export class CollectionCardView extends CollectionSubView() { Document={doc} NativeWidth={returnZero} NativeHeight={returnZero} - fitWidth={returnFalse} - onDoubleClickScript={this.onChildDoubleClick} + PanelWidth={this.childPanelWidth} + PanelHeight={this.childPanelHeight} renderDepth={this._props.renderDepth + 1} LayoutTemplate={this._props.childLayoutTemplate} LayoutTemplateString={this._props.childLayoutString} containerViewPath={this.childContainerViewPath} ScreenToLocalTransform={screenToLocalTransform} // makes sure the box wrapper thing is in the right spot isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive} - PanelWidth={this.childPanelWidth} - PanelHeight={this.childPanelHeight} + isContentActive={this.isChildContentActive(doc)} + fitWidth={returnFalse} waitForDoubleClickToClick={returnNever} scriptContext={this} + onDoubleClickScript={this.onChildDoubleClick} 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(doc)} dontHideOnDrag /> ); /** * Determines how many cards are in the row of a card at a specific index - * @param index - * @returns + * @param index numerical index of card in total list of all cards + * @returns number of cards in row that contains index */ - overflowAmCardsCalc = (index: number) => { - if (this.sortedDocs.length < this._maxRowCount) { - return this.sortedDocs.length; + cardsInRowThatIncludesCardIndex = (index: number) => { + if (this.childCards.length < this._maxRowCount) { + return this.childCards.length; } - const totalCards = this.sortedDocs.length; - // if 9 or less + const totalCards = this.childCards.length; if (index < totalCards - (totalCards % this._maxRowCount)) { return this._maxRowCount; } return totalCards % this._maxRowCount; }; /** - * Determines the index a card is in in a row - * @param realIndex - * @returns + * Determines the index a card is in in a row. If the row is not full, then the cards + * are centered within the row (as if unrendered cards had been added to the start and end + * of the row) and the retuned index is the index the card in this virtual full row. + * @param index numerical index of card in total list of all cards + * @returns index of card in its row, normalized to a full size row */ - overflowIndexCalc = (realIndex: number) => realIndex % this._maxRowCount; + centeredIndexOfCardInRow = (index: number) => { + const cardsInRow = this.cardsInRowThatIncludesCardIndex(index); + const lineIndex = index % this._maxRowCount; + if (cardsInRow === this._maxRowCount) return lineIndex; + return lineIndex + (this._maxRowCount - cardsInRow) / 2; + }; /** - * Translates the cards in the second rows and beyond over to the right - * @param realIndex - * @param calcIndex - * @param calcRowCards - * @returns + * Returns the rotation of a card in radians based on its horizontal location (and thus m apping to a circle arc). + * The amount of rotation is goverend by the Doc's card_arch field which specifies, in degrees, the range of a circle + * arc that cards should cover -- by default, -45 to 45 degrees. + * @param index numerical index of card in total list of all cards + * @returns angle of rotation in radians + */ + rotate = (index: number) => { + const cardsInRow = this.cardsInRowThatIncludesCardIndex(index); + const centeredIndexInRow = (cardsInRow < this._maxRowCount ? index + (this._maxRowCount - cardsInRow) / 2 : index) % this._maxRowCount; + const rowIndexMax = this._maxRowCount - 1; + return ((this.archAngle / 2) * (centeredIndexInRow - rowIndexMax / 2)) / (rowIndexMax / 2); + }; + /** + * Provides a vertical adjustment to a card's grid position so that it will lie along an arch. + * @param index numerical index of card in total list of all cards + */ + translateY = (index: number) => { + const Magnitude = ((this._props.PanelHeight() * this.fitContentScale) / 2) * Math.sqrt(((this.archAngle * (180 / Math.PI)) / 60) * 4); + return Magnitude * (1 - Math.sin(this.rotate(index) + Math.PI / 2) - (1 - Math.sin(this.archAngle / 2 + Math.PI / 2)) / 2); + }; + /** + * When the card index is for a row (not the first row) that is not full, this returns a horizontal adjustment that centers the row + * @param index index of card from start of deck + * @param cardsInRow number of cards in the row containing the indexed card + * @returns horizontal pixel translation */ - translateOverflowX = (realIndex: number, calcRowCards: number) => (realIndex < this._maxRowCount ? 0 : (this._maxRowCount - calcRowCards) * (this.childPanelWidth() / 2)); + horizontalAdjustmentForPartialRows = (index: number, cardsInRow: number) => (index < this._maxRowCount ? 0 : (this._maxRowCount - cardsInRow) * (this.childPanelWidth() / 2)); /** - * Determines how far to translate a card in the y direction depending on its index and if it's selected + * Adjusts the vertical placement of the card from its grid position so that it will either line on a + * circular arc if the card isn't active, or so that it will be centered otherwise. * @param isActive whether the card is focused for interaction - * @param realIndex index of card from start of deck - * @param amCards ?? - * @param calcRowIndex index of card from start of row - * @returns Y translation of card + * @param index index of card from start of deck + * @returns vertical pixel translation */ - calculateTranslateY = (isActive: boolean, realIndex: number, amCards: number, calcRowIndex: number) => { - const rowHeight = (this._props.PanelHeight() * this.fitContentScale) / this.numRows; - const rowIndex = Math.trunc(realIndex / this._maxRowCount); + adjustCardYtoFitArch = (isActive: boolean, index: number) => { + const rowHeight = this._props.PanelHeight() / this.numRows; + const rowIndex = Math.floor(index / this._maxRowCount); const rowToCenterShift = this.numRows / 2 - rowIndex; - if (isActive) return rowToCenterShift * rowHeight - rowHeight / 2; - if (amCards == 1) return 50 * this.fitContentScale; - return this.translateY(amCards, calcRowIndex, realIndex); + return isActive + ? (rowToCenterShift * rowHeight - rowHeight / 2) * ((this.cardSpacing * this.fitContentScale) / 100) // + : this.translateY(index); }; /** @@ -493,7 +489,7 @@ export class CollectionCardView extends CollectionSubView() { } if (questionType === '6') { - this.Document.cardSort = 'chat'; + this.Document.card_sort = 'chat'; } listItems.forEach((item, index) => { @@ -549,7 +545,7 @@ export class CollectionCardView extends CollectionSubView() { await this.childPairStringListAndUpdateSortDesc(); }; - childScreenToLocal = computedFn((doc: Doc, index: number, calcRowIndex: number, isSelected: boolean, amCards: number) => () => { + childScreenToLocal = computedFn((doc: Doc, index: number, isSelected: boolean) => () => { // need to explicitly trigger an invalidation since we're reading everything from the Dom this._forceChildXf; this._props.ScreenToLocalTransform(); @@ -560,24 +556,40 @@ export class CollectionCardView extends CollectionSubView() { return new Transform(-translateX + (dref?.centeringX || 0) * scale, -translateY + (dref?.centeringY || 0) * scale, 1) - .scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, calcRowIndex) : 0); // prettier-ignore + .scale(1 / scale).rotate(!isSelected ? -this.rotate(this.centeredIndexOfCardInRow(index)) : 0); // prettier-ignore }); + /** + * Releases the currently focused Doc by deselecting it and returning it to its location on the arch, and selecting the + * cardDeck itself. + * This will also force the Doc to recompute its layout transform when the animation completes. + * In addition, this sets an animating flag on the Doc so that it will receive no poiner events when animating, such as hover + * events that would trigger a flashcard to flip. + * @param doc doc that will be animated away from center focus + */ + releaseCurDoc = action(() => { + const selDoc = this._curDoc; + this._curDoc = undefined; + const cardDocView = DocumentView.getDocumentView(selDoc, this.DocumentView?.()); + if (cardDocView && selDoc) { + DocumentView.DeselectView(cardDocView); + this._props.select(false); + selDoc[Animation] = selDoc; // turns off pointer events & doc decorations while animating - useful for flashcards that reveal back on hover + setTimeout(action(() => { + selDoc[Animation] = undefined; + this._forceChildXf++; + }), 350); // prettier-ignore + } + }); + + /** + * turns off the _dropped flag at the end of a drag/drop, or releases the focused Doc if a different Doc is clicked + */ 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._curDoc === doc || this._dropped) { this._dropped = false; } else { - // otherwise, turn off documentDecorations becase we're in a selection transition and want to avoid artifacts. - // Turn them back on when the animation has completed and the render and backend structures are in synch - SnappingManager.SetHideDecorations(true); - setTimeout( - action(() => { - SnappingManager.SetHideDecorations(false); - this._forceChildXf++; - }), - 1000 - ); + this.releaseCurDoc(); // NOTE: the onClick script for the card will select the new card (ie, 'doc') } }); @@ -585,25 +597,17 @@ export class CollectionCardView extends CollectionSubView() { * Actually renders all the cards */ @computed get renderCards() { - if (!this.childCards.length) { - return null; - } - + console.log('CHILDPw = ' + this.childPanelWidth()); // Map sorted documents to their rendered components return this.sortedDocs.map((doc, index) => { - const calcRowIndex = this.overflowIndexCalc(index); - const amCards = this.overflowAmCardsCalc(index); + const cardsInRow = this.cardsInRowThatIncludesCardIndex(index); + + const childScreenToLocal = this.childScreenToLocal(doc, index, doc === this._curDoc); - const childScreenToLocal = this.childScreenToLocal(doc, index, calcRowIndex, doc === this._curDoc, amCards); + const translateToCenterIfActive = () => (doc === this._curDoc ? (cardsInRow / 2 - (index % this._maxRowCount)) * 100 - 50 : 0); - const translateIfSelected = () => { - const indexInRow = index % this._maxRowCount; - const rowIndex = Math.trunc(index / this._maxRowCount); - const rowCenterIndex = Math.min(this._maxRowCount, this.sortedDocs.length - rowIndex * this._maxRowCount) / 2; - return (rowCenterIndex - indexInRow) * 100 - 50; - }; const aspect = NumCast(doc.height) / NumCast(doc.width, 1); - const vscale = Math.max(1,Math.min((this._props.PanelHeight() * 0.95 * this.fitContentScale) / (aspect * this.childPanelWidth()), + const vscale = Math.max(1,Math.min((this._props.PanelHeight() * 0.95 * this.fitContentScale * this.nativeScaling) / (aspect * this.childPanelWidth()), (this._props.PanelHeight() - 80) / (aspect * (this._props.PanelWidth() / 10)))); // prettier-ignore const hscale = Math.min(this.sortedDocs.length, this._maxRowCount) / 2; // bcz: hack - the grid is divided evenly into maxRowCount cells, so the max scaling would be maxRowCount -- but making things that wide is ugly, so cap it off at half the window size return ( @@ -614,9 +618,9 @@ export class CollectionCardView extends CollectionSubView() { style={{ width: this.childPanelWidth(), height: 'max-content', - 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) + transform: `translateY(${this.adjustCardYtoFitArch(doc === this._curDoc, index)}px) + translateX(calc(${translateToCenterIfActive()}% + ${this.horizontalAdjustmentForPartialRows(index, cardsInRow)}px)) + rotate(${doc !== this._curDoc ? this.rotate(index) : 0}rad) scale(${doc === this._curDoc ? `${Math.min(hscale, vscale) * 100}%` : this._hoveredNodeIndex === index ? 1.1 : 1})`, }} // prettier-ignore onPointerEnter={() => this.setHoveredNodeIndex(index)} @@ -636,26 +640,19 @@ export class CollectionCardView extends CollectionSubView() { ScreenToLocalTransform: this.contentScreenToLocalXf, }); answered = action(() => { - this._curDoc = this.curDoc ? this.filteredChildDocs()[(this.filteredChildDocs().findIndex(this.curDoc) + 1) % (this.filteredChildDocs().length || 1)] : undefined; + this._curDoc = this.curDoc ? this.filteredChildDocs()[(this.filteredChildDocs().findIndex(d => d === this.curDoc()) + 1) % (this.filteredChildDocs().length || 1)] : undefined; }); curDoc = () => this._curDoc; render() { - const isEmpty = this.childCards.length === 0; + const fitContentScale = this.childCards.length === 0 ? 1 : this.fitContentScale; 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 - ); + onPointerDown={action(e => { + if (e.button === 2 || e.ctrlKey) return; + this.releaseCurDoc(); })} onPointerLeave={action(() => (this._docDraggedIndex = -1))} onPointerMove={e => this.onPointerMove(...this._props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY))} @@ -665,16 +662,31 @@ export class CollectionCardView extends CollectionSubView() { color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string, }}> <div - className="card-wrapper" + className="collectionCardView-inner" style={{ - ...(!isEmpty && { transform: `scale(${1 / this.fitContentScale})` }), - ...{ height: `${100 * (isEmpty ? 1 : this.fitContentScale)}%` }, - ...{ width: `${100 * (isEmpty ? 1 : this.fitContentScale)}%` }, - gridAutoRows: `${100 / this.numRows}%`, + transform: `scale(${1 / fitContentScale})`, + height: `${100 * fitContentScale}%`, + width: `${100 * fitContentScale}%`, }}> - {this.renderCards} + <div + className="collectionCardView-cardwrapper" + style={{ + gridAutoRows: `${100 / this.numRows}%`, + height: `${this.cardSpacing}%`, + }}> + {this.renderCards} + </div> + <div + className="collectionCardView-flashcardUI" + style={{ + pointerEvents: this.childCards.length === 0 ? undefined : 'none', + height: `${100 / this.nativeScaling / fitContentScale}%`, + width: `${100 / this.nativeScaling / fitContentScale}%`, + transform: `scale(${this.nativeScaling * fitContentScale})`, + }}> + {this.flashCardUI(this.curDoc, this.docViewProps, this.answered)} + </div> </div> - {this.flashCardUI(this.curDoc, this.docViewProps, this.answered)} </div> ); } diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index e9ace733e..a71cc43ba 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -205,7 +205,7 @@ export class CollectionCarousel3DView extends CollectionSubView() { onPassiveWheel = (e: WheelEvent) => e.stopPropagation(); curDoc = () => this.carouselItems[NumCast(this.layoutDoc._carousel_index)]?.layout; - answered = (correct: boolean) => (!correct || !this.curDoc()) && this.changeSlide(1); + answered = (correct: boolean) => (!correct || !this.curDoc() || NumCast(this.layoutDoc._carousel_index) === this.carouselItems.length - 1) && this.changeSlide(1); docViewProps = () => ({ ...this._props, // isDocumentActive: this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this._props.isContentActive, diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index ff587b199..1f2bc908f 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -203,7 +203,7 @@ export class CollectionCarouselView extends CollectionSubView() { isContentActive: this.isChildContentActive, ScreenToLocalTransform: this.contentScreenToLocalXf, }); - answered = () => this.advance(); + answered = (correct: boolean) => (!correct || !this.curDoc() || NumCast(this.layoutDoc._carousel_index) === this.carouselItems.length - 1) && this.advance(); render() { return ( diff --git a/src/client/views/collections/FlashcardPracticeUI.scss b/src/client/views/collections/FlashcardPracticeUI.scss index 4ed27793d..210c6798f 100644 --- a/src/client/views/collections/FlashcardPracticeUI.scss +++ b/src/client/views/collections/FlashcardPracticeUI.scss @@ -1,3 +1,9 @@ +.FlashcardPracticeUI { + width: 100%; + height: 100%; + display: flex; + align-items: center; +} .FlashcardPracticeUI-remove, .FlashcardPracticeUI-check { position: absolute; diff --git a/src/client/views/collections/FlashcardPracticeUI.tsx b/src/client/views/collections/FlashcardPracticeUI.tsx index 79eb7f107..ec892ee3d 100644 --- a/src/client/views/collections/FlashcardPracticeUI.tsx +++ b/src/client/views/collections/FlashcardPracticeUI.tsx @@ -104,8 +104,8 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp const setPracticeVal = (e: React.MouseEvent, val: string) => { e.stopPropagation(); const curDoc = this._props.curDoc(); - curDoc && (curDoc[this.practiceField] = val); this._props.advance?.(val === practiceVal.CORRECT); + curDoc && (curDoc[this.practiceField] = val); }; return this.practiceMode == practiceMode.PRACTICE && this._props.curDoc() ? ( @@ -182,7 +182,7 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp tryFilterOut = (doc: Doc) => (this.practiceMode && BoolCast(doc?._layout_isFlashcard) && doc[this.practiceField] === practiceVal.CORRECT ? true : false); // show only cards that aren't marked as correct render() { return ( - <> + <div className="FlashcardPracticeUI"> {this.emptyMessage} {this.practiceButtons} {this._props.layoutDoc._chromeHidden ? null : ( @@ -208,7 +208,7 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp {this.practiceModesMenu} </div> )} - </> + </div> ); } } diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 903e04ad7..954c79f7d 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -189,38 +189,38 @@ ScriptingGlobals.add(function showFreeform( setDoc: (doc: Doc, dv: DocumentView) => { Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards}, // prettier-ignore }], ['time', { - checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "time", - setDoc: (doc: Doc, dv: DocumentView) => { doc.cardSort === "time" ? doc.cardSort = '' : doc.cardSort = 'time'}, // prettier-ignore + checkResult: (doc: Doc) => StrCast(doc?.card_sort) === "time", + setDoc: (doc: Doc, dv: DocumentView) => { doc.card_sort === "time" ? doc.card_sort = '' : doc.card_sort = 'time'}, // prettier-ignore }], ['docType', { - checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "type", - setDoc: (doc: Doc, dv: DocumentView) => { doc.cardSort === "type" ? doc.cardSort = '' : doc.cardSort = 'type'}, // prettier-ignore + checkResult: (doc: Doc) => StrCast(doc?.card_sort) === "type", + setDoc: (doc: Doc, dv: DocumentView) => { doc.card_sort === "type" ? doc.card_sort = '' : doc.card_sort = 'type'}, // prettier-ignore }], ['color', { - checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "color", - setDoc: (doc: Doc, dv: DocumentView) => { doc.cardSort === "color" ? doc.cardSort = '' : doc.cardSort = 'color'}, // prettier-ignore + checkResult: (doc: Doc) => StrCast(doc?.card_sort) === "color", + setDoc: (doc: Doc, dv: DocumentView) => { doc.card_sort === "color" ? doc.card_sort = '' : doc.card_sort = 'color'}, // prettier-ignore }], ['tag', { - checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "tag", - setDoc: (doc: Doc, dv: DocumentView) => { doc.cardSort === "tag" ? doc.cardSort = '' : doc.cardSort = 'tag'}, // prettier-ignore + checkResult: (doc: Doc) => StrCast(doc?.card_sort) === "tag", + setDoc: (doc: Doc, dv: DocumentView) => { doc.card_sort === "tag" ? doc.card_sort = '' : doc.card_sort = 'tag'}, // prettier-ignore }], ['up', { - checkResult: (doc: Doc) => BoolCast(!doc?.cardSort_isDesc), + checkResult: (doc: Doc) => BoolCast(!doc?.card_sort_isDesc), setDoc: (doc: Doc, dv: DocumentView) => { - doc.cardSort_isDesc = false; + doc.card_sort_isDesc = false; }, }], ['down', { - checkResult: (doc: Doc) => BoolCast(doc?.cardSort_isDesc), + checkResult: (doc: Doc) => BoolCast(doc?.card_sort_isDesc), setDoc: (doc: Doc, dv: DocumentView) => { - doc.cardSort_isDesc = true; + doc.card_sort_isDesc = true; }, }], ['toggle-chat', { checkResult: (doc: Doc) => GPTPopup.Instance.visible, setDoc: (doc: Doc, dv: DocumentView) => { if (GPTPopup.Instance.visible){ - doc.cardSort = '' + doc.card_sort = '' GPTPopup.Instance.setVisible(false); } else { diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 38ce5f2f7..3c126ea4a 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -8,7 +8,7 @@ import ReactLoading from 'react-loading'; import { imageUrlToBase64, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; -import { DocData } from '../../../fields/DocSymbols'; +import { Animation, DocData } from '../../../fields/DocSymbols'; import { RichTextField } from '../../../fields/RichTextField'; import { BoolCast, DocCast, NumCast, RTFCast, StrCast, toList } from '../../../fields/Types'; import { nullAudio } from '../../../fields/URLField'; @@ -18,7 +18,6 @@ import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; -import { SnappingManager } from '../../util/SnappingManager'; import { undoable } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; @@ -235,7 +234,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() @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 SnappingManager.HideDecorations ? null : ( + return ( <div className="comparisonBox-bottomMenu" style={{ transform: `scale(${this.uiBtnScaling})` }}> {this.revealOpHover || !this._props.isSelected() ? null : this.overlayAlternateIcon} {!this._props.isSelected() || this._renderSide === this.frontKey ? null : ( @@ -666,20 +665,20 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() <> <DocumentView {...this._props} - showTags={undefined} - fitWidth={undefined} // set to returnTrue to make images fill the comparisonBox-- should be a user option - ignoreUsePath={layoutString ? true : undefined} - renderDepth={this.props.renderDepth + 1} - LayoutTemplateString={layoutString} Document={layoutString ? this.Document : targetDoc} - containerViewPath={this._props.docViewPath} - moveDocument={whichSlot === this.frontKey ? this.moveDocFront : this.moveDocBack} - removeDocument={whichSlot === this.frontKey ? this.remDocFront : this.remDocBack} NativeWidth={returnZero} NativeHeight={returnZero} + renderDepth={this.props.renderDepth + 1} + LayoutTemplateString={layoutString} + containerViewPath={this._props.docViewPath} ScreenToLocalTransform={this.contentScreenToLocalXf} - isContentActive={this.childActiveFunc} isDocumentActive={returnFalse} + isContentActive={this.childActiveFunc} + showTags={undefined} + fitWidth={undefined} // set to returnTrue to make images fill the comparisonBox-- should be a user option + ignoreUsePath={layoutString ? true : undefined} + moveDocument={whichSlot === this.frontKey ? this.moveDocFront : this.moveDocBack} + removeDocument={whichSlot === this.frontKey ? this.remDocFront : this.remDocBack} dontSelect={returnTrue} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} styleProvider={this._childActive ? this._props.styleProvider : this.docStyleProvider} @@ -795,7 +794,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() [flashcardRevealOp.SLIDE, this.renderAsBeforeAfter]]); // prettier-ignore if (this.isQuizMode) this.renderAsQuiz(this.frontText); return ( - <div className="comparisonBox" style={{ pointerEvents: this._props.isContentActive() ? 'unset' : undefined }} onContextMenu={this.flashcardContextMenu}> + <div className="comparisonBox" style={{ pointerEvents: this._props.isContentActive() && !this.Document[Animation] ? 'unset' : undefined }} onContextMenu={this.flashcardContextMenu}> {renderMode.get(this.revealOp)?.() ?? null} {this.loading ? ( <div className="loading-spinner"> diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 9aa000ba7..aab8a183a 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/require-default-props */ import { computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; |