add private media library for integrations

This commit is contained in:
Shawn Erquhart 2017-11-19 01:56:16 -05:00
parent d9905b4a6a
commit 9569f18ee4
7 changed files with 109 additions and 38 deletions

View File

@ -33,7 +33,7 @@ export function insertMedia(mediaPath) {
} }
export function loadMedia(opts = {}) { export function loadMedia(opts = {}) {
const { delay = 0, query = '', page = 1 } = opts; const { delay = 0, query = '', page = 1, privateUpload } = opts;
return async (dispatch, getState) => { return async (dispatch, getState) => {
const state = getState(); const state = getState();
const backend = currentBackend(state.config); const backend = currentBackend(state.config);
@ -42,17 +42,18 @@ export function loadMedia(opts = {}) {
const provider = getIntegrationProvider(state.integrations, backend.getToken, integration); const provider = getIntegrationProvider(state.integrations, backend.getToken, integration);
dispatch(mediaLoading(page)); dispatch(mediaLoading(page));
try { try {
const files = await provider.retrieve(query, page); const files = await provider.retrieve(query, page, privateUpload);
const mediaLoadedOpts = { const mediaLoadedOpts = {
page, page,
canPaginate: true, canPaginate: true,
dynamicSearch: true, dynamicSearch: true,
dynamicSearchQuery: query dynamicSearchQuery: query,
privateUpload,
}; };
return dispatch(mediaLoaded(files, mediaLoadedOpts)); return dispatch(mediaLoaded(files, mediaLoadedOpts));
} }
catch(error) { catch(error) {
return dispatch(mediaLoadFailed()); return dispatch(mediaLoadFailed({ privateUpload }));
} }
} }
dispatch(mediaLoading(page)); dispatch(mediaLoading(page));
@ -66,7 +67,8 @@ export function loadMedia(opts = {}) {
}; };
} }
export function persistMedia(file, privateUpload) { export function persistMedia(file, opts = {}) {
const { privateUpload } = opts;
return async (dispatch, getState) => { return async (dispatch, getState) => {
const state = getState(); const state = getState();
const backend = currentBackend(state.config); const backend = currentBackend(state.config);
@ -81,7 +83,7 @@ export function persistMedia(file, privateUpload) {
const asset = await backend.persistMedia(assetProxy); const asset = await backend.persistMedia(assetProxy);
return dispatch(mediaPersisted(asset)); return dispatch(mediaPersisted(asset));
} }
return dispatch(mediaPersisted(assetProxy.asset)); return dispatch(mediaPersisted(assetProxy.asset, { privateUpload }));
} }
catch(error) { catch(error) {
console.error(error); console.error(error);
@ -90,12 +92,13 @@ export function persistMedia(file, privateUpload) {
kind: 'danger', kind: 'danger',
dismissAfter: 8000, dismissAfter: 8000,
})); }));
return dispatch(mediaPersistFailed()); return dispatch(mediaPersistFailed({ privateUpload }));
} }
}; };
} }
export function deleteMedia(file) { export function deleteMedia(file, opts = {}) {
const { privateUpload } = opts;
return (dispatch, getState) => { return (dispatch, getState) => {
const state = getState(); const state = getState();
const backend = currentBackend(state.config); const backend = currentBackend(state.config);
@ -105,7 +108,7 @@ export function deleteMedia(file) {
dispatch(mediaDeleting()); dispatch(mediaDeleting());
return provider.delete(file.id) return provider.delete(file.id)
.then(() => { .then(() => {
return dispatch(mediaDeleted(file)); return dispatch(mediaDeleted(file, { privateUpload }));
}) })
.catch(error => { .catch(error => {
console.error(error); console.error(error);
@ -114,7 +117,7 @@ export function deleteMedia(file) {
kind: 'danger', kind: 'danger',
dismissAfter: 8000, dismissAfter: 8000,
})); }));
return dispatch(mediaDeleteFailed()); return dispatch(mediaDeleteFailed({ privateUpload }));
}); });
} }
dispatch(mediaDeleting()); dispatch(mediaDeleting());
@ -148,36 +151,41 @@ export function mediaLoaded(files, opts = {}) {
}; };
} }
export function mediaLoadFailed(error) { export function mediaLoadFailed(error, opts = {}) {
return { type: MEDIA_LOAD_FAILURE }; const { privateUpload } = opts;
return { type: MEDIA_LOAD_FAILURE, payload: { privateUpload } };
} }
export function mediaPersisting() { export function mediaPersisting() {
return { type: MEDIA_PERSIST_REQUEST }; return { type: MEDIA_PERSIST_REQUEST };
} }
export function mediaPersisted(asset) { export function mediaPersisted(asset, opts = {}) {
const { privateUpload } = opts;
return { return {
type: MEDIA_PERSIST_SUCCESS, type: MEDIA_PERSIST_SUCCESS,
payload: { file: asset }, payload: { file: asset, privateUpload },
}; };
} }
export function mediaPersistFailed(error) { export function mediaPersistFailed(error, opts = {}) {
return { type: MEDIA_PERSIST_FAILURE }; const { privateUpload } = opts;
return { type: MEDIA_PERSIST_FAILURE, payload: { privateUpload } };
} }
export function mediaDeleting() { export function mediaDeleting() {
return { type: MEDIA_DELETE_REQUEST }; return { type: MEDIA_DELETE_REQUEST };
} }
export function mediaDeleted(file) { export function mediaDeleted(file, opts = {}) {
const { privateUpload } = opts;
return { return {
type: MEDIA_DELETE_SUCCESS, type: MEDIA_DELETE_SUCCESS,
payload: { file }, payload: { file, privateUpload },
}; };
} }
export function mediaDeleteFailed(error) { export function mediaDeleteFailed(error, opts = {}) {
return { type: MEDIA_DELETE_FAILURE }; const { privateUpload } = opts;
return { type: MEDIA_DELETE_FAILURE, payload: { privateUpload } };
} }

