import { WixPatternsContainer, CollectionState, FiltersMap, } from '@wix/bex-core'; import { computed, makeObservable, observable } from 'mobx'; import { ToolbarCollectionState, ToolbarCollectionStateParamsInitParams, } from './ToolbarCollectionState'; import { CollectionDragAndDropState } from '../components/DragAndDrop'; import { RectState } from './RectState'; import { GridSizePreset } from './gridSizePresets'; import { ResponsiveColumnsState } from './ResponsiveColumnsState'; import { GridParentState } from './GridParentState'; import type { Preset } from '../components/Grid/Grid.types'; export type LayoutType = 'grid' | 'list'; export interface GridBaseStateParams { readonly collection: CollectionState; readonly container: WixPatternsContainer; readonly toolbar: ToolbarCollectionState>; readonly sizePreset: GridSizePreset; readonly range?: { start: number; length: number }; readonly layoutType: LayoutType | undefined; readonly parentState: GridParentState; readonly draggable?: 'none' | 'draggable'; } export interface GridBaseStateInitParams extends ToolbarCollectionStateParamsInitParams {} export class GridBaseState { readonly toolbar; readonly container; readonly collection; readonly _containerRectState; readonly _responsiveColumnsState; readonly parentState; _sizePreset; layoutType; trailingItemsCount = 0; minItemWidth = 196; smallCardWidthThreshold = 200; preset: Preset = 'title'; _draggable: 'none' | 'draggable' = 'none'; _localLimit: number | null | undefined; _localStart: number; forceRenderIndexes: number[] | undefined = undefined; collectionDragAndDropState: CollectionDragAndDropState | null = null; get _dnd() { return this.collectionDragAndDropState?.dnd; } constructor(params: GridBaseStateParams) { this.container = params.container; this.collection = params.collection; this.parentState = params.parentState; this.layoutType = params.layoutType ?? 'grid'; this._containerRectState = new RectState({ container: this.container, }); this._draggable = params.draggable ?? 'none'; this.toolbar = params.toolbar as ToolbarCollectionState< unknown, Partial >; this._responsiveColumnsState = new ResponsiveColumnsState({ gridState: this, minItemWidth: this.minItemWidth, rectState: this._containerRectState, }); this._sizePreset = params.sizePreset; this._localLimit = params.range?.length; this._localStart = params.range?.start ?? 0; makeObservable(this, { trailingItemsCount: observable.ref, forceRenderIndexes: observable.ref, preset: observable.ref, _draggable: observable.ref, draggable: computed, itemsToRenderCount: computed, itemsToRender: computed, rowCount: computed, isInteracting: computed, calcGridProps: computed, columnsCount: computed, _sizeProps: computed, itemKeyRecyclerOverride: computed, _localLimit: observable.ref, _localStart: observable.ref, itemsContentWidth: computed, containerPaddingTop: computed, gap: computed, _sizePreset: observable.ref, layoutType: observable.ref, }); } get sizePreset() { return this.parentState._sizePreset ?? this._sizePreset; } get imagePlacement() { return this.sizePreset.imagePlacement; } get gap() { const { layoutType } = this; if (layoutType === 'list') { return 12; } return 24; } get containerPaddingTop() { const { layoutType } = this; if (layoutType === 'list') { return 0; } return this.toolbar.contentPadding; } get aspectRatio() { return this.sizePreset.aspectRatio; } get footerHeights() { return this.sizePreset.footerHeights; } get itemsContentWidth() { return this._responsiveColumnsState.itemsContentWidth; } get draggable() { if (this._dnd?.isDisabled) { return 'none' as const; } return this._draggable; } get window() { return this.container.window; } get itemsToRenderCount() { const { collection, trailingItemsCount, _localLimit, _localStart } = this; const { result: { keyedItems }, } = collection; const itemsCount = _localLimit != null ? Math.min(_localLimit, keyedItems.length) : keyedItems.length; return collection.fetchedLastPage || (_localLimit != null && _localStart + _localLimit < keyedItems.length) ? itemsCount + trailingItemsCount : itemsCount - Math.floor(itemsCount % this.columnsCount); } get reportBi() { return this.toolbar.reportBi; } get columnsCount() { return this.calcGridProps.columnCount; } get containerRect() { return this.toolbar.containerRect; } get rowCount() { const { itemsToRenderCount, columnsCount } = this; return Math.ceil(itemsToRenderCount / columnsCount); } get isInteracting() { return this._dnd?.isActive || this._dnd?.isDropping; } get itemsToRender(): null[] { const { itemsToRenderCount } = this; return new Array(itemsToRenderCount).fill(null); } getItemRenderTypeAt(index: number) { const { collection, itemsToRenderCount, _localStart } = this; const { result: { keyedItems }, } = collection; if (index >= itemsToRenderCount) { return null; } const indexInCollection = index + _localStart; if (indexInCollection >= keyedItems.length) { return 'trailing'; } return keyedItems[indexInCollection]; } get errorMonitor() { return this.container.errorMonitor; } get _sizeProps() { const { aspectRatio, preset, draggable, _responsiveColumnsState: { gap, columnCount: responsiveColumnCount, itemsContentWidth: width, widthWithLastColumnGap, }, layoutType, } = this; const columnCount = layoutType === 'list' ? 1 : responsiveColumnCount; const widthWithoutGaps = width - (columnCount - 1) * gap; const columnWidth = widthWithoutGaps / columnCount; const cardFooterSize = 'small' as const; const cardFooterHeight = this.footerHeights[preset][draggable][cardFooterSize]; const cardTopImageHeight = columnWidth * aspectRatio; const rowHeight = cardTopImageHeight + (cardFooterHeight > 0 ? cardFooterHeight + 1 : 0); const columnWidthIncludingGap = widthWithLastColumnGap / columnCount; const rowHeightIncludingGap = rowHeight + gap; return { columnCount, columnWidth: () => columnWidth, columnWidthIncludingGap: () => columnWidthIncludingGap, rowHeight: () => rowHeight, rowHeightFixed: rowHeight, cardTopImageHeight, rowHeightIncludingGap: () => rowHeightIncludingGap, cardFooterHeight, cardFooterSize, }; } get calcGridProps() { const { toolbar: { itemsContentHeight }, _sizeProps: { rowHeightFixed }, } = this; const overscanCount = Math.round( (itemsContentHeight / rowHeightFixed) * 1.5, ); return { ...this._sizeProps, overscanCount, }; } get getCollectionSnapshot() { return this.toolbar.getCollectionSnapshot; } onAddItemClick = (origin?: string) => { this.toolbar.toolbarBIReporter.onAddItemClick(origin); }; onAddItemActionComplete = async (items: T[]) => { return this.toolbar.onAddItemActionComplete(items); }; get itemKeyRecyclerOverride() { const { collection } = this; const { result: { keyedItems }, } = collection; return (index: number) => { return keyedItems[index]?.key ?? index; }; } get dragAndDropCategories() { return this.toolbar.dragAndDropCategories; } get listAttributes() { return { ...this._dnd?.nullAnnouncements.listAttributes?.(), role: 'list', }; } _subscribe() { const { parentState: { _subGrids }, } = this; if (!_subGrids) { return; } _subGrids.push(this); return () => { const i = _subGrids.indexOf(this); i !== -1 && _subGrids.splice(i, 1); }; } init() { return this._subscribe(); } }