aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/CollectionCardDeckView.tsx
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2024-10-04 18:39:15 -0400
committerbobzel <zzzman@gmail.com>2024-10-04 18:39:15 -0400
commitf63aa4c65f6336c7708c50ced83867702a310240 (patch)
treec524602d8e2f564f083633f994c1978df65e09e9 /src/client/views/collections/CollectionCardDeckView.tsx
parentb791fc559112b5972211a5382d07ce15ad07132a (diff)
cleaning up CardDeckView so that animations work smoothly
Diffstat (limited to 'src/client/views/collections/CollectionCardDeckView.tsx')
-rw-r--r--src/client/views/collections/CollectionCardDeckView.tsx199
1 files changed, 84 insertions, 115 deletions
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 101cc8082..a9ab9de26 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -2,7 +2,6 @@ import { IReactionDisposer, ObservableMap, action, computed, makeObservable, obs
import { observer } from 'mobx-react';
import * as React from 'react';
import { ClientUtils, DashColor, returnFalse, returnZero } from '../../../ClientUtils';
-import { emptyFunction } from '../../../Utils';
import { Doc } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
@@ -13,7 +12,6 @@ import { gptImageLabel } from '../../apis/gpt/GPT';
import { DocumentType } from '../../documents/DocumentTypes';
import { DragManager } from '../../util/DragManager';
import { dropActionType } from '../../util/DropActionTypes';
-import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoable } from '../../util/UndoManager';
@@ -23,12 +21,12 @@ import { DocumentView } 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',
Type = 'type',
Color = 'color',
- Custom = 'custom',
Chat = 'chat',
Tag = 'tag',
None = '',
@@ -46,14 +44,13 @@ export class CollectionCardView extends CollectionSubView() {
private _dropDisposer?: DragManager.DragDropDisposer;
private _disposers: { [key: string]: IReactionDisposer } = {};
private _textToDoc = new Map<string, Doc>();
- private _dropped = false; // indicate when a card doc has just moved;
+ 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)
@observable _forceChildXf = 0;
@observable _hoveredNodeIndex = -1;
@observable _docRefs = new ObservableMap<Doc, DocumentView>();
@observable _maxRowCount = 10;
@observable _docDraggedIndex: number = -1;
- @observable overIndex: number = -1;
static imageUrlToBase64 = async (imageUrl: string): Promise<string> => {
try {
@@ -101,7 +98,6 @@ export class CollectionCardView extends CollectionSubView() {
componentDidMount() {
this._props.setContentViewBox?.(this);
- // Reaction to cardSort changes
this._disposers.sort = reaction(
() => GPTPopup.Instance.visible,
isVis => {
@@ -135,8 +131,7 @@ export class CollectionCardView extends CollectionSubView() {
}
/**
- * The child documents to be rendered-- either all of them except the Links or the docs in the currently active
- * custom group
+ * 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);
@@ -155,8 +150,7 @@ export class CollectionCardView extends CollectionSubView() {
*/
quizMode = () => {
const randomIndex = Math.floor(Math.random() * this.childDocs.length);
- SelectionManager.DeselectAll();
- DocumentView.SelectView(DocumentView.getDocumentView(this.childDocs[randomIndex]), false);
+ DocumentView.getDocumentView(this.childDocs[randomIndex])?.select(false);
};
/**
@@ -168,29 +162,15 @@ export class CollectionCardView extends CollectionSubView() {
@action
setHoveredNodeIndex = (index: number) => {
- if (!DocumentView.SelectedDocs().includes(this.childDocs[index])) {
- this._hoveredNodeIndex = index;
- }
+ if (!SnappingManager.IsDragging) this._hoveredNodeIndex = index;
};
- /**
- * Translates the hovered node to the center of the screen
- * @param index
- * @returns
- */
- translateHover = (index: number) => (this._hoveredNodeIndex === index && !DocumentView.SelectedDocs().includes(this.childDocs[index]) ? -50 : 0);
-
- isSelected = (index: number) => DocumentView.SelectedDocs().includes(this.childDocs[index]);
-
- /**
- * Returns all the documents except the one that's currently selected
- */
- inactiveDocs = () => this.childDocsWithoutLinks.filter(d => !DocumentView.SelectedDocs().includes(d));
+ isSelected = (doc: Doc) => this._docRefs.get(doc)?.IsSelected;
childPanelWidth = () => NumCast(this.layoutDoc.childPanelWidth, this._props.PanelWidth() / 2);
childPanelHeight = () => this._props.PanelHeight() * this.fitContentScale;
onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
- isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
- isChildContentActive = () => !!this.isContentActive();
+ 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
@@ -202,13 +182,14 @@ export class CollectionCardView extends CollectionSubView() {
if (amCards == 1) return 0;
const possRotate = -30 + index * (30 / ((amCards - (amCards % 2)) / 2));
- const stepMag = Math.abs(-30 + (amCards / 2 - 1) * (30 / ((amCards - (amCards % 2)) / 2)));
-
- if (amCards % 2 === 0 && possRotate === 0) {
- return possRotate + Math.abs(-30 + (index - 1) * (30 / (amCards / 2)));
- }
- if (amCards % 2 === 0 && index > (amCards + 1) / 2) {
- return possRotate + stepMag;
+ 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;
@@ -274,22 +255,13 @@ export class CollectionCardView extends CollectionSubView() {
/**
* Checks to see if a card is being dragged and calls the appropriate methods if so
- * @param e the current pointer event
*/
-
@action
onPointerMove = (x: number, y: number) => {
this._docDraggedIndex = DragManager.docsBeingDragged.length ? this.findCardDropIndex(x, y) : -1;
};
/**
- * Handles external drop of images/PDFs etc from outside Dash.
- */
- onExternalDrop = async (e: React.DragEvent): Promise<void> => {
- super.onExternalDrop(e, {});
- };
-
- /**
* Resets all the doc dragging vairables once a card is dropped
* @param e
* @param de drop event
@@ -326,7 +298,7 @@ export class CollectionCardView extends CollectionSubView() {
}
/**
- * Used to determine how to sort cards based on tags. The lestmost tags are given lower values while cards to the right are
+ * 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
* @param doc the doc whose value is being determined
* @returns its value based on its tags
@@ -352,24 +324,14 @@ export class CollectionCardView extends CollectionSubView() {
docs.sort((docA, docB) => {
const [typeA, typeB] = (() => {
switch (sortType) {
- case cardSortings.Time:
- return [DateCast(docA.author_date)?.date ?? Date.now(), DateCast(docB.author_date)?.date ?? Date.now()];
- case cardSortings.Color: {
- const d1 = DashColor(StrCast(docA.backgroundColor));
- const d2 = DashColor(StrCast(docB.backgroundColor));
- return [d1.hsv().hue(), d2.hsv().hue()];
- }
- case cardSortings.Tag:
- return [this.tagValue(docA) ?? 9999, this.tagValue(docB) ?? 9999];
- case cardSortings.Chat:
- return [NumCast(docA.chatIndex) ?? 9999, NumCast(docB.chatIndex) ?? 9999];
- default:
- return [StrCast(docA.type), StrCast(docB.type)];
+ default:
+ case cardSortings.Type: return [StrCast(docA.type), StrCast(docB.type)];
+ case cardSortings.Chat: return [NumCast(docA.chatIndex, 9999), NumCast(docB.chatIndex,9999)];
+ case cardSortings.Time: return [DateCast(docA.author_date)?.date ?? Date.now(), DateCast(docB.author_date)?.date ?? Date.now()];
+ case cardSortings.Color:return [DashColor(StrCast(docA.backgroundColor)).hsv().hue(), DashColor(StrCast(docB.backgroundColor)).hsv().hue()];
}
- })();
-
- const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0;
- return isDesc ? out : -out;
+ })(); //prettier-ignore
+ return (typeA < typeB ? -1 : typeA > typeB ? 1 : 0) * (isDesc ? 1 : -1);
});
if (dragIndex !== -1) {
const draggedDoc = DragManager.docsBeingDragged[0];
@@ -382,6 +344,15 @@ 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
+ ? false
+ : undefined;
+
displayDoc = (doc: Doc, screenToLocalTransform: () => Transform) => (
<DocumentView
{...this._props}
@@ -396,13 +367,14 @@ export class CollectionCardView extends CollectionSubView() {
LayoutTemplateString={this._props.childLayoutString}
containerViewPath={this.childContainerViewPath}
ScreenToLocalTransform={screenToLocalTransform} // makes sure the box wrapper thing is in the right spot
- isContentActive={emptyFunction}
isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
PanelWidth={this.childPanelWidth}
PanelHeight={this.childPanelHeight}
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}
dontHideOnDrag
/>
);
@@ -416,13 +388,11 @@ export class CollectionCardView extends CollectionSubView() {
if (this.sortedDocs.length < this._maxRowCount) {
return this.sortedDocs.length;
}
- // 13 - 3 = 10
const totalCards = this.sortedDocs.length;
// if 9 or less
if (index < totalCards - (totalCards % this._maxRowCount)) {
return this._maxRowCount;
}
- // (3)
return totalCards % this._maxRowCount;
};
/**
@@ -443,17 +413,17 @@ export class CollectionCardView extends CollectionSubView() {
/**
* Determines how far to translate a card in the y direction depending on its index, whether or not its being hovered, or if it's selected
* @param isHovered
- * @param isSelected
+ * @param isActive
* @param realIndex
* @param amCards
* @param calcRowIndex
* @returns
*/
- calculateTranslateY = (isHovered: boolean, isSelected: boolean, realIndex: number, amCards: number, calcRowIndex: number) => {
+ calculateTranslateY = (isHovered: boolean, isActive: boolean, realIndex: number, amCards: number, calcRowIndex: number) => {
const rowHeight = (this._props.PanelHeight() * this.fitContentScale) / this.numRows;
const rowIndex = Math.trunc(realIndex / this._maxRowCount);
const rowToCenterShift = this.numRows / 2 - rowIndex;
- if (isSelected) return rowToCenterShift * rowHeight - rowHeight / 2;
+ if (isActive) return rowToCenterShift * rowHeight - rowHeight / 2;
if (amCards == 1) return 50 * this.fitContentScale;
return this.translateY(amCards, calcRowIndex, realIndex);
};
@@ -576,15 +546,43 @@ export class CollectionCardView extends CollectionSubView() {
await this.childPairStringListAndUpdateSortDesc();
};
+ childScreenToLocal = computedFn((doc: Doc, index: number, calcRowIndex: number, isSelected: boolean, amCards: number) => () => {
+ // need to explicitly trigger an invalidation since we're reading everything from the Dom
+ this._forceChildXf;
+ this._props.ScreenToLocalTransform();
+
+ const dref = this._docRefs.get(doc);
+ const { translateX, translateY, scale } = ClientUtils.GetScreenTransform(dref?.ContentDiv);
+ if (!scale) return new Transform(0, 0, 0);
+
+ 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
+ });
+
+ 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) {
+ 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.SetIsResizing(doc[Id]);
+ setTimeout(
+ action(() => {
+ SnappingManager.SetIsResizing(undefined);
+ this._forceChildXf++;
+ }),
+ 1000
+ );
+ }
+ });
+
/**
* Actually renders all the cards
*/
@computed get renderCards() {
- const sortedDocs = this.sortedDocs;
- const anySelected = this.childDocs.some(doc => DocumentView.SelectedDocs().includes(doc));
- const isEmpty = this.childDocsWithoutLinks.length === 0;
-
- if (isEmpty) {
+ 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
@@ -593,31 +591,18 @@ export class CollectionCardView extends CollectionSubView() {
}
// Map sorted documents to their rendered components
- return sortedDocs.map((doc, index) => {
- const realIndex = sortedDocs.indexOf(doc);
+ return this.sortedDocs.map((doc, index) => {
+ const realIndex = this.sortedDocs.indexOf(doc);
const calcRowIndex = this.overflowIndexCalc(realIndex);
const amCards = this.overflowAmCardsCalc(realIndex);
const view = DocumentView.getDocumentView(doc, this.DocumentView?.());
- const isSelected = view?.ComponentView?.isAnyChildContentActive?.() || view?.IsSelected ? true : false;
- const childScreenToLocal = () => {
- // need to explicitly trigger an invalidation since we're reading everything from the Dom
- this._forceChildXf;
- this._props.ScreenToLocalTransform();
-
- const dref = this._docRefs.get(doc);
- const { translateX, translateY, scale } = ClientUtils.GetScreenTransform(dref?.ContentDiv);
- if (!scale) return new Transform(0, 0, 0);
-
- 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
- };
+ const childScreenToLocal = this.childScreenToLocal(doc, index, calcRowIndex, !!view?.IsContentActive, amCards);
const translateIfSelected = () => {
const indexInRow = index % this._maxRowCount;
const rowIndex = Math.trunc(index / this._maxRowCount);
- const rowCenterIndex = Math.min(this._maxRowCount, sortedDocs.length - rowIndex * this._maxRowCount) / 2;
+ 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);
@@ -627,33 +612,18 @@ export class CollectionCardView extends CollectionSubView() {
return (
<div
key={doc[Id]}
- className={`card-item${isSelected ? '-active' : anySelected ? '-inactive' : ''}`}
- onPointerUp={action(() => {
- // if a card doc has just moved, or a card is selected and in front, then ignore this event
- if (DocumentView.SelectedDocs().includes(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.SetIsResizing(doc[Id]);
- setTimeout(
- action(() => {
- SnappingManager.SetIsResizing(undefined);
- this._forceChildXf++;
- }),
- 1000
- );
- }
- })}
+ className={`card-item${view?.IsContentActive ? '-active' : this.isAnyChildContentActive() ? '-inactive' : ''}`}
+ onPointerUp={() => this.cardPointerUp(doc)}
style={{
width: this.childPanelWidth(),
height: 'max-content',
- transform: `translateY(${this.calculateTranslateY(this._hoveredNodeIndex === index, isSelected, realIndex, amCards, calcRowIndex)}px)
- translateX(calc(${isSelected ? translateIfSelected() : 0}% + ${this.translateOverflowX(realIndex, amCards)}px))
- rotate(${!isSelected ? this.rotate(amCards, calcRowIndex) : 0}deg)
- scale(${isSelected ? `${Math.min(hscale, vscale) * 100}%` : this._hoveredNodeIndex === index ? 1.05 : 1})`,
+ transform: `translateY(${this.calculateTranslateY(this._hoveredNodeIndex === index, !!view?.IsContentActive, realIndex, amCards, calcRowIndex)}px)
+ translateX(calc(${view?.IsContentActive ? translateIfSelected() : 0}% + ${this.translateOverflowX(realIndex, 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})`,
}} // prettier-ignore
- onPointerEnter={() => !SnappingManager.IsDragging && this.setHoveredNodeIndex(index)}>
+ onPointerEnter={() => this.setHoveredNodeIndex(index)}
+ onPointerLeave={() => this.setHoveredNodeIndex(-1)}>
{this.displayDoc(doc, childScreenToLocal)}
</div>
);
@@ -680,8 +650,7 @@ export class CollectionCardView extends CollectionSubView() {
...(!isEmpty && { transform: `scale(${1 / this.fitContentScale})` }),
...(!isEmpty && { height: `${100 * this.fitContentScale}%` }),
gridAutoRows: `${100 / this.numRows}%`,
- }}
- onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
+ }}>
{this.renderCards}
</div>
</div>