View File

@ -96,3 +96,25 @@
overflow-wrap: break-word; overflow-wrap: break-word;
line-height: 1.3 !important; line-height: 1.3 !important;
} }
.nc-mediaLibrary-dialogPrivate {
background-color: var(--backgroundAltColor);
& .nc-mediaLibrary-title,
& .nc-mediaLibrary-emptyMessage,
& .nc-mediaLibrary-paginatingMessage,
& h1 {
color: var(--textFieldBorderColor);
}
& .nc-mediaLibrary-card,
& .nc-mediaLibrary-searchInput {
background-color: var(--textFieldBorderColor);
}
& button:disabled,
& label[disabled] {
background-color: rgba(217, 217, 217, 0.15);
}
}

View File

@ -48,6 +48,10 @@ class MediaLibrary extends React.Component {
if (isOpening) { if (isOpening) {
this.setState({ selectedFile: {}, query: '' }); this.setState({ selectedFile: {}, query: '' });
} }
if (isOpening && (this.props.privateUpload !== nextProps.privateUpload)) {
this.props.loadMedia({ privateUpload: nextProps.privateUpload });
}
} }
/** /**
@ -111,7 +115,7 @@ class MediaLibrary extends React.Component {
*/ */
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
const { loadMedia, persistMedia, privateUpload } = this.props; const { persistMedia, privateUpload } = this.props;
const { files: fileList } = event.dataTransfer || event.target; const { files: fileList } = event.dataTransfer || event.target;
const files = [...fileList]; const files = [...fileList];
const file = files[0]; const file = files[0];
@ -121,7 +125,7 @@ class MediaLibrary extends React.Component {
* improved in the future, but isn't currently resulting in noticeable * improved in the future, but isn't currently resulting in noticeable
* performance/load time issues. * performance/load time issues.
*/ */
await persistMedia(file, privateUpload); await persistMedia(file, { privateUpload });
this.scrollToTop(); this.scrollToTop();
}; };
@ -143,20 +147,20 @@ class MediaLibrary extends React.Component {
*/ */
handleDelete = () => { handleDelete = () => {
const { selectedFile } = this.state; const { selectedFile } = this.state;
const { files, deleteMedia } = this.props; const { files, deleteMedia, privateUpload } = this.props;
if (!window.confirm('Are you sure you want to delete selected media?')) { if (!window.confirm('Are you sure you want to delete selected media?')) {
return; return;
} }
const file = files.find(file => selectedFile.key === file.key); const file = files.find(file => selectedFile.key === file.key);
deleteMedia(file) deleteMedia(file, { privateUpload })
.then(() => { .then(() => {
this.setState({ selectedFile: {} }); this.setState({ selectedFile: {} });
}); });
}; };
handleLoadMore = () => { handleLoadMore = () => {
const { loadMedia, dynamicSearchQuery, page } = this.props; const { loadMedia, dynamicSearchQuery, page, privateUpload } = this.props;
loadMedia({ query: dynamicSearchQuery, page: page + 1 }); loadMedia({ query: dynamicSearchQuery, page: page + 1, privateUpload });
}; };
/** /**
@ -167,8 +171,9 @@ class MediaLibrary extends React.Component {
* so this handler has no impact. * so this handler has no impact.
*/ */
handleSearchKeyDown = async (event) => { handleSearchKeyDown = async (event) => {
if (event.key === 'Enter' && this.props.dynamicSearch) { const { dynamicSearch, loadMedia, privateUpload } = this.props;
await this.props.loadMedia({ query: this.state.query }) if (event.key === 'Enter' && dynamicSearch) {
await loadMedia({ query: this.state.query, privateUpload })
this.scrollToTop(); this.scrollToTop();
} }
}; };
@ -216,6 +221,7 @@ class MediaLibrary extends React.Component {
hasNextPage, hasNextPage,
page, page,
isPaginating, isPaginating,
privateUpload,
} = this.props; } = this.props;
const { query, selectedFile } = this.state; const { query, selectedFile } = this.state;
const filteredFiles = forImage ? this.filterImages(files) : files; const filteredFiles = forImage ? this.filterImages(files) : files;
@ -236,7 +242,7 @@ class MediaLibrary extends React.Component {
<Dialog <Dialog
isVisible={isVisible} isVisible={isVisible}
onClose={this.handleClose} onClose={this.handleClose}
className="nc-mediaLibrary-dialog" className={c('nc-mediaLibrary-dialog', { 'nc-mediaLibrary-dialogPrivate': privateUpload })}
footer={ footer={
<MediaLibraryFooter <MediaLibraryFooter
onDelete={this.handleDelete} onDelete={this.handleDelete}
@ -251,7 +257,10 @@ class MediaLibrary extends React.Component {
/> />
} }
> >
<h1 className="nc-mediaLibrary-title">{forImage ? 'Images' : 'Assets'}</h1> <h1 className="nc-mediaLibrary-title">
{privateUpload ? 'Private ' : null}
{forImage ? 'Images' : 'Assets'}
</h1>
<input <input
className="nc-mediaLibrary-searchInput" className="nc-mediaLibrary-searchInput"
value={query} value={query}

View File

@ -45,7 +45,7 @@ export default class FileControl extends React.Component {
handleClick = (e) => { handleClick = (e) => {
const { field, onOpenMediaLibrary } = this.props; const { field, onOpenMediaLibrary } = this.props;
return onOpenMediaLibrary({ controlID: this.controlID, privateUpload: field.private }); return onOpenMediaLibrary({ controlID: this.controlID, privateUpload: field.get('private') });
}; };
renderFileName = () => { renderFileName = () => {

View File

@ -43,7 +43,7 @@ export default class ImageControl extends React.Component {
handleClick = (e) => { handleClick = (e) => {
const { field, onOpenMediaLibrary } = this.props; const { field, onOpenMediaLibrary } = this.props;
return onOpenMediaLibrary({ controlID: this.controlID, forImage: true, privateUpload: field.private }); return onOpenMediaLibrary({ controlID: this.controlID, forImage: true, privateUpload: field.get('private') });
}; };
renderFileName = () => { renderFileName = () => {

View File

@ -1,4 +1,4 @@
import { pickBy } from 'lodash'; import { pickBy, trimEnd } from 'lodash';
import { addParams } from '../../../lib/urlHelper'; import { addParams } from '../../../lib/urlHelper';
export default class AssetStore { export default class AssetStore {
@ -10,7 +10,7 @@ export default class AssetStore {
this.getToken = getToken; this.getToken = getToken;
this.shouldConfirmUpload = config.get('shouldConfirmUpload', false); this.shouldConfirmUpload = config.get('shouldConfirmUpload', false);
this.getSignedFormURL = config.get('getSignedFormURL'); this.getSignedFormURL = trimEnd(config.get('getSignedFormURL'), '/');
} }
parseJsonResponse(response) { parseJsonResponse(response) {
@ -65,8 +65,8 @@ export default class AssetStore {
return content; return content;
} }
async retrieve(query, page) { async retrieve(query, page, privateUpload) {
const params = pickBy({ search: query, page }, val => !!val); const params = pickBy({ search: query, page, filter: privateUpload ? 'private' : 'public' }, val => !!val);
const url = addParams(this.getSignedFormURL, params); const url = addParams(this.getSignedFormURL, params);
const token = await this.getToken(); const token = await this.getToken();
const headers = { const headers = {

View File

@ -17,14 +17,26 @@ import {
} from '../actions/mediaLibrary'; } from '../actions/mediaLibrary';
const mediaLibrary = (state = Map({ isVisible: false, controlMedia: Map() }), action) => { const mediaLibrary = (state = Map({ isVisible: false, controlMedia: Map() }), action) => {
const privateUploadChanged = state.get('privateUpload') !== get(action, ['payload', 'privateUpload']);
switch (action.type) { switch (action.type) {
case MEDIA_LIBRARY_OPEN: { case MEDIA_LIBRARY_OPEN: {
const { controlID, forImage } = action.payload || {}; const { controlID, forImage, privateUpload } = action.payload || {};
if (privateUploadChanged) {
return Map({
isVisible: true,
forImage,
controlID,
canInsert: !!controlID,
privateUpload,
controlMedia: Map(),
});
}
return state.withMutations(map => { return state.withMutations(map => {
map.set('isVisible', true); map.set('isVisible', true);
map.set('forImage', forImage); map.set('forImage', forImage);
map.set('controlID', controlID); map.set('controlID', controlID);
map.set('canInsert', !!controlID); map.set('canInsert', !!controlID);
map.set('privateUpload', privateUpload);
}); });
} }
case MEDIA_LIBRARY_CLOSE: case MEDIA_LIBRARY_CLOSE:
@ -40,7 +52,12 @@ const mediaLibrary = (state = Map({ isVisible: false, controlMedia: Map() }), ac
map.set('isPaginating', action.payload.page > 1); map.set('isPaginating', action.payload.page > 1);
}); });
case MEDIA_LOAD_SUCCESS: { case MEDIA_LOAD_SUCCESS: {
const { files = [], page, canPaginate, dynamicSearch, dynamicSearchQuery } = action.payload; const { files = [], page, canPaginate, dynamicSearch, dynamicSearchQuery, privateUpload } = action.payload;
if (privateUploadChanged) {
return state;
}
const filesWithKeys = files.map(file => ({ ...file, key: uuid() })); const filesWithKeys = files.map(file => ({ ...file, key: uuid() }));
return state.withMutations(map => { return state.withMutations(map => {
map.set('isLoading', false); map.set('isLoading', false);
@ -59,11 +76,17 @@ const mediaLibrary = (state = Map({ isVisible: false, controlMedia: Map() }), ac
}); });
} }
case MEDIA_LOAD_FAILURE: case MEDIA_LOAD_FAILURE:
if (privateUploadChanged) {
return state;
}
return state.set('isLoading', false); return state.set('isLoading', false);
case MEDIA_PERSIST_REQUEST: case MEDIA_PERSIST_REQUEST:
return state.set('isPersisting', true); return state.set('isPersisting', true);
case MEDIA_PERSIST_SUCCESS: { case MEDIA_PERSIST_SUCCESS: {
const { file } = action.payload; const { file } = action.payload;
if (privateUploadChanged) {
return state;
}
return state.withMutations(map => { return state.withMutations(map => {
const fileWithKey = { ...file, key: uuid() }; const fileWithKey = { ...file, key: uuid() };
const updatedFiles = [fileWithKey, ...map.get('files')]; const updatedFiles = [fileWithKey, ...map.get('files')];
@ -72,11 +95,17 @@ const mediaLibrary = (state = Map({ isVisible: false, controlMedia: Map() }), ac
}); });
} }
case MEDIA_PERSIST_FAILURE: case MEDIA_PERSIST_FAILURE:
if (privateUploadChanged) {
return state;
}
return state.set('isPersisting', false); return state.set('isPersisting', false);
case MEDIA_DELETE_REQUEST: case MEDIA_DELETE_REQUEST:
return state.set('isDeleting', true); return state.set('isDeleting', true);
case MEDIA_DELETE_SUCCESS: { case MEDIA_DELETE_SUCCESS: {
const { key } = action.payload.file; const { key } = action.payload.file;
if (privateUploadChanged) {
return state;
}
return state.withMutations(map => { return state.withMutations(map => {
const updatedFiles = map.get('files').filter(file => file.key !== key); const updatedFiles = map.get('files').filter(file => file.key !== key);
map.set('files', updatedFiles); map.set('files', updatedFiles);
@ -84,6 +113,9 @@ const mediaLibrary = (state = Map({ isVisible: false, controlMedia: Map() }), ac
}); });
} }
case MEDIA_DELETE_FAILURE: case MEDIA_DELETE_FAILURE:
if (privateUploadChanged) {
return state;
}
return state.set('isDeleting', false); return state.set('isDeleting', false);
default: default:
return state; return state;