feat: add media lib virtualization (#3381)
This commit is contained in:
@ -56,7 +56,9 @@
|
||||
"react-sortable-hoc": "^1.0.0",
|
||||
"react-split-pane": "^0.1.85",
|
||||
"react-topbar-progress-indicator": "^2.0.0",
|
||||
"react-virtualized-auto-sizer": "^1.0.2",
|
||||
"react-waypoint": "^8.1.0",
|
||||
"react-window": "^1.8.5",
|
||||
"redux": "^4.0.1",
|
||||
"redux-notifications": "^4.0.1",
|
||||
"redux-optimist": "^1.0.0",
|
||||
|
@ -8,7 +8,7 @@ const IMAGE_HEIGHT = 160;
|
||||
|
||||
const Card = styled.div`
|
||||
width: ${props => props.width};
|
||||
height: 240px;
|
||||
height: ${props => props.height};
|
||||
margin: ${props => props.margin};
|
||||
border: ${borders.textField};
|
||||
border-color: ${props => props.isSelected && colors.active};
|
||||
@ -71,6 +71,7 @@ class MediaLibraryCard extends React.Component {
|
||||
onClick,
|
||||
draftText,
|
||||
width,
|
||||
height,
|
||||
margin,
|
||||
isPrivate,
|
||||
type,
|
||||
@ -83,6 +84,7 @@ class MediaLibraryCard extends React.Component {
|
||||
isSelected={isSelected}
|
||||
onClick={onClick}
|
||||
width={width}
|
||||
height={height}
|
||||
margin={margin}
|
||||
tabIndex="-1"
|
||||
isPrivate={isPrivate}
|
||||
@ -114,6 +116,7 @@ MediaLibraryCard.propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
draftText: PropTypes.string.isRequired,
|
||||
width: PropTypes.string.isRequired,
|
||||
height: PropTypes.string.isRequired,
|
||||
margin: PropTypes.string.isRequired,
|
||||
isPrivate: PropTypes.bool,
|
||||
type: PropTypes.string,
|
||||
|
@ -5,6 +5,143 @@ import Waypoint from 'react-waypoint';
|
||||
import MediaLibraryCard from './MediaLibraryCard';
|
||||
import { Map } from 'immutable';
|
||||
import { colors } from 'netlify-cms-ui-default';
|
||||
import { FixedSizeGrid as Grid } from 'react-window';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
|
||||
const CardWrapper = props => {
|
||||
const {
|
||||
rowIndex,
|
||||
columnIndex,
|
||||
style,
|
||||
data: {
|
||||
mediaItems,
|
||||
isSelectedFile,
|
||||
onAssetClick,
|
||||
cardDraftText,
|
||||
cardWidth,
|
||||
cardHeight,
|
||||
isPrivate,
|
||||
displayURLs,
|
||||
loadDisplayURL,
|
||||
columnCount,
|
||||
gutter,
|
||||
},
|
||||
} = props;
|
||||
const index = rowIndex * columnCount + columnIndex;
|
||||
if (index >= mediaItems.length) {
|
||||
return null;
|
||||
}
|
||||
const file = mediaItems[index];
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
...style,
|
||||
left: style.left + gutter * columnIndex,
|
||||
top: style.top + gutter,
|
||||
width: style.width - gutter,
|
||||
height: style.height - gutter,
|
||||
}}
|
||||
>
|
||||
<MediaLibraryCard
|
||||
key={file.key}
|
||||
isSelected={isSelectedFile(file)}
|
||||
text={file.name}
|
||||
onClick={() => onAssetClick(file)}
|
||||
isDraft={file.draft}
|
||||
draftText={cardDraftText}
|
||||
width={cardWidth}
|
||||
height={cardHeight}
|
||||
margin={'0px'}
|
||||
isPrivate={isPrivate}
|
||||
displayURL={displayURLs.get(file.id, file.url ? Map({ url: file.url }) : Map())}
|
||||
loadDisplayURL={() => loadDisplayURL(file)}
|
||||
type={file.type}
|
||||
isViewableImage={file.isViewableImage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const VirtualizedGrid = props => {
|
||||
const { mediaItems, setScrollContainerRef } = props;
|
||||
|
||||
return (
|
||||
<CardGridContainer ref={setScrollContainerRef}>
|
||||
<AutoSizer>
|
||||
{({ height, width }) => {
|
||||
const cardWidth = parseInt(props.cardWidth, 10);
|
||||
const cardHeight = parseInt(props.cardHeight, 10);
|
||||
const gutter = parseInt(props.cardMargin, 10);
|
||||
const columnWidth = cardWidth + gutter;
|
||||
const rowHeight = cardHeight + gutter;
|
||||
const columnCount = Math.floor(width / columnWidth);
|
||||
const rowCount = Math.ceil(mediaItems.length / columnCount);
|
||||
return (
|
||||
<Grid
|
||||
columnCount={columnCount}
|
||||
columnWidth={columnWidth}
|
||||
rowCount={rowCount}
|
||||
rowHeight={rowHeight}
|
||||
width={width}
|
||||
height={height}
|
||||
itemData={{ ...props, gutter, columnCount }}
|
||||
>
|
||||
{CardWrapper}
|
||||
</Grid>
|
||||
);
|
||||
}}
|
||||
</AutoSizer>
|
||||
</CardGridContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const PaginatedGrid = ({
|
||||
setScrollContainerRef,
|
||||
mediaItems,
|
||||
isSelectedFile,
|
||||
onAssetClick,
|
||||
cardDraftText,
|
||||
cardWidth,
|
||||
cardHeight,
|
||||
cardMargin,
|
||||
isPrivate,
|
||||
displayURLs,
|
||||
loadDisplayURL,
|
||||
canLoadMore,
|
||||
onLoadMore,
|
||||
isPaginating,
|
||||
paginatingMessage,
|
||||
}) => {
|
||||
return (
|
||||
<CardGridContainer ref={setScrollContainerRef}>
|
||||
<CardGrid>
|
||||
{mediaItems.map(file => (
|
||||
<MediaLibraryCard
|
||||
key={file.key}
|
||||
isSelected={isSelectedFile(file)}
|
||||
text={file.name}
|
||||
onClick={() => onAssetClick(file)}
|
||||
isDraft={file.draft}
|
||||
draftText={cardDraftText}
|
||||
width={cardWidth}
|
||||
height={cardHeight}
|
||||
margin={cardMargin}
|
||||
isPrivate={isPrivate}
|
||||
displayURL={displayURLs.get(file.id, file.url ? Map({ url: file.url }) : Map())}
|
||||
loadDisplayURL={() => loadDisplayURL(file)}
|
||||
type={file.type}
|
||||
isViewableImage={file.isViewableImage}
|
||||
/>
|
||||
))}
|
||||
{!canLoadMore ? null : <Waypoint onEnter={onLoadMore} />}
|
||||
</CardGrid>
|
||||
{!isPaginating ? null : (
|
||||
<PaginatingMessage isPrivate={isPrivate}>{paginatingMessage}</PaginatingMessage>
|
||||
)}
|
||||
</CardGridContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const CardGridContainer = styled.div`
|
||||
overflow-y: auto;
|
||||
@ -23,48 +160,13 @@ const PaginatingMessage = styled.h1`
|
||||
color: ${props => props.isPrivate && colors.textFieldBorder};
|
||||
`;
|
||||
|
||||
const MediaLibraryCardGrid = ({
|
||||
setScrollContainerRef,
|
||||
mediaItems,
|
||||
isSelectedFile,
|
||||
onAssetClick,
|
||||
canLoadMore,
|
||||
onLoadMore,
|
||||
isPaginating,
|
||||
paginatingMessage,
|
||||
cardDraftText,
|
||||
cardWidth,
|
||||
cardMargin,
|
||||
isPrivate,
|
||||
displayURLs,
|
||||
loadDisplayURL,
|
||||
}) => (
|
||||
<CardGridContainer ref={setScrollContainerRef}>
|
||||
<CardGrid>
|
||||
{mediaItems.map(file => (
|
||||
<MediaLibraryCard
|
||||
key={file.key}
|
||||
isSelected={isSelectedFile(file)}
|
||||
text={file.name}
|
||||
onClick={() => onAssetClick(file)}
|
||||
isDraft={file.draft}
|
||||
draftText={cardDraftText}
|
||||
width={cardWidth}
|
||||
margin={cardMargin}
|
||||
isPrivate={isPrivate}
|
||||
displayURL={displayURLs.get(file.id, file.url ? Map({ url: file.url }) : Map())}
|
||||
loadDisplayURL={() => loadDisplayURL(file)}
|
||||
type={file.type}
|
||||
isViewableImage={file.isViewableImage}
|
||||
/>
|
||||
))}
|
||||
{!canLoadMore ? null : <Waypoint onEnter={onLoadMore} />}
|
||||
</CardGrid>
|
||||
{!isPaginating ? null : (
|
||||
<PaginatingMessage isPrivate={isPrivate}>{paginatingMessage}</PaginatingMessage>
|
||||
)}
|
||||
</CardGridContainer>
|
||||
);
|
||||
const MediaLibraryCardGrid = props => {
|
||||
const { canLoadMore, isPaginating } = props;
|
||||
if (canLoadMore || isPaginating) {
|
||||
return <PaginatedGrid {...props} />;
|
||||
}
|
||||
return <VirtualizedGrid {...props} />;
|
||||
};
|
||||
|
||||
MediaLibraryCardGrid.propTypes = {
|
||||
setScrollContainerRef: PropTypes.func.isRequired,
|
||||
|
@ -17,6 +17,7 @@ import { colors } from 'netlify-cms-ui-default';
|
||||
* widths per breakpoint.
|
||||
*/
|
||||
const cardWidth = `280px`;
|
||||
const cardHeight = `240px`;
|
||||
const cardMargin = `10px`;
|
||||
|
||||
/**
|
||||
@ -172,6 +173,7 @@ const MediaLibraryModal = ({
|
||||
paginatingMessage={t('mediaLibrary.mediaLibraryModal.loading')}
|
||||
cardDraftText={t('mediaLibrary.mediaLibraryCard.draft')}
|
||||
cardWidth={cardWidth}
|
||||
cardHeight={cardHeight}
|
||||
cardMargin={cardMargin}
|
||||
isPrivate={privateUpload}
|
||||
loadDisplayURL={loadDisplayURL}
|
||||
|
@ -10,6 +10,7 @@ describe('MediaLibraryCard', () => {
|
||||
onClick: jest.fn(),
|
||||
draftText: 'Draft',
|
||||
width: '100px',
|
||||
height: '240px',
|
||||
margin: '10px',
|
||||
isViewableImage: true,
|
||||
loadDisplayURL: jest.fn(),
|
||||
|
@ -52,6 +52,7 @@ exports[`MediaLibraryCard should match snapshot for draft image 1`] = `
|
||||
|
||||
<div
|
||||
class="emotion-8 emotion-9"
|
||||
height="240px"
|
||||
tabindex="-1"
|
||||
width="100px"
|
||||
>
|
||||
@ -122,6 +123,7 @@ exports[`MediaLibraryCard should match snapshot for non draft image 1`] = `
|
||||
|
||||
<div
|
||||
class="emotion-6 emotion-7"
|
||||
height="240px"
|
||||
tabindex="-1"
|
||||
width="100px"
|
||||
>
|
||||
@ -188,6 +190,7 @@ exports[`MediaLibraryCard should match snapshot for non viewable image 1`] = `
|
||||
|
||||
<div
|
||||
class="emotion-6 emotion-7"
|
||||
height="240px"
|
||||
tabindex="-1"
|
||||
width="100px"
|
||||
>
|
||||
|
Reference in New Issue
Block a user