feat: singleton array list widget (#336)
This commit is contained in:
committed by
GitHub
parent
a60d53b4ec
commit
c5e94ed16d
@ -22,15 +22,13 @@ import {
|
||||
selectEntriesGroup,
|
||||
selectEntriesSort,
|
||||
selectViewStyle,
|
||||
} from '@staticcms/core/reducers/entries';
|
||||
} from '@staticcms/core/reducers/selectors/entries';
|
||||
import CollectionControls from './CollectionControls';
|
||||
import CollectionTop from './CollectionTop';
|
||||
import EntriesCollection from './Entries/EntriesCollection';
|
||||
import EntriesSearch from './Entries/EntriesSearch';
|
||||
import Sidebar from './Sidebar';
|
||||
|
||||
import type { ComponentType } from 'react';
|
||||
import type { ConnectedProps } from 'react-redux';
|
||||
import type {
|
||||
Collection,
|
||||
SortDirection,
|
||||
@ -39,6 +37,8 @@ import type {
|
||||
ViewGroup,
|
||||
} from '@staticcms/core/interface';
|
||||
import type { RootState } from '@staticcms/core/store';
|
||||
import type { ComponentType } from 'react';
|
||||
import type { ConnectedProps } from 'react-redux';
|
||||
|
||||
const CollectionMain = styled('main')`
|
||||
width: 100%;
|
||||
@ -271,13 +271,13 @@ function mapStateToProps(state: RootState, ownProps: TranslatedProps<CollectionV
|
||||
t,
|
||||
} = ownProps;
|
||||
const collection: Collection = name ? collections[name] : collections[0];
|
||||
const sort = selectEntriesSort(state.entries, collection.name);
|
||||
const sort = selectEntriesSort(state, collection.name);
|
||||
const sortableFields = selectSortableFields(collection, t);
|
||||
const viewFilters = selectViewFilters(collection);
|
||||
const viewGroups = selectViewGroups(collection);
|
||||
const filter = selectEntriesFilter(state.entries, collection.name);
|
||||
const group = selectEntriesGroup(state.entries, collection.name);
|
||||
const viewStyle = selectViewStyle(state.entries);
|
||||
const filter = selectEntriesFilter(state, collection.name);
|
||||
const group = selectEntriesGroup(state, collection.name);
|
||||
const viewStyle = selectViewStyle(state);
|
||||
|
||||
return {
|
||||
isSearchResults,
|
||||
|
@ -9,21 +9,21 @@ import {
|
||||
} from '@staticcms/core/actions/entries';
|
||||
import { colors } from '@staticcms/core/components/UI/styles';
|
||||
import { Cursor } from '@staticcms/core/lib/util';
|
||||
import { selectCollectionEntriesCursor } from '@staticcms/core/reducers/cursors';
|
||||
import { selectCollectionEntriesCursor } from '@staticcms/core/reducers/selectors/cursors';
|
||||
import {
|
||||
selectEntries,
|
||||
selectEntriesLoaded,
|
||||
selectGroups,
|
||||
selectIsFetching,
|
||||
} from '@staticcms/core/reducers/entries';
|
||||
} from '@staticcms/core/reducers/selectors/entries';
|
||||
import Entries from './Entries';
|
||||
|
||||
import type { ComponentType } from 'react';
|
||||
import type { t } from 'react-polyglot';
|
||||
import type { ConnectedProps } from 'react-redux';
|
||||
import type { CollectionViewStyle } from '@staticcms/core/constants/collectionViews';
|
||||
import type { Collection, Entry, GroupOfEntries, TranslatedProps } from '@staticcms/core/interface';
|
||||
import type { RootState } from '@staticcms/core/store';
|
||||
import type { ComponentType } from 'react';
|
||||
import type { t } from 'react-polyglot';
|
||||
import type { ConnectedProps } from 'react-redux';
|
||||
|
||||
const GroupHeading = styled('h2')`
|
||||
font-size: 23px;
|
||||
@ -163,18 +163,18 @@ function mapStateToProps(state: RootState, ownProps: EntriesCollectionOwnProps)
|
||||
const { collection, viewStyle, filterTerm } = ownProps;
|
||||
const page = state.entries.pages[collection.name]?.page;
|
||||
|
||||
let entries = selectEntries(state.entries, collection);
|
||||
const groups = selectGroups(state.entries, collection);
|
||||
let entries = selectEntries(state, collection);
|
||||
const groups = selectGroups(state, collection);
|
||||
|
||||
if ('nested' in collection) {
|
||||
const collectionFolder = collection.folder ?? '';
|
||||
entries = filterNestedEntries(filterTerm || '', collectionFolder, entries);
|
||||
}
|
||||
|
||||
const entriesLoaded = selectEntriesLoaded(state.entries, collection.name);
|
||||
const isFetching = selectIsFetching(state.entries, collection.name);
|
||||
const entriesLoaded = selectEntriesLoaded(state, collection.name);
|
||||
const isFetching = selectIsFetching(state, collection.name);
|
||||
|
||||
const rawCursor = selectCollectionEntriesCursor(state.cursors, collection.name);
|
||||
const rawCursor = selectCollectionEntriesCursor(state, collection.name);
|
||||
const cursor = Cursor.create(rawCursor).clearData();
|
||||
|
||||
return { ...ownProps, page, entries, groups, entriesLoaded, isFetching, viewStyle, cursor };
|
||||
|
@ -7,13 +7,13 @@ import {
|
||||
searchEntries as searchEntriesAction,
|
||||
} from '@staticcms/core/actions/search';
|
||||
import { Cursor } from '@staticcms/core/lib/util';
|
||||
import { selectSearchedEntries } from '@staticcms/core/reducers';
|
||||
import { selectSearchedEntries } from '@staticcms/core/reducers/selectors/entries';
|
||||
import Entries from './Entries';
|
||||
|
||||
import type { ConnectedProps } from 'react-redux';
|
||||
import type { CollectionViewStyle } from '@staticcms/core/constants/collectionViews';
|
||||
import type { Collections } from '@staticcms/core/interface';
|
||||
import type { RootState } from '@staticcms/core/store';
|
||||
import type { ConnectedProps } from 'react-redux';
|
||||
|
||||
const EntriesSearch = ({
|
||||
collections,
|
||||
|
@ -11,7 +11,7 @@ import { getAsset as getAssetAction } from '@staticcms/core/actions/media';
|
||||
import { VIEW_STYLE_GRID, VIEW_STYLE_LIST } from '@staticcms/core/constants/collectionViews';
|
||||
import useMediaAsset from '@staticcms/core/lib/hooks/useMediaAsset';
|
||||
import { selectEntryCollectionTitle } from '@staticcms/core/lib/util/collection.util';
|
||||
import { selectIsLoadingAsset } from '@staticcms/core/reducers/medias';
|
||||
import { selectIsLoadingAsset } from '@staticcms/core/reducers/selectors/medias';
|
||||
|
||||
import type { CollectionViewStyle } from '@staticcms/core/constants/collectionViews';
|
||||
import type { Collection, Entry, Field } from '@staticcms/core/interface';
|
||||
@ -78,7 +78,7 @@ function mapStateToProps(state: RootState, ownProps: EntryCardOwnProps) {
|
||||
image = encodeURI(image.trim());
|
||||
}
|
||||
|
||||
const isLoadingAsset = selectIsLoadingAsset(state.medias);
|
||||
const isLoadingAsset = selectIsLoadingAsset(state);
|
||||
|
||||
return {
|
||||
...ownProps,
|
||||
|
@ -10,7 +10,7 @@ import { colors, components } from '@staticcms/core/components/UI/styles';
|
||||
import { transientOptions } from '@staticcms/core/lib';
|
||||
import { selectEntryCollectionTitle } from '@staticcms/core/lib/util/collection.util';
|
||||
import { stringTemplate } from '@staticcms/core/lib/widgets';
|
||||
import { selectEntries } from '@staticcms/core/reducers/entries';
|
||||
import { selectEntries } from '@staticcms/core/reducers/selectors/entries';
|
||||
|
||||
import type { Collection, Entry } from '@staticcms/core/interface';
|
||||
import type { RootState } from '@staticcms/core/store';
|
||||
@ -344,7 +344,7 @@ interface NestedCollectionOwnProps {
|
||||
|
||||
function mapStateToProps(state: RootState, ownProps: NestedCollectionOwnProps) {
|
||||
const { collection } = ownProps;
|
||||
const entries = selectEntries(state.entries, collection) ?? [];
|
||||
const entries = selectEntries(state, collection) ?? [];
|
||||
return { ...ownProps, entries };
|
||||
}
|
||||
|
||||
|
@ -24,15 +24,12 @@ import {
|
||||
} from '@staticcms/core/actions/scroll';
|
||||
import { selectFields } from '@staticcms/core/lib/util/collection.util';
|
||||
import { useWindowEvent } from '@staticcms/core/lib/util/window.util';
|
||||
import { selectEntry } from '@staticcms/core/reducers';
|
||||
import { selectEntry } from '@staticcms/core/reducers/selectors/entries';
|
||||
import { history, navigateToCollection, navigateToNewEntry } from '@staticcms/core/routing/history';
|
||||
import confirm from '../UI/Confirm';
|
||||
import Loader from '../UI/Loader';
|
||||
import EditorInterface from './EditorInterface';
|
||||
|
||||
import type { Blocker } from 'history';
|
||||
import type { ComponentType } from 'react';
|
||||
import type { ConnectedProps } from 'react-redux';
|
||||
import type {
|
||||
Collection,
|
||||
EditorPersistOptions,
|
||||
@ -40,6 +37,9 @@ import type {
|
||||
TranslatedProps,
|
||||
} from '@staticcms/core/interface';
|
||||
import type { RootState } from '@staticcms/core/store';
|
||||
import type { Blocker } from 'history';
|
||||
import type { ComponentType } from 'react';
|
||||
import type { ConnectedProps } from 'react-redux';
|
||||
|
||||
const Editor = ({
|
||||
entry,
|
||||
|
@ -25,8 +25,8 @@ import { resolveWidget } from '@staticcms/core/lib/registry';
|
||||
import { getFieldLabel } from '@staticcms/core/lib/util/field.util';
|
||||
import { isNotNullish } from '@staticcms/core/lib/util/null.util';
|
||||
import { validate } from '@staticcms/core/lib/util/validation.util';
|
||||
import { selectFieldErrors } from '@staticcms/core/reducers/entryDraft';
|
||||
import { selectIsLoadingAsset } from '@staticcms/core/reducers/medias';
|
||||
import { selectFieldErrors } from '@staticcms/core/reducers/selectors/entryDraft';
|
||||
import { selectIsLoadingAsset } from '@staticcms/core/reducers/selectors/medias';
|
||||
import { useAppDispatch, useAppSelector } from '@staticcms/core/store/hooks';
|
||||
|
||||
import type {
|
||||
@ -160,6 +160,7 @@ const EditorControl = ({
|
||||
forList = false,
|
||||
changeDraftField,
|
||||
i18n,
|
||||
fieldName,
|
||||
}: TranslatedProps<EditorControlProps>) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@ -170,8 +171,9 @@ const EditorControl = ({
|
||||
const fieldHint = field.hint;
|
||||
|
||||
const path = useMemo(
|
||||
() => (parentPath.length > 0 ? `${parentPath}.${field.name}` : field.name),
|
||||
[field.name, parentPath],
|
||||
() =>
|
||||
parentPath.length > 0 ? `${parentPath}.${fieldName ?? field.name}` : fieldName ?? field.name,
|
||||
[field.name, fieldName, parentPath],
|
||||
);
|
||||
|
||||
const [dirty, setDirty] = useState(!isEmpty(value));
|
||||
@ -336,13 +338,14 @@ interface EditorControlOwnProps {
|
||||
value: ValueOrNestedValue;
|
||||
forList?: boolean;
|
||||
i18n: I18nSettings | undefined;
|
||||
fieldName?: string;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: RootState, ownProps: EditorControlOwnProps) {
|
||||
const { collections, entryDraft } = state;
|
||||
const entry = entryDraft.entry;
|
||||
const collection = entryDraft.entry ? collections[entryDraft.entry.collection] : null;
|
||||
const isLoadingAsset = selectIsLoadingAsset(state.medias);
|
||||
const isLoadingAsset = selectIsLoadingAsset(state);
|
||||
|
||||
return {
|
||||
...ownProps,
|
||||
|
@ -12,7 +12,7 @@ import { lengths } from '@staticcms/core/components/UI/styles';
|
||||
import { getPreviewStyles, getPreviewTemplate, resolveWidget } from '@staticcms/core/lib/registry';
|
||||
import { selectTemplateName, useInferedFields } from '@staticcms/core/lib/util/collection.util';
|
||||
import { selectField } from '@staticcms/core/lib/util/field.util';
|
||||
import { selectIsLoadingAsset } from '@staticcms/core/reducers/medias';
|
||||
import { selectIsLoadingAsset } from '@staticcms/core/reducers/selectors/medias';
|
||||
import { getTypedFieldForValue } from '@staticcms/list/typedListHelpers';
|
||||
import EditorPreview from './EditorPreview';
|
||||
import EditorPreviewContent from './EditorPreviewContent';
|
||||
@ -106,17 +106,12 @@ function getWidgetFor(
|
||||
}
|
||||
|
||||
const value = values?.[field.name];
|
||||
let fieldWithWidgets = Object.entries(field).reduce((acc, [key, fieldValue]) => {
|
||||
if (!['fields', 'fields'].includes(key)) {
|
||||
acc[key] = fieldValue;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, unknown>) as RenderedField;
|
||||
let fieldWithWidgets = field as RenderedField;
|
||||
|
||||
if ('fields' in field && field.fields) {
|
||||
fieldWithWidgets = {
|
||||
...fieldWithWidgets,
|
||||
fields: getNestedWidgets(
|
||||
renderedFields: getNestedWidgets(
|
||||
config,
|
||||
collection,
|
||||
fields,
|
||||
@ -130,7 +125,7 @@ function getWidgetFor(
|
||||
} else if ('types' in field && field.types) {
|
||||
fieldWithWidgets = {
|
||||
...fieldWithWidgets,
|
||||
fields: getTypedNestedWidgets(
|
||||
renderedFields: getTypedNestedWidgets(
|
||||
config,
|
||||
collection,
|
||||
field,
|
||||
@ -594,7 +589,7 @@ export interface EditorPreviewPaneOwnProps {
|
||||
}
|
||||
|
||||
function mapStateToProps(state: RootState, ownProps: EditorPreviewPaneOwnProps) {
|
||||
const isLoadingAsset = selectIsLoadingAsset(state.medias);
|
||||
const isLoadingAsset = selectIsLoadingAsset(state);
|
||||
return { ...ownProps, isLoadingAsset, config: state.config };
|
||||
}
|
||||
|
||||
|
@ -12,15 +12,15 @@ import {
|
||||
persistMedia as persistMediaAction,
|
||||
} from '@staticcms/core/actions/mediaLibrary';
|
||||
import { fileExtension } from '@staticcms/core/lib/util';
|
||||
import { selectMediaFiles } from '@staticcms/core/reducers/mediaLibrary';
|
||||
import { selectMediaFiles } from '@staticcms/core/reducers/selectors/mediaLibrary';
|
||||
import alert from '../UI/Alert';
|
||||
import confirm from '../UI/Confirm';
|
||||
import MediaLibraryModal from './MediaLibraryModal';
|
||||
|
||||
import type { ChangeEvent, KeyboardEvent } from 'react';
|
||||
import type { ConnectedProps } from 'react-redux';
|
||||
import type { MediaFile, TranslatedProps } from '@staticcms/core/interface';
|
||||
import type { RootState } from '@staticcms/core/store';
|
||||
import type { ChangeEvent, KeyboardEvent } from 'react';
|
||||
import type { ConnectedProps } from 'react-redux';
|
||||
|
||||
/**
|
||||
* Extensions used to determine which files to show when the media library is
|
||||
|
@ -117,12 +117,12 @@ const ListItemTopBar = ({
|
||||
/>
|
||||
</IconButton>
|
||||
) : null}
|
||||
<StyledTitle key="title" onClick={onCollapseToggle}>
|
||||
<StyledTitle key="title" onClick={onCollapseToggle} data-testid="list-item-title">
|
||||
{title}
|
||||
</StyledTitle>
|
||||
{listeners ? <DragHandle listeners={listeners} /> : null}
|
||||
{onRemove ? (
|
||||
<TopBarButton onClick={onRemove}>
|
||||
<TopBarButton data-testid="remove-button" onClick={onRemove}>
|
||||
<CloseIcon />
|
||||
</TopBarButton>
|
||||
) : null}
|
||||
|
@ -53,6 +53,7 @@ export interface ObjectWidgetTopBarProps {
|
||||
heading: ReactNode;
|
||||
label?: string;
|
||||
hasError?: boolean;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
const ObjectWidgetTopBar = ({
|
||||
@ -66,6 +67,7 @@ const ObjectWidgetTopBar = ({
|
||||
label,
|
||||
hasError = false,
|
||||
t,
|
||||
testId,
|
||||
}: TranslatedProps<ObjectWidgetTopBarProps>) => {
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
@ -129,6 +131,7 @@ const ObjectWidgetTopBar = ({
|
||||
endIcon={<AddIcon fontSize="small" />}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
data-testid="add-button"
|
||||
>
|
||||
{t('editor.editorWidgets.list.add', { item: label })}
|
||||
</Button>
|
||||
@ -147,7 +150,7 @@ const ObjectWidgetTopBar = ({
|
||||
}, [allowAdd, types, renderTypesDropdown, renderAddButton]);
|
||||
|
||||
return (
|
||||
<TopBarContainer>
|
||||
<TopBarContainer data-testid={testId}>
|
||||
<ExpandButtonContainer $hasError={hasError}>
|
||||
<IconButton onClick={onCollapseToggle} data-testid="expand-button">
|
||||
<ExpandMoreIcon
|
||||
|
Reference in New Issue
Block a user