fix: folder collection path (#549)

This commit is contained in:
Daniel Lautzenheiser
2023-02-16 13:34:35 -05:00
committed by GitHub
parent 93915dac35
commit 8f7237ab7c
53 changed files with 742 additions and 513 deletions

View File

@ -14,8 +14,8 @@ import { currentBackend } from '@staticcms/core/backend';
import { colors, GlobalStyles } from '@staticcms/core/components/UI/styles';
import { history } from '@staticcms/core/routing/history';
import { getDefaultPath } from '../../lib/util/collection.util';
import CollectionRoute from '../Collection/CollectionRoute';
import EditorRoute from '../Editor/EditorRoute';
import CollectionRoute from '../collection/CollectionRoute';
import EditorRoute from '../editor/EditorRoute';
import MediaLibrary from '../MediaLibrary/MediaLibrary';
import Page from '../page/Page';
import Snackbars from '../snackbar/Snackbars';
@ -183,7 +183,7 @@ const App = ({
element={<EditorRoute collections={collections} newRecord />}
/>
<Route
path="/collections/:name/entries/:slug"
path="/collections/:name/entries/*"
element={<EditorRoute collections={collections} />}
/>
<Route

View File

@ -63,6 +63,7 @@ const Entries = ({
<>
{'collection' in otherProps ? (
<EntryListing
key="collection-listing"
collection={otherProps.collection}
entries={entries}
viewStyle={viewStyle}
@ -72,6 +73,7 @@ const Entries = ({
/>
) : (
<EntryListing
key="search-listing"
collections={otherProps.collections}
entries={entries}
viewStyle={viewStyle}

View File

@ -1,22 +1,17 @@
import { styled } from '@mui/material/styles';
import React, { useCallback, useEffect, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { translate } from 'react-polyglot';
import { connect } from 'react-redux';
import {
loadEntries as loadEntriesAction,
traverseCollectionCursor as traverseCollectionCursorAction,
} from '@staticcms/core/actions/entries';
import { loadEntries, traverseCollectionCursor } from '@staticcms/core/actions/entries';
import { colors } from '@staticcms/core/components/UI/styles';
import useEntries from '@staticcms/core/lib/hooks/useEntries';
import useGroups from '@staticcms/core/lib/hooks/useGroups';
import { Cursor } from '@staticcms/core/lib/util';
import { selectCollectionEntriesCursor } from '@staticcms/core/reducers/selectors/cursors';
import {
selectEntries,
selectEntriesLoaded,
selectGroups,
selectIsFetching,
} from '@staticcms/core/reducers/selectors/entries';
import { selectEntriesLoaded, selectIsFetching } from '@staticcms/core/reducers/selectors/entries';
import Entries from './Entries';
import { useAppDispatch } from '@staticcms/core/store/hooks';
import type { CollectionViewStyle } from '@staticcms/core/constants/collectionViews';
import type { Collection, Entry, GroupOfEntries, TranslatedProps } from '@staticcms/core/interface';
@ -48,90 +43,6 @@ function getGroupTitle(group: GroupOfEntries, t: t) {
return `${label} ${value}`.trim();
}
function withGroups(
groups: GroupOfEntries[],
entries: Entry[],
EntriesToRender: ComponentType<EntriesToRenderProps>,
t: t,
) {
return groups.map(group => {
const title = getGroupTitle(group, t);
return (
<GroupContainer key={group.id} id={group.id}>
<GroupHeading>{title}</GroupHeading>
<EntriesToRender entries={getGroupEntries(entries, group.paths)} />
</GroupContainer>
);
});
}
interface EntriesToRenderProps {
entries: Entry[];
}
const EntriesCollection = ({
collection,
entries,
groups,
isFetching,
viewStyle,
cursor,
page,
traverseCollectionCursor,
t,
entriesLoaded,
readyToLoad,
loadEntries,
}: TranslatedProps<EntriesCollectionProps>) => {
const [prevReadyToLoad, setPrevReadyToLoad] = useState(false);
const [prevCollection, setPrevCollection] = useState(collection);
useEffect(() => {
if (
collection &&
!entriesLoaded &&
readyToLoad &&
(!prevReadyToLoad || prevCollection !== collection)
) {
loadEntries(collection);
}
setPrevReadyToLoad(readyToLoad);
setPrevCollection(collection);
}, [collection, entriesLoaded, loadEntries, prevCollection, prevReadyToLoad, readyToLoad]);
const handleCursorActions = useCallback(
(action: string) => {
traverseCollectionCursor(collection, action);
},
[collection, traverseCollectionCursor],
);
const EntriesToRender = useCallback(
({ entries }: EntriesToRenderProps) => {
return (
<Entries
collection={collection}
entries={entries}
isFetching={isFetching}
collectionName={collection.label}
viewStyle={viewStyle}
cursor={cursor}
handleCursorActions={handleCursorActions}
page={page}
/>
);
},
[collection, cursor, handleCursorActions, isFetching, page, viewStyle],
);
if (groups && groups.length > 0) {
return <>{withGroups(groups, entries, EntriesToRender, t)}</>;
}
return <EntriesToRender entries={entries} />;
};
export function filterNestedEntries(path: string, collectionFolder: string, entries: Entry[]) {
const filtered = entries.filter(e => {
const entryPath = e.path.slice(collectionFolder.length + 1);
@ -152,6 +63,94 @@ export function filterNestedEntries(path: string, collectionFolder: string, entr
return filtered;
}
const EntriesCollection = ({
collection,
filterTerm,
isFetching,
viewStyle,
cursor,
page,
t,
entriesLoaded,
readyToLoad,
}: TranslatedProps<EntriesCollectionProps>) => {
const dispatch = useAppDispatch();
const [prevReadyToLoad, setPrevReadyToLoad] = useState(false);
const [prevCollection, setPrevCollection] = useState(collection);
const groups = useGroups(collection.name);
const entries = useEntries(collection);
const filteredEntries = useMemo(() => {
if ('nested' in collection) {
const collectionFolder = collection.folder ?? '';
return filterNestedEntries(filterTerm || '', collectionFolder, entries);
}
return entries;
}, [collection, entries, filterTerm]);
useEffect(() => {
if (
collection &&
!entriesLoaded &&
readyToLoad &&
(!prevReadyToLoad || prevCollection !== collection)
) {
dispatch(loadEntries(collection));
}
setPrevReadyToLoad(readyToLoad);
setPrevCollection(collection);
}, [collection, dispatch, entriesLoaded, prevCollection, prevReadyToLoad, readyToLoad]);
const handleCursorActions = useCallback(
(action: string) => {
dispatch(traverseCollectionCursor(collection, action));
},
[collection, dispatch],
);
if (groups && groups.length > 0) {
<>
{groups.map(group => {
const title = getGroupTitle(group, t);
return (
<GroupContainer key={group.id} id={group.id}>
<GroupHeading>{title}</GroupHeading>
<Entries
collection={collection}
entries={getGroupEntries(filteredEntries, group.paths)}
isFetching={isFetching}
collectionName={collection.label}
viewStyle={viewStyle}
cursor={cursor}
handleCursorActions={handleCursorActions}
page={page}
/>
</GroupContainer>
);
})}
</>;
}
return (
<Entries
key="entries-without-group"
collection={collection}
entries={filteredEntries}
isFetching={isFetching}
collectionName={collection.label}
viewStyle={viewStyle}
cursor={cursor}
handleCursorActions={handleCursorActions}
page={page}
/>
);
};
interface EntriesCollectionOwnProps {
collection: Collection;
viewStyle: CollectionViewStyle;
@ -163,27 +162,16 @@ function mapStateToProps(state: RootState, ownProps: EntriesCollectionOwnProps)
const { collection, viewStyle, filterTerm } = ownProps;
const page = state.entries.pages[collection.name]?.page;
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, collection.name);
const isFetching = selectIsFetching(state, collection.name);
const rawCursor = selectCollectionEntriesCursor(state, collection.name);
const cursor = Cursor.create(rawCursor).clearData();
return { ...ownProps, page, entries, groups, entriesLoaded, isFetching, viewStyle, cursor };
return { ...ownProps, page, filterTerm, entriesLoaded, isFetching, viewStyle, cursor };
}
const mapDispatchToProps = {
loadEntries: loadEntriesAction,
traverseCollectionCursor: traverseCollectionCursorAction,
};
const mapDispatchToProps = {};
const connector = connect(mapStateToProps, mapDispatchToProps);
export type EntriesCollectionProps = ConnectedProps<typeof connector>;

View File

@ -4,10 +4,8 @@ import CardContent from '@mui/material/CardContent';
import CardMedia from '@mui/material/CardMedia';
import Typography from '@mui/material/Typography';
import React, { useMemo } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
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 { getPreviewCard } from '@staticcms/core/lib/registry';
@ -17,28 +15,58 @@ import {
selectTemplateName,
} from '@staticcms/core/lib/util/collection.util';
import { selectConfig } from '@staticcms/core/reducers/selectors/config';
import { selectIsLoadingAsset } from '@staticcms/core/reducers/selectors/medias';
import { useAppSelector } from '@staticcms/core/store/hooks';
import useWidgetsFor from '../../common/widget/useWidgetsFor';
import type { CollectionViewStyle } from '@staticcms/core/constants/collectionViews';
import type { Collection, Entry, Field } from '@staticcms/core/interface';
import type { RootState } from '@staticcms/core/store';
import type { ConnectedProps } from 'react-redux';
import type { Collection, Entry, FileOrImageField, MediaField } from '@staticcms/core/interface';
export interface EntryCardProps {
entry: Entry;
imageFieldName?: string | null | undefined;
collection: Collection;
collectionLabel?: string;
viewStyle?: CollectionViewStyle;
}
const EntryCard = ({
collection,
entry,
path,
image,
imageField,
collectionLabel,
viewStyle = VIEW_STYLE_LIST,
}: NestedCollectionProps) => {
imageFieldName,
}: EntryCardProps) => {
const entryData = entry.data;
const path = useMemo(
() => `/collections/${collection.name}/entries/${entry.slug}`,
[collection.name, entry.slug],
);
const imageField = useMemo(
() =>
'fields' in collection
? (collection.fields?.find(
f => f.name === imageFieldName && f.widget === 'image',
) as FileOrImageField)
: undefined,
[collection, imageFieldName],
);
const image = useMemo(() => {
let i = imageFieldName ? (entryData?.[imageFieldName] as string | undefined) : undefined;
if (i) {
i = encodeURI(i.trim());
}
return i;
}, [entryData, imageFieldName]);
const summary = useMemo(() => selectEntryCollectionTitle(collection, entry), [collection, entry]);
const fields = selectFields(collection, entry.slug);
const imageUrl = useMediaAsset(image, collection, imageField, entry);
const imageUrl = useMediaAsset(image, collection as Collection<MediaField>, imageField, entry);
const config = useAppSelector(selectConfig);
@ -97,51 +125,4 @@ const EntryCard = ({
);
};
interface EntryCardOwnProps {
entry: Entry;
inferredFields: {
titleField?: string | null | undefined;
descriptionField?: string | null | undefined;
imageField?: string | null | undefined;
remainingFields?: Field[] | undefined;
};
collection: Collection;
imageField?: Field;
collectionLabel?: string;
viewStyle?: CollectionViewStyle;
}
function mapStateToProps(state: RootState, ownProps: EntryCardOwnProps) {
const { entry, inferredFields, collection } = ownProps;
const entryData = entry.data;
let image = inferredFields.imageField
? (entryData?.[inferredFields.imageField] as string | undefined)
: undefined;
if (image) {
image = encodeURI(image.trim());
}
const isLoadingAsset = selectIsLoadingAsset(state);
return {
...ownProps,
path: `/collections/${collection.name}/entries/${entry.slug}`,
image,
imageField:
'fields' in collection
? collection.fields?.find(f => f.name === inferredFields.imageField && f.widget === 'image')
: undefined,
isLoadingAsset,
};
}
const mapDispatchToProps = {
getAsset: getAssetAction,
};
const connector = connect(mapStateToProps, mapDispatchToProps);
export type NestedCollectionProps = ConnectedProps<typeof connector>;
export default connector(EntryCard);
export default EntryCard;

View File

@ -8,7 +8,7 @@ import { selectFields, selectInferredField } from '@staticcms/core/lib/util/coll
import EntryCard from './EntryCard';
import type { CollectionViewStyle } from '@staticcms/core/constants/collectionViews';
import type { Field, Collection, Collections, Entry } from '@staticcms/core/interface';
import type { Collection, Collections, Entry, Field } from '@staticcms/core/interface';
import type Cursor from '@staticcms/core/lib/util/Cursor';
interface CardsGridProps {
@ -100,19 +100,19 @@ const EntryListing = ({
const renderedCards = useMemo(() => {
if ('collection' in otherProps) {
const inferredFields = inferFields(otherProps.collection);
return entries.map((entry, idx) => (
return entries.map(entry => (
<EntryCard
collection={otherProps.collection}
inferredFields={inferredFields}
imageFieldName={inferredFields.imageField}
viewStyle={viewStyle}
entry={entry}
key={idx}
key={entry.slug}
/>
));
}
const isSingleCollectionInList = Object.keys(otherProps.collections).length === 1;
return entries.map((entry, idx) => {
return entries.map(entry => {
const collectionName = entry.collection;
const collection = Object.values(otherProps.collections).find(
coll => coll.name === collectionName,
@ -123,9 +123,9 @@ const EntryListing = ({
<EntryCard
collection={collection}
entry={entry}
inferredFields={inferredFields}
imageFieldName={inferredFields.imageField}
collectionLabel={collectionLabel}
key={idx}
key={entry.slug}
/>
) : null;
});

View File

@ -3,18 +3,15 @@ import { styled } from '@mui/material/styles';
import sortBy from 'lodash/sortBy';
import { dirname, sep } from 'path';
import React, { Fragment, useCallback, useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { NavLink } from 'react-router-dom';
import { colors, components } from '@staticcms/core/components/UI/styles';
import { transientOptions } from '@staticcms/core/lib';
import useEntries from '@staticcms/core/lib/hooks/useEntries';
import { selectEntryCollectionTitle } from '@staticcms/core/lib/util/collection.util';
import { stringTemplate } from '@staticcms/core/lib/widgets';
import { selectEntries } from '@staticcms/core/reducers/selectors/entries';
import type { Collection, Entry } from '@staticcms/core/interface';
import type { RootState } from '@staticcms/core/store';
import type { ConnectedProps } from 'react-redux';
const { addFileTemplateFields } = stringTemplate;
@ -271,7 +268,14 @@ export function updateNode(
return updater([...treeData]);
}
const NestedCollection = ({ collection, entries, filterTerm }: NestedCollectionProps) => {
export interface NestedCollectionProps {
collection: Collection;
filterTerm: string;
}
const NestedCollection = ({ collection, filterTerm }: NestedCollectionProps) => {
const entries = useEntries(collection);
const [treeData, setTreeData] = useState<TreeNodeData[]>(getTreeData(collection, entries));
const [selected, setSelected] = useState<TreeNodeData | null>(null);
const [useFilter, setUseFilter] = useState(true);
@ -337,18 +341,4 @@ const NestedCollection = ({ collection, entries, filterTerm }: NestedCollectionP
return <TreeNode collection={collection} treeData={treeData} onToggle={onToggle} />;
};
interface NestedCollectionOwnProps {
collection: Collection;
filterTerm: string;
}
function mapStateToProps(state: RootState, ownProps: NestedCollectionOwnProps) {
const { collection } = ownProps;
const entries = selectEntries(state, collection) ?? [];
return { ...ownProps, entries };
}
const connector = connect(mapStateToProps, {});
export type NestedCollectionProps = ConnectedProps<typeof connector>;
export default connector(NestedCollection);
export default NestedCollection;

View File

@ -12,7 +12,9 @@ interface EditorRouteProps {
}
const EditorRoute = ({ newRecord = false, collections }: EditorRouteProps) => {
const { name, slug } = useParams();
const { name, ...params } = useParams();
const slug = params['*'];
const shouldRedirect = useMemo(() => {
if (!name) {
return false;

View File

@ -1,6 +1,5 @@
import fuzzy from 'fuzzy';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { translate } from 'react-polyglot';
import { connect } from 'react-redux';
import {
@ -12,12 +11,13 @@ import {
persistMedia as persistMediaAction,
} from '@staticcms/core/actions/mediaLibrary';
import { fileExtension } from '@staticcms/core/lib/util';
import MediaLibraryCloseEvent from '@staticcms/core/lib/util/events/MediaLibraryCloseEvent';
import { selectMediaFiles } from '@staticcms/core/reducers/selectors/mediaLibrary';
import alert from '../UI/Alert';
import confirm from '../UI/Confirm';
import MediaLibraryModal from './MediaLibraryModal';
import type { MediaFile, TranslatedProps } from '@staticcms/core/interface';
import type { MediaFile } from '@staticcms/core/interface';
import type { RootState } from '@staticcms/core/store';
import type { ChangeEvent, KeyboardEvent } from 'react';
import type { ConnectedProps } from 'react-redux';
@ -53,7 +53,7 @@ const MediaLibrary = ({
isDeleting,
hasNextPage,
isPaginating,
config,
config: mediaConfig,
loadMedia,
dynamicSearchQuery,
page,
@ -61,31 +61,28 @@ const MediaLibrary = ({
deleteMedia,
insertMedia,
closeMediaLibrary,
collection,
field,
t,
}: TranslatedProps<MediaLibraryProps>) => {
}: MediaLibraryProps) => {
const [selectedFile, setSelectedFile] = useState<MediaFile | null>(null);
const [query, setQuery] = useState<string | undefined>(undefined);
const [prevIsVisible, setPrevIsVisible] = useState(false);
useEffect(() => {
loadMedia();
loadMedia({});
}, [loadMedia]);
useEffect(() => {
if (!prevIsVisible && isVisible) {
setSelectedFile(null);
setQuery('');
loadMedia();
} else if (prevIsVisible && !isVisible) {
window.dispatchEvent(new MediaLibraryCloseEvent());
}
setPrevIsVisible(isVisible);
}, [isVisible, prevIsVisible]);
useEffect(() => {
if (!prevIsVisible && isVisible) {
loadMedia();
}
}, [isVisible, loadMedia, prevIsVisible]);
const loadDisplayURL = useCallback(
@ -155,7 +152,7 @@ const MediaLibrary = ({
[selectedFile?.key],
);
const scrollContainerRef = useRef<HTMLDivElement>();
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
const scrollToTop = () => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollTop = 0;
@ -189,7 +186,8 @@ const MediaLibrary = ({
event.preventDefault();
const files = [...Array.from(fileList)];
const file = files[0];
const maxFileSize = typeof config.max_file_size === 'number' ? config.max_file_size : 512000;
const maxFileSize =
typeof mediaConfig.max_file_size === 'number' ? mediaConfig.max_file_size : 512000;
if (maxFileSize && file.size > maxFileSize) {
alert({
@ -213,7 +211,7 @@ const MediaLibrary = ({
event.target.value = '';
}
},
[config.max_file_size, field, persistMedia],
[mediaConfig.max_file_size, field, persistMedia],
);
/**
@ -310,7 +308,7 @@ const MediaLibrary = ({
/**
* Filters files that do not match the query. Not used for dynamic search.
*/
const queryFilter = useCallback((query: string, files: { name: string }[]) => {
const queryFilter = useCallback((query: string, files: MediaFile[]): MediaFile[] => {
/**
* Because file names don't have spaces, typing a space eliminates all
* potential matches, so we strip them all out internally before running the
@ -318,11 +316,10 @@ const MediaLibrary = ({
*/
const strippedQuery = query.replace(/ /g, '');
const matches = fuzzy.filter(strippedQuery, files, { extract: file => file.name });
const matchFiles = matches.map((match, queryIndex) => {
return matches.map((match, queryIndex) => {
const file = files[match.index];
return { ...file, queryIndex };
});
return matchFiles;
}) as MediaFile[];
}, []);
return (
@ -339,7 +336,7 @@ const MediaLibrary = ({
hasNextPage={hasNextPage}
isPaginating={isPaginating}
query={query}
selectedFile={selectedFile}
selectedFile={selectedFile ?? undefined}
handleFilter={filterImages}
handleQuery={queryFilter}
toTableData={toTableData}
@ -350,12 +347,13 @@ const MediaLibrary = ({
handleDelete={handleDelete}
handleInsert={handleInsert}
handleDownload={handleDownload}
setScrollContainerRef={scrollContainerRef}
scrollContainerRef={scrollContainerRef}
handleAssetClick={handleAssetClick}
handleLoadMore={handleLoadMore}
displayURLs={displayURLs}
loadDisplayURL={loadDisplayURL}
t={t}
collection={collection}
field={field}
/>
);
};
@ -379,6 +377,7 @@ function mapStateToProps(state: RootState) {
page: mediaLibrary.page,
hasNextPage: mediaLibrary.hasNextPage,
isPaginating: mediaLibrary.isPaginating,
collection: mediaLibrary.collection,
field,
};
return { ...mediaLibraryProps };
@ -396,4 +395,4 @@ const mapDispatchToProps = {
const connector = connect(mapStateToProps, mapDispatchToProps);
export type MediaLibraryProps = ConnectedProps<typeof connector>;
export default connector(translate()(MediaLibrary));
export default connector(MediaLibrary);

View File

@ -4,8 +4,11 @@ import React, { useEffect } from 'react';
import { borders, colors, effects, lengths, shadows } from '@staticcms/core/components/UI/styles';
import useMediaAsset from '@staticcms/core/lib/hooks/useMediaAsset';
import transientOptions from '@staticcms/core/lib/util/transientOptions';
import { selectEditingDraft } from '@staticcms/core/reducers/selectors/entryDraft';
import { useAppSelector } from '@staticcms/core/store/hooks';
import type { MediaLibraryDisplayURL } from '@staticcms/core/reducers/mediaLibrary';
import type { Field, Collection } from '@staticcms/core/interface';
const IMAGE_HEIGHT = 160;
@ -89,6 +92,8 @@ interface MediaLibraryCardProps {
isViewableImage: boolean;
loadDisplayURL: () => void;
isDraft?: boolean;
collection?: Collection;
field?: Field;
}
const MediaLibraryCard = ({
@ -103,9 +108,12 @@ const MediaLibraryCard = ({
type,
isViewableImage,
isDraft,
collection,
field,
loadDisplayURL,
}: MediaLibraryCardProps) => {
const url = useMediaAsset(displayURL.url);
const entry = useAppSelector(selectEditingDraft);
const url = useMediaAsset(displayURL.url, collection, field, entry);
useEffect(() => {
if (!displayURL.url) {

View File

@ -6,12 +6,12 @@ import { FixedSizeGrid as Grid } from 'react-window';
import MediaLibraryCard from './MediaLibraryCard';
import type { GridChildComponentProps } from 'react-window';
import type { MediaFile } from '@staticcms/core/interface';
import type { Collection, Field, MediaFile } from '@staticcms/core/interface';
import type {
MediaLibraryDisplayURL,
MediaLibraryState,
} from '@staticcms/core/reducers/mediaLibrary';
import type { GridChildComponentProps } from 'react-window';
export interface MediaLibraryCardItem {
displayURL?: MediaLibraryDisplayURL;
@ -25,7 +25,7 @@ export interface MediaLibraryCardItem {
}
export interface MediaLibraryCardGridProps {
setScrollContainerRef: () => void;
scrollContainerRef: React.MutableRefObject<HTMLDivElement | null>;
mediaItems: MediaFile[];
isSelectedFile: (file: MediaFile) => boolean;
onAssetClick: (asset: MediaFile) => void;
@ -39,6 +39,8 @@ export interface MediaLibraryCardGridProps {
cardMargin: string;
loadDisplayURL: (asset: MediaFile) => void;
displayURLs: MediaLibraryState['displayURLs'];
collection?: Collection;
field?: Field;
}
export type CardGridItemData = MediaLibraryCardGridProps & {
@ -61,6 +63,8 @@ const CardWrapper = ({
loadDisplayURL,
columnCount,
gutter,
collection,
field,
},
}: GridChildComponentProps<CardGridItemData>) => {
const index = rowIndex * columnCount + columnIndex;
@ -93,6 +97,8 @@ const CardWrapper = ({
loadDisplayURL={() => loadDisplayURL(file)}
type={file.type}
isViewableImage={file.isViewableImage ?? false}
collection={collection}
field={field}
/>
</div>
);
@ -126,7 +132,7 @@ const VirtualizedGrid = (props: MediaLibraryCardGridProps) => {
cardHeight: inputCardHeight,
cardMargin,
mediaItems,
setScrollContainerRef,
scrollContainerRef,
} = props;
return (
@ -141,7 +147,7 @@ const VirtualizedGrid = (props: MediaLibraryCardGridProps) => {
const rowCount = Math.ceil(mediaItems.length / columnCount);
return (
<StyledCardGridContainer $width={width} $height={height} ref={setScrollContainerRef}>
<StyledCardGridContainer $width={width} $height={height} ref={scrollContainerRef}>
<Grid
columnCount={columnCount}
columnWidth={columnWidth}
@ -168,7 +174,7 @@ const VirtualizedGrid = (props: MediaLibraryCardGridProps) => {
};
const PaginatedGrid = ({
setScrollContainerRef,
scrollContainerRef,
mediaItems,
isSelectedFile,
onAssetClick,
@ -182,9 +188,11 @@ const PaginatedGrid = ({
onLoadMore,
isPaginating,
paginatingMessage,
collection,
field,
}: MediaLibraryCardGridProps) => {
return (
<StyledCardGridContainer ref={setScrollContainerRef}>
<StyledCardGridContainer ref={scrollContainerRef}>
<CardGrid>
{mediaItems.map(file => (
<MediaLibraryCard
@ -201,6 +209,8 @@ const PaginatedGrid = ({
loadDisplayURL={() => loadDisplayURL(file)}
type={file.type}
isViewableImage={file.isViewableImage ?? false}
collection={collection}
field={field}
/>
))}
{!canLoadMore ? null : <Waypoint onEnter={onLoadMore} />}

View File

@ -11,9 +11,9 @@ import EmptyMessage from './EmptyMessage';
import MediaLibraryCardGrid from './MediaLibraryCardGrid';
import MediaLibraryTop from './MediaLibraryTop';
import type { ChangeEvent, ChangeEventHandler, KeyboardEventHandler } from 'react';
import type { MediaFile, TranslatedProps } from '@staticcms/core/interface';
import type { Collection, Field, MediaFile, TranslatedProps } from '@staticcms/core/interface';
import type { MediaLibraryState } from '@staticcms/core/reducers/mediaLibrary';
import type { ChangeEvent, ChangeEventHandler, FC, KeyboardEventHandler } from 'react';
const StyledFab = styled(Fab)`
position: absolute;
@ -95,11 +95,13 @@ interface MediaLibraryModalProps {
handleDelete: () => void;
handleInsert: () => void;
handleDownload: () => void;
setScrollContainerRef: () => void;
scrollContainerRef: React.MutableRefObject<HTMLDivElement | null>;
handleAssetClick: (asset: MediaFile) => void;
handleLoadMore: () => void;
loadDisplayURL: (file: MediaFile) => void;
displayURLs: MediaLibraryState['displayURLs'];
collection?: Collection;
field?: Field;
}
const MediaLibraryModal = ({
@ -126,11 +128,13 @@ const MediaLibraryModal = ({
handleDelete,
handleInsert,
handleDownload,
setScrollContainerRef,
scrollContainerRef,
handleAssetClick,
handleLoadMore,
loadDisplayURL,
displayURLs,
collection,
field,
t,
}: TranslatedProps<MediaLibraryModalProps>) => {
const filteredFiles = forImage ? handleFilter(files) : files;
@ -177,7 +181,7 @@ const MediaLibraryModal = ({
<DialogContent>
{!shouldShowEmptyMessage ? null : <EmptyMessage content={emptyMessage} />}
<MediaLibraryCardGrid
setScrollContainerRef={setScrollContainerRef}
scrollContainerRef={scrollContainerRef}
mediaItems={tableData}
isSelectedFile={file => selectedFile?.key === file.key}
onAssetClick={handleAssetClick}
@ -191,10 +195,12 @@ const MediaLibraryModal = ({
cardMargin={cardMargin}
loadDisplayURL={loadDisplayURL}
displayURLs={displayURLs}
collection={collection}
field={field}
/>
</DialogContent>
</StyledModal>
);
};
export default translate()(MediaLibraryModal);
export default translate()(MediaLibraryModal) as FC<MediaLibraryModalProps>;

View File

@ -0,0 +1,35 @@
import React from 'react';
import useMediaAsset from '@staticcms/core/lib/hooks/useMediaAsset';
import { selectEditingDraft } from '@staticcms/core/reducers/selectors/entryDraft';
import { useAppSelector } from '@staticcms/core/store/hooks';
import type { Collection, MediaField } from '@staticcms/core/interface';
export interface ImageProps<F extends MediaField> {
src?: string;
alt?: string;
collection?: Collection<F>;
field?: F;
}
const Image = <F extends MediaField>({ src, alt, collection, field }: ImageProps<F>) => {
const entry = useAppSelector(selectEditingDraft);
const assetSource = useMediaAsset(src, collection, field, entry);
return <img key="image" role="presentation" src={assetSource} alt={alt} />;
};
export const withMdxImage = <F extends MediaField>({
collection,
field,
}: Omit<ImageProps<F>, 'src' | 'alt'>) => {
const MdxImage = ({ src, alt }: Pick<ImageProps<F>, 'src' | 'alt'>) => (
<Image src={src} alt={alt} collection={collection} field={field} />
);
return MdxImage;
};
export default Image;

View File

@ -6,7 +6,7 @@ import { useParams } from 'react-router-dom';
import { getAdditionalLink } from '@staticcms/core/lib/registry';
import MainView from '../App/MainView';
import Sidebar from '../Collection/Sidebar';
import Sidebar from '../collection/Sidebar';
import type { ComponentType } from 'react';
import type { ConnectedProps } from 'react-redux';