Netlify Large Media integration (#2124)
This commit is contained in:
committed by
Shawn Erquhart
parent
17ae6f3045
commit
da2249c651
@ -1,5 +1,6 @@
|
||||
import { Map } from 'immutable';
|
||||
import { actions as notifActions } from 'redux-notifications';
|
||||
import { getBlobSHA } from 'netlify-cms-lib-util';
|
||||
import { currentBackend } from 'src/backend';
|
||||
import { createAssetProxy } from 'ValueObjects/AssetProxy';
|
||||
import { selectIntegration } from 'Reducers';
|
||||
@ -119,7 +120,11 @@ export function loadMedia(opts = {}) {
|
||||
backend
|
||||
.getMedia()
|
||||
.then(files => dispatch(mediaLoaded(files)))
|
||||
.catch(error => dispatch(error.status === 404 ? mediaLoaded() : mediaLoadFailed())),
|
||||
.catch(
|
||||
error =>
|
||||
console.error(error) ||
|
||||
dispatch(error.status === 404 ? mediaLoaded() : mediaLoadFailed()),
|
||||
),
|
||||
),
|
||||
);
|
||||
}, delay);
|
||||
@ -153,13 +158,17 @@ export function persistMedia(file, opts = {}) {
|
||||
dispatch(mediaPersisting());
|
||||
|
||||
try {
|
||||
const id = await getBlobSHA(file);
|
||||
const getDisplayURL = () => URL.createObjectURL(file);
|
||||
const assetProxy = await createAssetProxy(fileName, file, false, privateUpload);
|
||||
dispatch(addAsset(assetProxy));
|
||||
if (!integration) {
|
||||
const asset = await backend.persistMedia(state.config, assetProxy);
|
||||
return dispatch(mediaPersisted(asset));
|
||||
return dispatch(mediaPersisted({ id, getDisplayURL, ...asset }));
|
||||
}
|
||||
return dispatch(mediaPersisted(assetProxy.asset, { privateUpload }));
|
||||
return dispatch(
|
||||
mediaPersisted({ id, getDisplayURL, ...assetProxy.asset }, { privateUpload }),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
dispatch(
|
||||
@ -221,16 +230,26 @@ export function deleteMedia(file, opts = {}) {
|
||||
}
|
||||
|
||||
export function loadMediaDisplayURL(file) {
|
||||
return async dispatch => {
|
||||
const { getBlobPromise, id } = file;
|
||||
|
||||
if (id && getBlobPromise) {
|
||||
return async (dispatch, getState) => {
|
||||
const { getDisplayURL, id, url, urlIsPublicPath } = file;
|
||||
const { mediaLibrary: mediaLibraryState } = getState();
|
||||
const displayURLPath = ['displayURLs', id];
|
||||
const shouldLoadDisplayURL =
|
||||
id &&
|
||||
((url && urlIsPublicPath) ||
|
||||
(getDisplayURL &&
|
||||
!mediaLibraryState.getIn([...displayURLPath, 'url']) &&
|
||||
!mediaLibraryState.getIn([...displayURLPath, 'isFetching']) &&
|
||||
!mediaLibraryState.getIn([...displayURLPath, 'err'])));
|
||||
if (shouldLoadDisplayURL) {
|
||||
try {
|
||||
dispatch(mediaDisplayURLRequest(id));
|
||||
const blob = await getBlobPromise();
|
||||
const newURL = window.URL.createObjectURL(blob);
|
||||
dispatch(mediaDisplayURLSuccess(id, newURL));
|
||||
return newURL;
|
||||
const newURL = (urlIsPublicPath && url) || (await getDisplayURL());
|
||||
if (newURL) {
|
||||
dispatch(mediaDisplayURLSuccess(id, newURL));
|
||||
return newURL;
|
||||
}
|
||||
throw new Error('No display URL was returned!');
|
||||
} catch (err) {
|
||||
dispatch(mediaDisplayURLFailure(id, err));
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
import { orderBy, map } from 'lodash';
|
||||
import { Map } from 'immutable';
|
||||
import { translate } from 'react-polyglot';
|
||||
import fuzzy from 'fuzzy';
|
||||
import { resolvePath, fileExtension } from 'netlify-cms-lib-util';
|
||||
@ -26,11 +25,13 @@ const IMAGE_EXTENSIONS = [...IMAGE_EXTENSIONS_VIEWABLE];
|
||||
|
||||
const fileShape = {
|
||||
key: PropTypes.string.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
size: PropTypes.number.isRequired,
|
||||
size: PropTypes.number,
|
||||
queryOrder: PropTypes.number,
|
||||
url: PropTypes.string.isRequired,
|
||||
url: PropTypes.string,
|
||||
urlIsPublicPath: PropTypes.bool,
|
||||
getDisplayURL: PropTypes.func,
|
||||
};
|
||||
|
||||
class MediaLibrary extends React.Component {
|
||||
@ -97,28 +98,9 @@ class MediaLibrary extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
getDisplayURL = file => {
|
||||
const { isVisible, loadMediaDisplayURL, displayURLs } = this.props;
|
||||
|
||||
if (!isVisible) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (file && file.url) {
|
||||
return file.url;
|
||||
}
|
||||
|
||||
const { url, isFetching } = displayURLs.get(file.id, Map()).toObject();
|
||||
|
||||
if (url && url !== '') {
|
||||
return url;
|
||||
}
|
||||
|
||||
if (!isFetching) {
|
||||
loadMediaDisplayURL(file);
|
||||
}
|
||||
|
||||
return '';
|
||||
loadDisplayURL = file => {
|
||||
const { loadMediaDisplayURL } = this.props;
|
||||
loadMediaDisplayURL(file);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -137,7 +119,7 @@ class MediaLibrary extends React.Component {
|
||||
toTableData = files => {
|
||||
const tableData =
|
||||
files &&
|
||||
files.map(({ key, name, id, size, queryOrder, url, urlIsPublicPath, getBlobPromise }) => {
|
||||
files.map(({ key, name, id, size, queryOrder, url, urlIsPublicPath, getDisplayURL }) => {
|
||||
const ext = fileExtension(name).toLowerCase();
|
||||
return {
|
||||
key,
|
||||
@ -148,7 +130,7 @@ class MediaLibrary extends React.Component {
|
||||
queryOrder,
|
||||
url,
|
||||
urlIsPublicPath,
|
||||
getBlobPromise,
|
||||
getDisplayURL,
|
||||
isImage: IMAGE_EXTENSIONS.includes(ext),
|
||||
isViewableImage: IMAGE_EXTENSIONS_VIEWABLE.includes(ext),
|
||||
};
|
||||
@ -291,6 +273,7 @@ class MediaLibrary extends React.Component {
|
||||
hasNextPage,
|
||||
isPaginating,
|
||||
privateUpload,
|
||||
displayURLs,
|
||||
t,
|
||||
} = this.props;
|
||||
|
||||
@ -322,7 +305,8 @@ class MediaLibrary extends React.Component {
|
||||
setScrollContainerRef={ref => (this.scrollContainerRef = ref)}
|
||||
handleAssetClick={this.handleAssetClick}
|
||||
handleLoadMore={this.handleLoadMore}
|
||||
getDisplayURL={this.getDisplayURL}
|
||||
displayURLs={displayURLs}
|
||||
loadDisplayURL={this.loadDisplayURL}
|
||||
t={t}
|
||||
/>
|
||||
);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import styled from 'react-emotion';
|
||||
import { colors, borders, lengths } from 'netlify-cms-ui-default';
|
||||
|
||||
@ -43,32 +44,35 @@ const CardText = styled.p`
|
||||
line-height: 1.3 !important;
|
||||
`;
|
||||
|
||||
const MediaLibraryCard = ({
|
||||
isSelected,
|
||||
displayURL,
|
||||
text,
|
||||
onClick,
|
||||
width,
|
||||
margin,
|
||||
isPrivate,
|
||||
type,
|
||||
}) => (
|
||||
<Card
|
||||
isSelected={isSelected}
|
||||
onClick={onClick}
|
||||
width={width}
|
||||
margin={margin}
|
||||
tabIndex="-1"
|
||||
isPrivate={isPrivate}
|
||||
>
|
||||
<div>{displayURL ? <CardImage src={displayURL} /> : <CardFileIcon>{type}</CardFileIcon>}</div>
|
||||
<CardText>{text}</CardText>
|
||||
</Card>
|
||||
);
|
||||
class MediaLibraryCard extends React.Component {
|
||||
render() {
|
||||
const { isSelected, displayURL, text, onClick, width, margin, isPrivate, type } = this.props;
|
||||
const url = displayURL.get('url');
|
||||
return (
|
||||
<Card
|
||||
isSelected={isSelected}
|
||||
onClick={onClick}
|
||||
width={width}
|
||||
margin={margin}
|
||||
tabIndex="-1"
|
||||
isPrivate={isPrivate}
|
||||
>
|
||||
<div>{url ? <CardImage src={url} /> : <CardFileIcon>{type}</CardFileIcon>}</div>
|
||||
<CardText>{text}</CardText>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
UNSAFE_componentWillMount() {
|
||||
const { displayURL, loadDisplayURL } = this.props;
|
||||
if (!displayURL || (!displayURL.url && !displayURL.isFetching && !displayURL.err)) {
|
||||
loadDisplayURL();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MediaLibraryCard.propTypes = {
|
||||
isSelected: PropTypes.bool,
|
||||
displayURL: PropTypes.string,
|
||||
displayURL: ImmutablePropTypes.map.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
width: PropTypes.string.isRequired,
|
||||
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import styled from 'react-emotion';
|
||||
import Waypoint from 'react-waypoint';
|
||||
import MediaLibraryCard from './MediaLibraryCard';
|
||||
import { Map } from 'immutable';
|
||||
import { colors } from 'netlify-cms-ui-default';
|
||||
|
||||
const CardGridContainer = styled.div`
|
||||
@ -33,7 +34,8 @@ const MediaLibraryCardGrid = ({
|
||||
cardWidth,
|
||||
cardMargin,
|
||||
isPrivate,
|
||||
getDisplayURL,
|
||||
displayURLs,
|
||||
loadDisplayURL,
|
||||
}) => (
|
||||
<CardGridContainer innerRef={setScrollContainerRef}>
|
||||
<CardGrid>
|
||||
@ -46,7 +48,8 @@ const MediaLibraryCardGrid = ({
|
||||
width={cardWidth}
|
||||
margin={cardMargin}
|
||||
isPrivate={isPrivate}
|
||||
displayURL={file.isViewableImage && getDisplayURL(file)}
|
||||
displayURL={displayURLs.get(file.id, Map())}
|
||||
loadDisplayURL={() => loadDisplayURL(file)}
|
||||
type={file.type}
|
||||
/>
|
||||
))}
|
||||
@ -76,7 +79,7 @@ MediaLibraryCardGrid.propTypes = {
|
||||
paginatingMessage: PropTypes.string,
|
||||
cardWidth: PropTypes.string.isRequired,
|
||||
cardMargin: PropTypes.string.isRequired,
|
||||
getDisplayURL: PropTypes.func.isRequired,
|
||||
loadDisplayURL: PropTypes.func.isRequired,
|
||||
isPrivate: PropTypes.bool,
|
||||
};
|
||||
|
||||
|
@ -93,7 +93,8 @@ const MediaLibraryModal = ({
|
||||
setScrollContainerRef,
|
||||
handleAssetClick,
|
||||
handleLoadMore,
|
||||
getDisplayURL,
|
||||
loadDisplayURL,
|
||||
displayURLs,
|
||||
t,
|
||||
}) => {
|
||||
const filteredFiles = forImage ? handleFilter(files) : files;
|
||||
@ -171,7 +172,8 @@ const MediaLibraryModal = ({
|
||||
cardWidth={cardWidth}
|
||||
cardMargin={cardMargin}
|
||||
isPrivate={privateUpload}
|
||||
getDisplayURL={getDisplayURL}
|
||||
loadDisplayURL={loadDisplayURL}
|
||||
displayURLs={displayURLs}
|
||||
/>
|
||||
</StyledModal>
|
||||
);
|
||||
@ -179,11 +181,13 @@ const MediaLibraryModal = ({
|
||||
|
||||
const fileShape = {
|
||||
key: PropTypes.string.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
size: PropTypes.number.isRequired,
|
||||
size: PropTypes.number,
|
||||
queryOrder: PropTypes.number,
|
||||
url: PropTypes.string.isRequired,
|
||||
url: PropTypes.string,
|
||||
urlIsPublicPath: PropTypes.bool,
|
||||
getDisplayURL: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
MediaLibraryModal.propTypes = {
|
||||
@ -213,7 +217,7 @@ MediaLibraryModal.propTypes = {
|
||||
setScrollContainerRef: PropTypes.func.isRequired,
|
||||
handleAssetClick: PropTypes.func.isRequired,
|
||||
handleLoadMore: PropTypes.func.isRequired,
|
||||
getDisplayURL: PropTypes.func.isRequired,
|
||||
loadDisplayURL: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
@ -169,10 +169,14 @@ const mediaLibrary = (state = Map(defaultState), action) => {
|
||||
|
||||
case MEDIA_DISPLAY_URL_FAILURE: {
|
||||
const displayURLPath = ['displayURLs', action.payload.key];
|
||||
return state
|
||||
.setIn([...displayURLPath, 'isFetching'], false)
|
||||
.setIn([...displayURLPath, 'err'], action.payload.err)
|
||||
.deleteIn([...displayURLPath, 'url']);
|
||||
return (
|
||||
state
|
||||
.setIn([...displayURLPath, 'isFetching'], false)
|
||||
// make sure that err is set so the CMS won't attempt to load
|
||||
// the image again
|
||||
.setIn([...displayURLPath, 'err'], action.payload.err || true)
|
||||
.deleteIn([...displayURLPath, 'url'])
|
||||
);
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
|
Reference in New Issue
Block a user