import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import * as React from 'react'; import { returnZero } from '../../../ClientUtils'; import { Utils } from '../../../Utils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { Transform } from '../../util/Transform'; import { PinDocView, PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProp'; import { DocumentView } from '../nodes/DocumentView'; import { FocusViewOptions } from '../nodes/FocusViewOptions'; import './CollectionCarousel3DView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; // eslint-disable-next-line @typescript-eslint/no-require-imports const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = require('../global/globalCssVariables.module.scss'); @observer export class CollectionCarousel3DView extends CollectionSubView() { private _dropDisposer?: DragManager.DragDropDisposer; constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } componentDidMount(): void { this._props.setContentViewBox?.(this); } componentWillUnmount() { this._dropDisposer?.(); } protected createDashEventsTarget = (ele: HTMLDivElement | null) => { this._dropDisposer?.(); if (ele) { this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); } this.fixWheelEvents(ele, this._props.isContentActive); }; @computed get scrollSpeed() { return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; // default scroll speed } @computed get carouselItems() { return this.childLayoutPairs.filter(pair => !pair.layout.layout_isSvg); } centerScale = Number(CAROUSEL3D_CENTER_SCALE); sideScale = Number(CAROUSEL3D_SIDE_SCALE); panelWidth = () => this._props.PanelWidth() / 3 / this.nativeScaling(); panelHeight = () => (this._props.PanelHeight() * this.sideScale) / this.nativeScaling(); onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick); isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive(); isChildContentActive = computedFn( (doc: Doc) => () => this._props.isContentActive?.() === false ? false : this._props.isDocumentActive?.() && (this._props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive)) ? true : this._props.isContentActive?.() && this.curDoc() === doc ? true : this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false ? false : undefined ); contentScreenToLocalXf = () => this._props.ScreenToLocalTransform().translate(0, (-(Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) / this.nativeScaling()); childScreenLeftToLocal = () => this.contentScreenToLocalXf() .translate( (-this.panelWidth() * (1 - this.sideScale)) / 2, // (-this.panelHeight() * (1 - this.sideScale)) / 2 ) .scale(1 / this.sideScale); childScreenRightToLocal = () => this.contentScreenToLocalXf() .translate( -2 * this.panelWidth() - (this.panelWidth() * (1 - this.sideScale)) / 2, // (-this.panelHeight() * (1 - this.sideScale)) / 2 ) .scale(1 / this.sideScale); childCenterScreenToLocal = () => this.contentScreenToLocalXf() .translate( -this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2, // Focused Doc is shifted right by 1/3 panel width then left by increased size percent of center * 1/2 * panel width / 3 ((this.centerScale - 1) * this.panelHeight()) / 2 ) .scale(1 / this.centerScale); focus = (anchor: Doc, options: FocusViewOptions): Opt => { const docs = DocListCast(this.Document[this.fieldKey]); if (anchor.type === DocumentType.CONFIG || docs.includes(anchor)) { const newIndex = anchor.config_carousel_index ?? docs.getIndex(DocCast(anchor.annotationOn, anchor)!); options.didMove = newIndex !== this.layoutDoc._carousel_index; options.didMove && (this.layoutDoc._carousel_index = newIndex); } return undefined; }; getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document, config_carousel_index: this.layoutDoc._carousel_index as number }); PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { collectionType: true, filters: true } }, this.Document); addAsAnnotation && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_annotations', anchor); // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered return anchor; }; addDocTab = this.addLinkedDocTab; @computed get content() { const currentIndex = NumCast(this.layoutDoc._carousel_index); const displayDoc = (child: Doc, dxf: () => Transform) => ( ); return this.carouselItems.map((child, index) => (
{displayDoc(child.layout, index < currentIndex ? this.childScreenLeftToLocal : index === currentIndex ? this.childCenterScreenToLocal : this.childScreenRightToLocal)}
)); } changeSlide = (direction: number) => { this.layoutDoc._carousel_index = !this.curDoc() ? 0 : (NumCast(this.layoutDoc._carousel_index) + direction + this.carouselItems.length) % (this.carouselItems.length || 1); }; onArrowClick = (direction: number) => { this.changeSlide(direction); !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = direction === 1 ? 'fwd' : 'back'); // while autoscroll is on, keep the other autoscroll button hidden !this.layoutDoc.autoScrollOn && this.fadeScrollButton(); // keep pause button visible while autoscroll is on }; interval?: number; startAutoScroll = (direction: number) => { this.interval = window.setInterval(() => { this.changeSlide(direction); }, this.scrollSpeed); }; stopAutoScroll = () => { window.clearInterval(this.interval); this.interval = undefined; this.fadeScrollButton(); }; toggleAutoScroll = (direction: number) => { this.layoutDoc.autoScrollOn = !this.layoutDoc.autoScrollOn; this.layoutDoc.autoScrollOn ? this.startAutoScroll(direction) : this.stopAutoScroll(); }; fadeScrollButton = () => { window.setTimeout(() => { !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = 'none'); // fade away after 1.5s if it's not clicked. }, 1500); }; @computed get buttons() { return (
this.onArrowClick(-1)} />
this.onArrowClick(1)} /> {/* {this.autoScrollButton} */}
); } @computed get autoScrollButton() { const whichButton = this.layoutDoc.showScrollButton; return ( <>
this.toggleAutoScroll(-1)}> {this.layoutDoc.autoScrollOn ? : }
this.toggleAutoScroll(1)}> {this.layoutDoc.autoScrollOn ? : }
); } @computed get dots() { return this.carouselItems.map((_child, index) => (
{ this.layoutDoc._carousel_index = index; }} /> )); } @computed get translateX() { const index = NumCast(this.layoutDoc._carousel_index); return this.panelWidth() * (1 - index); } curDoc = () => this.carouselItems[NumCast(this.layoutDoc._carousel_index)]?.layout; 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, isContentActive: this._props.isContentActive, ScreenToLocalTransform: this.contentScreenToLocalXf, }); nativeScaling = () => this._props.NativeDimScaling?.() || 1; render() { return (
{this.content}
{this.buttons}
{this.dots}
{this.flashCardUI(this.curDoc, this.docViewProps, this.answered)}
); } }