/* eslint-disable import/prefer-default-export */
import orderBy from 'lodash/orderBy';
import uniqBy from 'lodash/uniqBy';
import { createSelector } from 'reselect';
import { COMMUNITY_GROUP } from '../data/entityTypes';
import { COMMUNITY_GROUP_POSTS } from '../data/collectionPaginationViews';
import { makeCollectionSelector } from './collectionSelector';
import { PAGINATION_OFFSET, PAGINATION_PARTITION } from '../data/paginationType';
/* eslint-disable max-len */
/**
* Contains the function
* {@link module:collectionPaginationViewSelector~makeCollectionPaginationViewSelector|makeCollectionPaginationViewSelector}
* that can be used to select all the entities that should be visible according to the current
* view pagination in the `collectionPaginationViewsReducer`
* @module collectionPaginationViewSelector
* @category redux-state
*/
/**
* Result returned by a `collectionPaginationViewSelector`
* @typedef CollectionPaginationViewSelectorResult
* @property entities {Object[]} An array of data for the entities that are currently in view, if
* found in the `entitiesSelector`
* @property entityRefs {Object[]} An array of the entities that are currently in view
* @property entityRefs[].id {string} The id of the entity
* @property entityRefs[].type {string} The type of the entity
* @property entityRefs[].data {*} The entity data, if found in the `entitiesSelector`. Note that
* this is a reference to the same object as the one in the `entities` array, but it is also
* included here for convenience
* @property allEntityRefs {Object[]} An array of all entities in the collection
* @property allEntityRefs[].id {string} The id of the entity
* @property allEntityRefs[].type {string} The type of the entity
* @property allEntityRefs[].data {*} The entity data, if found in the `entitiesSelector`
* @property pagination {Object} An object with view pagination info
*/
/**
* Selector returned by
* {@link module:collectionPaginationViewSelector~makeCollectionPaginationViewSelector|makeCollectionPaginationViewSelector}
* @param state {Object} The current redux state
* @callback collectionPaginationViewSelector
* @returns {CollectionPaginationViewSelectorResult} An object containing the selector data. See
* {@link module:collectionPaginationViewSelector~CollectionPaginationViewSelectorResult|CollectionPaginationViewSelectorResult}
*/
/**
* Returns a new {@link https://github.com/reactjs/reselect|reselect} selector that can be used
* to get all the entities that should be visible according to the current
* view pagination in the `collectionPaginationViewsReducer`.
* @function makeCollectionPaginationViewSelector
* @param collectionPaginationViewId {string} The id for the collectionPaginationView to read from
* @returns {collectionPaginationViewSelector} The selector. See
* {@link module:collectionPaginationViewSelector~collectionPaginationViewSelector|collectionPaginationViewSelector}
* @example // CommunityOverview/index.js
* const connector = connect(
* () => {
* const collectionPaginationViewSelector = makeCollectionPaginationViewSelector(
* POST_OVERVIEW
* );
*
* return (state) => {
* const posts = collectionPaginationViewSelector(state);
*
* return {
* posts: posts.entities,
* pagination: posts.pagination,
* };
* };
* },
* );
*/
/* eslint-enable max-len */
export const makeCollectionPaginationViewSelector = collectionPaginationViewId => {
const collectionSelector = makeCollectionSelector();
return createSelector(
state =>
collectionSelector(state, {
collectionId:
(state.view.collectionPagination[collectionPaginationViewId] &&
state.view.collectionPagination[collectionPaginationViewId].collection) ||
null,
}),
state =>
(state.view.collectionPagination[collectionPaginationViewId] &&
state.view.collectionPagination[collectionPaginationViewId].offset) ||
0,
state =>
(state.view.collectionPagination[collectionPaginationViewId] &&
state.view.collectionPagination[collectionPaginationViewId].limit) ||
0,
({ pagination: dataPagination, entityRefs: allEntityRefs }, offset, limit) => {
const entityRefs = new Array(limit).fill(null);
const { total, hasMore } = dataPagination;
const pagination = { total, offset, limit, hasMore };
const dataOffset = dataPagination.type === PAGINATION_OFFSET ? dataPagination.offset || 0 : 0;
for (
let i = Math.max(offset, dataOffset);
i - dataOffset < allEntityRefs.length && i - offset < limit;
i++
) {
entityRefs[i - offset] = allEntityRefs[i - dataOffset];
}
if (dataPagination.type === PAGINATION_PARTITION) {
pagination.before = { count: offset, atBegin: !offset && dataPagination.atBegin };
const afterCount = Math.max(allEntityRefs.length - (limit + offset), 0);
pagination.after = {
count: afterCount,
atEnd: !afterCount && dataPagination.atEnd,
};
} else {
pagination.before = { count: offset, atBegin: !offset };
if (total !== null) {
const afterCount = total - (offset + limit);
pagination.after = { count: afterCount, atEnd: afterCount <= 0 };
}
}
return {
entityRefs,
entities: entityRefs.map(e => (e ? e.data : e)),
allEntityRefs,
pagination,
};
},
);
};
export const postsCollectionPaginationViewSelector = (state, collectionPaginationViewId) => {
const collectionPaginationViewSelector = makeCollectionPaginationViewSelector(
collectionPaginationViewId,
);
const paginationData = collectionPaginationViewSelector(state);
const communityGroups = state.entities[COMMUNITY_GROUP];
let sortType = 'isPinned';
// We have 2 seperate flags for isPinned due to the pinning logic, if a post is community group pinned
// then it will not be pinned in the main community feed but will be pinned on the group page. This
// couldn't be driven from one property due to the posts being stored in Redux under one entity
// type which would overwrite the properties depending on where you fetched them. Storing them
// as seperate entities would require rewriting the interactions logic.
if (collectionPaginationViewId === COMMUNITY_GROUP_POSTS) {
sortType = 'isCommunityGroupPinned';
}
const { entityRefs } = paginationData;
const orderedItems = orderBy(entityRefs, post => (post ? post.data[sortType] : post), ['desc']);
const uniqueItems = uniqBy(orderedItems, 'id');
let posts = uniqueItems.filter(post => !!post) || [];
posts = posts.map(({ data: post }) => ({
...post,
communityGroup: communityGroups?.[post?.communityGroupId] || null,
}));
return {
...paginationData,
entities: posts,
};
};