aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2024-10-16 17:31:12 -0400
committerbobzel <zzzman@gmail.com>2024-10-16 17:31:12 -0400
commit9779182e74e427d3a8a2004d0efd01fac8387d55 (patch)
treef708b6582f8e39b6353770bf007b504fd1d60d67
parent29b83f023442c313ca5cf95f70f6430f101060e6 (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.ts4
-rw-r--r--src/client/views/DocumentDecorations.tsx4
-rw-r--r--src/client/views/collections/CollectionCardDeckView.scss24
-rw-r--r--src/client/views/collections/CollectionCardDeckView.tsx336
-rw-r--r--src/client/views/collections/CollectionCarousel3DView.tsx2
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx2
-rw-r--r--src/client/views/collections/FlashcardPracticeUI.scss6
-rw-r--r--src/client/views/collections/FlashcardPracticeUI.tsx6
-rw-r--r--src/client/views/global/globalScripts.ts26
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx25
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx1
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';