fix: properly handle default group and filter (#1083)

This commit is contained in:
Daniel Lautzenheiser 2024-02-08 10:42:35 -05:00 committed by GitHub
parent ccd242c06f
commit 2c72215e2d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 174 additions and 157 deletions

View File

@ -145,6 +145,8 @@ function applyFolderCollectionDefaults(
): FolderCollectionWithDefaults {
const collection: FolderCollectionWithDefaults = {
...originalCollection,
view_filters: undefined,
view_groups: undefined,
i18n: collectionI18n,
};
@ -228,6 +230,8 @@ function applyFilesCollectionDefaults(
const collection: FilesCollectionWithDefaults = {
...originalCollection,
i18n: collectionI18n,
view_filters: undefined,
view_groups: undefined,
files: originalCollection.files.map(f =>
applyCollectionFileDefaults(f, originalCollection, collectionI18n, config),
),
@ -271,7 +275,7 @@ function applyCollectionDefaults(
collection.fields = setI18nDefaultsForFields(collection.fields, Boolean(collectionI18n));
}
const { view_filters, view_groups } = collection;
const { view_filters, view_groups } = originalCollection;
if (!collection.sortable_fields) {
collection.sortable_fields = {
@ -280,7 +284,7 @@ function applyCollectionDefaults(
}
collection.view_filters = {
default: collection.view_filters?.default,
default: originalCollection.view_filters?.default,
filters: (view_filters?.filters ?? []).map(filter => {
return {
...filter,
@ -290,7 +294,7 @@ function applyCollectionDefaults(
};
collection.view_groups = {
default: collection.view_groups?.default,
default: originalCollection.view_groups?.default,
groups: (view_groups?.groups ?? []).map(group => {
return {
...group,

View File

@ -44,11 +44,7 @@ import { Cursor } from '../lib/util';
import { getFields, updateFieldByKey } from '../lib/util/collection.util';
import { createEmptyDraftData, createEmptyDraftI18nData } from '../lib/util/entry.util';
import { selectCollectionEntriesCursor } from '../reducers/selectors/cursors';
import {
selectEntriesSelectedSort,
selectIsFetching,
selectPublishedSlugs,
} from '../reducers/selectors/entries';
import { selectIsFetching, selectPublishedSlugs } from '../reducers/selectors/entries';
import { addSnackbar } from '../store/slices/snackbars';
import { createAssetProxy } from '../valueObjects/AssetProxy';
import createEntry from '../valueObjects/createEntry';
@ -73,7 +69,9 @@ import type {
SortDirection,
ValueOrNestedValue,
ViewFilter,
ViewFilterWithDefaults,
ViewGroup,
ViewGroupWithDefaults,
} from '../interface';
import type { RootState } from '../store';
import type AssetProxy from '../valueObjects/AssetProxy';
@ -122,7 +120,10 @@ export function entriesLoading(collection: CollectionWithDefaults) {
} as const;
}
export function filterEntriesRequest(collection: CollectionWithDefaults, filter: ViewFilter) {
export function filterEntriesRequest(
collection: CollectionWithDefaults,
filter: ViewFilterWithDefaults,
) {
return {
type: FILTER_ENTRIES_REQUEST,
payload: {
@ -162,7 +163,10 @@ export function filterEntriesFailure(
} as const;
}
export function groupEntriesRequest(collection: CollectionWithDefaults, group: ViewGroup) {
export function groupEntriesRequest(
collection: CollectionWithDefaults,
group: ViewGroupWithDefaults,
) {
return {
type: GROUP_ENTRIES_REQUEST,
payload: {
@ -315,7 +319,7 @@ export function sortByField(
};
}
export function filterByField(collection: CollectionWithDefaults, filter: ViewFilter) {
export function filterByField(collection: CollectionWithDefaults, filter: ViewFilterWithDefaults) {
return async (dispatch: ThunkDispatch<RootState, {}, AnyAction>, getState: () => RootState) => {
const state = getState();
// if we're already fetching we update the filter key, but skip loading entries
@ -334,17 +338,12 @@ export function filterByField(collection: CollectionWithDefaults, filter: ViewFi
};
}
export function groupByField(collection: CollectionWithDefaults, group: ViewGroup) {
export function groupByField(collection: CollectionWithDefaults, group: ViewGroupWithDefaults) {
return async (dispatch: ThunkDispatch<RootState, {}, AnyAction>, getState: () => RootState) => {
const state = getState();
const isFetching = selectIsFetching(state, collection.name);
dispatch({
type: GROUP_ENTRIES_REQUEST,
payload: {
collection: collection.name,
group,
},
});
dispatch(groupEntriesRequest(collection, group));
if (isFetching) {
return;
}
@ -699,10 +698,6 @@ export function loadEntries(collection: CollectionWithDefaults, page = 0) {
return;
}
const state = getState();
const sortField = selectEntriesSelectedSort(state, collection.name);
if (sortField) {
return dispatch(sortByField(collection, sortField.key, sortField.direction));
}
const configState = state.config;
if (!configState.config) {
@ -751,6 +746,7 @@ export function loadEntries(collection: CollectionWithDefaults, page = 0) {
);
} catch (error: unknown) {
console.error(error);
if (error instanceof Error) {
dispatch(
addSnackbar({

View File

@ -7,16 +7,16 @@ import GroupControl from './GroupControl';
import MobileCollectionControls from './mobile/MobileCollectionControls';
import SortControl from './SortControl';
import type { ViewStyle } from '@staticcms/core/constants/views';
import type {
FilterMap,
GroupMap,
SortableField,
SortDirection,
SortMap,
ViewFilter,
ViewGroup,
ViewFilterWithDefaults,
ViewGroupWithDefaults,
} from '@staticcms/core';
import type { ViewStyle } from '@staticcms/core/constants/views';
import type { FC } from 'react';
interface CollectionControlsProps {
@ -26,11 +26,11 @@ interface CollectionControlsProps {
onSortClick?: (key: string, direction?: SortDirection) => Promise<void>;
sort?: SortMap | undefined;
filter?: Record<string, FilterMap>;
viewFilters?: ViewFilter[];
onFilterClick?: (filter: ViewFilter) => void;
viewFilters?: ViewFilterWithDefaults[];
onFilterClick?: (filter: ViewFilterWithDefaults) => void;
group?: Record<string, GroupMap>;
viewGroups?: ViewGroup[];
onGroupClick?: (filter: ViewGroup) => void;
viewGroups?: ViewGroupWithDefaults[];
onGroupClick?: (filter: ViewGroupWithDefaults) => void;
}
const CollectionControls: FC<CollectionControlsProps> = ({

View File

@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useMemo } from 'react';
import {
changeViewStyle,
@ -6,7 +6,6 @@ import {
groupByField,
sortByField,
} from '@staticcms/core/actions/entries';
import { SORT_DIRECTION_ASCENDING } from '@staticcms/core/constants';
import useTranslate from '@staticcms/core/lib/hooks/useTranslate';
import {
getSortableFields,
@ -28,8 +27,13 @@ import CollectionHeader from './CollectionHeader';
import EntriesCollection from './entries/EntriesCollection';
import EntriesSearch from './entries/EntriesSearch';
import type {
CollectionWithDefaults,
SortDirection,
ViewFilterWithDefaults,
ViewGroupWithDefaults,
} from '@staticcms/core';
import type { ViewStyle } from '@staticcms/core/constants/views';
import type { CollectionWithDefaults, SortDirection, ViewFilter, ViewGroup } from '@staticcms/core';
import type { FC } from 'react';
import './Collection.css';
@ -70,13 +74,6 @@ const CollectionView: FC<CollectionViewProps> = ({
const filter = useAppSelector(state => selectEntriesFilter(state, collection?.name));
const group = useAppSelector(state => selectEntriesGroup(state, collection?.name));
const [readyToLoad, setReadyToLoad] = useState(false);
const [prevCollection, setPrevCollection] = useState<CollectionWithDefaults | null>();
useEffect(() => {
setPrevCollection(collection);
}, [collection]);
const searchResultKey = useMemo(
() => `collection.collectionTop.searchResults${isSingleSearchResult ? 'InCollection' : ''}`,
[isSingleSearchResult],
@ -110,12 +107,7 @@ const CollectionView: FC<CollectionViewProps> = ({
}
return (
<EntriesCollection
collection={collection}
viewStyle={viewStyle}
filterTerm={filterTerm}
readyToLoad={readyToLoad && collection === prevCollection}
/>
<EntriesCollection collection={collection} viewStyle={viewStyle} filterTerm={filterTerm} />
);
}, [
collection,
@ -123,8 +115,6 @@ const CollectionView: FC<CollectionViewProps> = ({
filterTerm,
isSearchResults,
isSingleSearchResult,
prevCollection,
readyToLoad,
searchTerm,
viewStyle,
]);
@ -137,14 +127,14 @@ const CollectionView: FC<CollectionViewProps> = ({
);
const onFilterClick = useCallback(
async (filter: ViewFilter) => {
async (filter: ViewFilterWithDefaults) => {
collection && (await dispatch(filterByField(collection, filter)));
},
[collection, dispatch],
);
const onGroupClick = useCallback(
async (group: ViewGroup) => {
async (group: ViewGroupWithDefaults) => {
collection && (await dispatch(groupByField(collection, group)));
},
[collection, dispatch],
@ -157,80 +147,6 @@ const CollectionView: FC<CollectionViewProps> = ({
[dispatch],
);
useEffect(() => {
if (prevCollection === collection) {
if (!readyToLoad) {
setReadyToLoad(true);
}
return;
}
if (sort?.[0]?.key) {
if (!readyToLoad) {
setReadyToLoad(true);
}
return;
}
const defaultSort = collection?.sortable_fields?.default;
const defaultViewGroupName = collection?.view_groups?.default;
const defaultViewFilterName = collection?.view_filters?.default;
if (!defaultViewGroupName && !defaultViewFilterName && (!defaultSort || !defaultSort.field)) {
if (!readyToLoad) {
setReadyToLoad(true);
}
return;
}
setReadyToLoad(false);
let alive = true;
const sortGroupFilterEntries = () => {
setTimeout(async () => {
if (defaultSort && defaultSort.field) {
await onSortClick(defaultSort.field, defaultSort.direction ?? SORT_DIRECTION_ASCENDING);
}
if (defaultViewGroupName) {
const defaultViewGroup = viewGroups?.groups.find(g => g.name === defaultViewGroupName);
if (defaultViewGroup) {
await onGroupClick(defaultViewGroup);
}
}
if (defaultViewFilterName) {
const defaultViewFilter = viewFilters?.filters.find(
f => f.name === defaultViewFilterName,
);
if (defaultViewFilter) {
await onFilterClick(defaultViewFilter);
}
}
if (alive) {
setReadyToLoad(true);
}
});
};
sortGroupFilterEntries();
return () => {
alive = false;
};
}, [
collection,
onFilterClick,
onGroupClick,
onSortClick,
prevCollection,
readyToLoad,
sort,
viewFilters?.filters,
viewGroups?.groups,
]);
const collectionDescription = collection?.description;
return (

View File

@ -7,8 +7,8 @@ import Menu from '../common/menu/Menu';
import MenuGroup from '../common/menu/MenuGroup';
import MenuItemButton from '../common/menu/MenuItemButton';
import type { FilterMap, ViewFilter } from '@staticcms/core';
import type { MouseEvent, FC } from 'react';
import type { FilterMap, ViewFilterWithDefaults } from '@staticcms/core';
import type { FC, MouseEvent } from 'react';
import './FilterControl.css';
@ -24,9 +24,9 @@ export const classes = generateClassNames('FilterControl', [
export interface FilterControlProps {
filter: Record<string, FilterMap> | undefined;
viewFilters: ViewFilter[] | undefined;
viewFilters: ViewFilterWithDefaults[] | undefined;
variant?: 'menu' | 'list';
onFilterClick: ((viewFilter: ViewFilter) => void) | undefined;
onFilterClick: ((viewFilter: ViewFilterWithDefaults) => void) | undefined;
}
const FilterControl: FC<FilterControlProps> = ({
@ -40,7 +40,7 @@ const FilterControl: FC<FilterControlProps> = ({
const anyActive = useMemo(() => Object.keys(filter).some(key => filter[key]?.active), [filter]);
const handleFilterClick = useCallback(
(viewFilter: ViewFilter) => (event: MouseEvent) => {
(viewFilter: ViewFilterWithDefaults) => (event: MouseEvent) => {
event.stopPropagation();
event.preventDefault();
onFilterClick?.(viewFilter);

View File

@ -7,7 +7,7 @@ import Menu from '../common/menu/Menu';
import MenuGroup from '../common/menu/MenuGroup';
import MenuItemButton from '../common/menu/MenuItemButton';
import type { GroupMap, ViewGroup } from '@staticcms/core';
import type { GroupMap, ViewGroupWithDefaults } from '@staticcms/core';
import type { FC, MouseEvent } from 'react';
import './GroupControl.css';
@ -25,9 +25,9 @@ export const classes = generateClassNames('GroupControl', [
export interface GroupControlProps {
group: Record<string, GroupMap> | undefined;
viewGroups: ViewGroup[] | undefined;
viewGroups: ViewGroupWithDefaults[] | undefined;
variant?: 'menu' | 'list';
onGroupClick: ((viewGroup: ViewGroup) => void) | undefined;
onGroupClick: ((viewGroup: ViewGroupWithDefaults) => void) | undefined;
}
const GroupControl: FC<GroupControlProps> = ({
@ -41,7 +41,7 @@ const GroupControl: FC<GroupControlProps> = ({
const activeGroup = useMemo(() => Object.values(group).find(f => f.active === true), [group]);
const handleGroupClick = useCallback(
(viewGroup: ViewGroup) => (event: MouseEvent) => {
(viewGroup: ViewGroupWithDefaults) => (event: MouseEvent) => {
event.stopPropagation();
event.preventDefault();
onGroupClick?.(viewGroup);

View File

@ -15,8 +15,8 @@ import Button from '../../common/button/Button';
import Entries from './Entries';
import entriesClasses from './Entries.classes';
import type { ViewStyle } from '@staticcms/core/constants/views';
import type { CollectionWithDefaults, Entry, GroupOfEntries } from '@staticcms/core';
import type { ViewStyle } from '@staticcms/core/constants/views';
import type { RootState } from '@staticcms/core/store';
import type { FC } from 'react';
import type { t } from 'react-polyglot';
@ -65,13 +65,11 @@ const EntriesCollection: FC<EntriesCollectionProps> = ({
cursor,
page,
entriesLoaded,
readyToLoad,
}) => {
const t = useTranslate();
const dispatch = useAppDispatch();
const [prevReadyToLoad, setPrevReadyToLoad] = useState(false);
const [prevCollection, setPrevCollection] = useState(collection);
const groups = useGroups(collection.name);
@ -89,26 +87,12 @@ const EntriesCollection: FC<EntriesCollectionProps> = ({
}, [collection, entries, filterTerm]);
useEffect(() => {
if (
collection &&
!entriesLoaded &&
readyToLoad &&
(!prevReadyToLoad || prevCollection !== collection)
) {
if (collection && !entriesLoaded && prevCollection !== collection) {
dispatch(loadEntries(collection));
}
setPrevReadyToLoad(readyToLoad);
setPrevCollection(collection);
}, [
collection,
dispatch,
entriesLoaded,
prevCollection,
prevReadyToLoad,
readyToLoad,
useWorkflow,
]);
}, [collection, dispatch, entriesLoaded, prevCollection, useWorkflow]);
const handleCursorActions = useCallback(
(action: string) => {
@ -182,7 +166,6 @@ const EntriesCollection: FC<EntriesCollectionProps> = ({
interface EntriesCollectionOwnProps {
collection: CollectionWithDefaults;
viewStyle: ViewStyle;
readyToLoad: boolean;
filterTerm: string;
}

View File

@ -89,9 +89,9 @@ export type SortMap = Record<string, SortObject>;
export type Sort = Record<string, SortMap>;
export type FilterMap = ViewFilter & { active?: boolean };
export type FilterMap = ViewFilterWithDefaults & { active?: boolean };
export type GroupMap = ViewGroup & { active?: boolean };
export type GroupMap = ViewGroupWithDefaults & { active?: boolean };
export type Filter = Record<string, Record<string, FilterMap>>; // collection.field.active
@ -305,6 +305,8 @@ export interface BaseCollection {
export interface BaseCollectionWithDefaults extends Omit<BaseCollection, 'i18n'> {
i18n?: I18nInfo;
view_filters?: ViewFiltersWithDefaults;
view_groups?: ViewGroupsWithDefaults;
}
export interface FilesCollection<EF extends BaseField = UnknownField> extends BaseCollection {
@ -958,11 +960,19 @@ export interface ViewFilter {
pattern: string | boolean | number;
}
export interface ViewFilterWithDefaults extends ViewFilter {
id: string;
}
export interface ViewFilters {
default?: string;
filters: ViewFilter[];
}
export interface ViewFiltersWithDefaults extends ViewFilters {
filters: ViewFilterWithDefaults[];
}
export interface ViewGroup {
id?: string;
name: string;
@ -971,11 +981,19 @@ export interface ViewGroup {
pattern?: string;
}
export interface ViewGroupWithDefaults extends ViewGroup {
id: string;
}
export interface ViewGroups {
default?: string;
groups: ViewGroup[];
}
export interface ViewGroupsWithDefaults extends ViewGroups {
groups: ViewGroupWithDefaults[];
}
export type SortDirection =
| typeof SORT_DIRECTION_ASCENDING
| typeof SORT_DIRECTION_DESCENDING

View File

@ -3,6 +3,7 @@ import sortBy from 'lodash/sortBy';
import {
CHANGE_VIEW_STYLE,
CONFIG_SUCCESS,
ENTRIES_FAILURE,
ENTRIES_REQUEST,
ENTRIES_SUCCESS,
@ -18,6 +19,7 @@ import {
GROUP_ENTRIES_REQUEST,
GROUP_ENTRIES_SUCCESS,
SEARCH_ENTRIES_SUCCESS,
SORT_DIRECTION_ASCENDING,
SORT_ENTRIES_FAILURE,
SORT_ENTRIES_REQUEST,
SORT_ENTRIES_SUCCESS,
@ -25,6 +27,7 @@ import {
import { VIEW_STYLES, VIEW_STYLE_TABLE } from '../constants/views';
import set from '../lib/util/set.util';
import type { ConfigAction } from '../actions/config';
import type { EntriesAction } from '../actions/entries';
import type { SearchAction } from '../actions/search';
import type { ViewStyle } from '../constants/views';
@ -124,9 +127,70 @@ export type EntriesState = {
function entries(
state: EntriesState = { entries: {}, pages: {}, sort: loadSort(), viewStyle: loadViewStyle() },
action: EntriesAction | SearchAction,
action: EntriesAction | SearchAction | ConfigAction,
): EntriesState {
switch (action.type) {
case CONFIG_SUCCESS: {
const config = action.payload.config;
const sort: EntriesState['sort'] = {};
const group: EntriesState['group'] = {};
const filter: EntriesState['filter'] = {};
for (const collection of config.collections) {
if (collection.sortable_fields && collection.sortable_fields.default) {
const key = collection.sortable_fields.default.field;
sort[collection.name] = {
[key]: {
key,
direction: collection.sortable_fields.default.direction ?? SORT_DIRECTION_ASCENDING,
},
} as SortMap;
}
if (collection.view_filters && collection.view_filters.default) {
const defaultViewFilterName = collection.view_filters.default;
const defaultViewFilter = collection.view_filters.filters.find(
f => f.name === defaultViewFilterName,
);
const collectionFilters: Record<string, FilterMap> = {};
if (defaultViewFilter) {
collectionFilters[defaultViewFilter.id] = {
...defaultViewFilter,
active: true,
};
}
filter[collection.name] = collectionFilters;
}
if (collection.view_groups && collection.view_groups.default) {
const defaultViewGroupName = collection.view_groups.default;
const defaultViewGroup = collection.view_groups.groups.find(
g => g.name === defaultViewGroupName,
);
const collectionGroups: Record<string, GroupMap> = {};
if (defaultViewGroup) {
collectionGroups[defaultViewGroup.id] = {
...defaultViewGroup,
active: true,
};
}
group[collection.name] = collectionGroups;
}
}
return {
...state,
sort,
group,
filter,
};
}
case ENTRY_REQUEST: {
const payload = action.payload;

View File

@ -4,7 +4,6 @@ import get from 'lodash/get';
import { SORT_DIRECTION_NONE } from '@staticcms/core/constants';
import { filterNullish } from '@staticcms/core/lib/util/null.util';
import type { ViewStyle } from '@staticcms/core/constants/views';
import type {
Entries,
Entry,
@ -14,6 +13,7 @@ import type {
SortMap,
SortObject,
} from '@staticcms/core';
import type { ViewStyle } from '@staticcms/core/constants/views';
import type { RootState } from '@staticcms/core/store';
export const selectEntriesFilters = (entries: RootState) => {

View File

@ -46,6 +46,24 @@ export const createMockFolderCollectionWithDefaults = <EF extends BaseField>(
): FolderCollectionWithDefaults<EF> => ({
...createMockFolderCollection(extra, ...fields),
i18n: extra.i18n,
view_filters: extra.view_filters
? {
...extra.view_filters,
filters: extra.view_filters.filters.map(f => ({
...f,
id: `${f.field}__${f.pattern}`,
})),
}
: undefined,
view_groups: extra.view_groups
? {
...extra.view_groups,
groups: extra.view_groups.groups.map(g => ({
...g,
id: `${g.field}__${g.pattern}`,
})),
}
: undefined,
});
export const createMockCollectionFile = <EF extends BaseField>(
@ -102,4 +120,22 @@ export const createMockFilesCollectionWithDefaults = <EF extends BaseField>(
...createMockFilesCollection(extra),
i18n: extra.i18n,
files: extra.files,
view_filters: extra.view_filters
? {
...extra.view_filters,
filters: extra.view_filters.filters.map(f => ({
...f,
id: `${f.field}__${f.pattern}`,
})),
}
: undefined,
view_groups: extra.view_groups
? {
...extra.view_groups,
groups: extra.view_groups.groups.map(g => ({
...g,
id: `${g.field}__${g.pattern}`,
})),
}
: undefined,
});