Add upload from url in input media

Signed-off-by: soupette <cyril.lpz@gmail.com>
This commit is contained in:
soupette 2020-03-31 21:27:06 +02:00
parent 099f8e63b2
commit edbe7a70de
6 changed files with 208 additions and 43 deletions

View File

@ -52,11 +52,11 @@ const Card = ({
{children} {children}
</CardImgWrapper> </CardImgWrapper>
<Flex> <Flex>
<Title>{withoutFileInfo ? '' : name}</Title> <Title>{!withoutFileInfo && name}&nbsp;</Title>
{!withoutFileInfo && <Tag label={getType(fileType)} />} {!withoutFileInfo && <Tag label={getType(fileType)} />}
</Flex> </Flex>
<Text color="grey" fontSize="xs" ellipsis> <Text color="grey" fontSize="xs" ellipsis>
{withoutFileInfo ? '' : `${getExtension(fileType)} - ${fileSize}`} {!withoutFileInfo && `${getExtension(fileType)} - ${fileSize}`}
</Text> </Text>
{hasError && <ErrorMessage>{errorMessage}</ErrorMessage>} {hasError && <ErrorMessage>{errorMessage}</ErrorMessage>}
</Wrapper> </Wrapper>

View File

@ -38,6 +38,9 @@ const RowItem = ({
fileSize = file.mime ? file.type : file.size / 1000; fileSize = file.mime ? file.type : file.size / 1000;
} }
const shouldDisplayControls = !isUploading && !isDownloading && file !== null;
const shouldDisplayTrashIcon = file === null && hasError;
return ( return (
<div className="col-xs-12 col-md-6 col-xl-3" key={originalIndex}> <div className="col-xs-12 col-md-6 col-xl-3" key={originalIndex}>
<Card <Card
@ -53,7 +56,12 @@ const RowItem = ({
withoutFileInfo={isDownloading || (file === null && hasError)} withoutFileInfo={isDownloading || (file === null && hasError)}
> >
{(isUploading || isDownloading) && <InfiniteLoadingIndicator onClick={handleClick} />} {(isUploading || isDownloading) && <InfiniteLoadingIndicator onClick={handleClick} />}
{!isUploading && !isDownloading && file !== null && ( {shouldDisplayTrashIcon && (
<CardControlsWrapper className="card-control-wrapper">
<CardControl title="delete" onClick={handleClickDelete} type="trash-alt" small />
</CardControlsWrapper>
)}
{shouldDisplayControls && (
<CardControlsWrapper className="card-control-wrapper"> <CardControlsWrapper className="card-control-wrapper">
<CardControl title="delete" onClick={handleClickDelete} type="trash-alt" small /> <CardControl title="delete" onClick={handleClickDelete} type="trash-alt" small />
<CardControl title="edit" onClick={handleClickEdit} small /> <CardControl title="edit" onClick={handleClickEdit} small />

View File

@ -2,7 +2,7 @@ import React, { useEffect, useState, useRef, memo } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Modal, ModalFooter, PopUpWarning, useGlobalContext, request } from 'strapi-helper-plugin'; import { Modal, ModalFooter, PopUpWarning, useGlobalContext, request } from 'strapi-helper-plugin';
import { Button } from '@buffetjs/core'; import { Button } from '@buffetjs/core';
import { isEmpty } from 'lodash';
import { getRequestUrl, getTrad } from '../../utils'; import { getRequestUrl, getTrad } from '../../utils';
import ModalHeader from '../../components/ModalHeader'; import ModalHeader from '../../components/ModalHeader';
import pluginId from '../../pluginId'; import pluginId from '../../pluginId';
@ -12,16 +12,20 @@ import useModalContext from '../../hooks/useModalContext';
const InputModalStepper = ({ isOpen, onToggle, onInputMediaChange }) => { const InputModalStepper = ({ isOpen, onToggle, onInputMediaChange }) => {
const { formatMessage } = useGlobalContext(); const { formatMessage } = useGlobalContext();
const [shouldDeleteFile, setShouldDeleteFile] = useState(false); const [shouldDeleteFile, setShouldDeleteFile] = useState(false);
const [displayNextButton, setDisplayNextButton] = useState(false);
const { const {
addFilesToUpload, addFilesToUpload,
currentStep, currentStep,
downloadFiles,
fetchMediaLib, fetchMediaLib,
filesToDownload,
filesToUpload, filesToUpload,
fileToEdit, fileToEdit,
goTo, goTo,
handleAbortUpload, handleAbortUpload,
handleCancelFileToUpload, handleCancelFileToUpload,
handleCleanFilesError, handleCleanFilesError,
handleClickNextButton,
handleClose, handleClose,
handleEditExistingFile, handleEditExistingFile,
handleFileSelection, handleFileSelection,
@ -56,29 +60,45 @@ const InputModalStepper = ({ isOpen, onToggle, onInputMediaChange }) => {
}; };
useEffect(() => { useEffect(() => {
if (currentStep === 'upload' && filesToUploadLength === 0) { if (currentStep === 'upload') {
// Go to the modal list view when file uploading is over // Go to the modal list view when file uploading is over
if (filesToUploadLength === 0) {
goToList(); goToList();
} else {
downloadFiles();
}
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [filesToUploadLength, currentStep]); }, [filesToUploadLength, currentStep]);
const goToList = () => {
fetchMediaLib();
goTo('list');
};
const handleConfirmDeleteFile = () => {
setShouldDeleteFile(true);
toggleModalWarning();
};
const addFilesToUploadList = ({ target: { value } }) => { const addFilesToUploadList = ({ target: { value } }) => {
addFilesToUpload({ target: { value } }); addFilesToUpload({ target: { value } });
goNext(); goNext();
}; };
const goBack = () => {
goTo(prev);
};
// FIXME: when back button needed
// eslint-disable-next-line no-unused-vars
const goNext = () => {
if (next === null) {
onToggle();
return;
}
goTo(next);
};
const goToList = () => {
fetchMediaLib();
goTo('list');
};
const handleClickDeleteFile = async () => { const handleClickDeleteFile = async () => {
toggleModalWarning(); toggleModalWarning();
}; };
@ -93,6 +113,16 @@ const InputModalStepper = ({ isOpen, onToggle, onInputMediaChange }) => {
} }
}; };
const handleCloseModal = () => {
setDisplayNextButton(false);
handleClose();
};
const handleConfirmDeleteFile = () => {
setShouldDeleteFile(true);
toggleModalWarning();
};
const handleGoToAddBrowseFiles = () => { const handleGoToAddBrowseFiles = () => {
handleCleanFilesError(); handleCleanFilesError();
@ -187,25 +217,12 @@ const InputModalStepper = ({ isOpen, onToggle, onInputMediaChange }) => {
onToggle(); onToggle();
}; };
const goBack = () => { const shouldDisplayNextButton = currentStep === 'browse' && displayNextButton;
goTo(prev); const isFinishButtonDisabled = filesToUpload.some(file => file.isDownloading);
};
// FIXME: when back button needed
// eslint-disable-next-line no-unused-vars
const goNext = () => {
if (next === null) {
onToggle();
return;
}
goTo(next);
};
return ( return (
<> <>
<Modal isOpen={isOpen} onToggle={handleToggle} onClosed={handleClose}> <Modal isOpen={isOpen} onToggle={handleToggle} onClosed={handleCloseModal}>
{/* header title */} {/* header title */}
<ModalHeader <ModalHeader
goBack={goBack} goBack={goBack}
@ -218,6 +235,7 @@ const InputModalStepper = ({ isOpen, onToggle, onInputMediaChange }) => {
<Component <Component
addFilesToUpload={addFilesToUploadList} addFilesToUpload={addFilesToUploadList}
components={components} components={components}
filesToDownload={filesToDownload}
filesToUpload={filesToUpload} filesToUpload={filesToUpload}
fileToEdit={fileToEdit} fileToEdit={fileToEdit}
isEditingUploadedFile={currentStep === 'edit'} isEditingUploadedFile={currentStep === 'edit'}
@ -238,6 +256,7 @@ const InputModalStepper = ({ isOpen, onToggle, onInputMediaChange }) => {
toggleDisableForm={handleFormDisabled} toggleDisableForm={handleFormDisabled}
onToggle={handleToggle} onToggle={handleToggle}
setCropResult={handleSetCropResult} setCropResult={handleSetCropResult}
setShouldDisplayNextButton={setDisplayNextButton}
withBackButton={withBackButton} withBackButton={withBackButton}
/> />
)} )}
@ -248,7 +267,12 @@ const InputModalStepper = ({ isOpen, onToggle, onInputMediaChange }) => {
{formatMessage({ id: 'app.components.Button.cancel' })} {formatMessage({ id: 'app.components.Button.cancel' })}
</Button> </Button>
{currentStep === 'upload' && ( {currentStep === 'upload' && (
<Button type="button" color="success" onClick={handleUploadFiles}> <Button
type="button"
color="success"
onClick={handleUploadFiles}
disabled={isFinishButtonDisabled}
>
{formatMessage( {formatMessage(
{ {
id: getTrad( id: getTrad(
@ -261,6 +285,16 @@ const InputModalStepper = ({ isOpen, onToggle, onInputMediaChange }) => {
)} )}
</Button> </Button>
)} )}
{shouldDisplayNextButton && (
<Button
type="button"
color="primary"
onClick={handleClickNextButton}
disabled={isEmpty(filesToDownload)}
>
Next
</Button>
)}
{currentStep === 'edit-new' && ( {currentStep === 'edit-new' && (
<Button color="success" type="button" onClick={handleSubmitEditNewFile}> <Button color="success" type="button" onClick={handleSubmitEditNewFile}>
{formatMessage({ id: 'form.button.finish' })} {formatMessage({ id: 'form.button.finish' })}

View File

@ -2,12 +2,20 @@ import React, { useReducer, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { request, generateSearchFromFilters } from 'strapi-helper-plugin'; import { request, generateSearchFromFilters } from 'strapi-helper-plugin';
import { get } from 'lodash'; import { get } from 'lodash';
import axios from 'axios';
import pluginId from '../../pluginId'; import pluginId from '../../pluginId';
import { getRequestUrl, compactParams, createNewFilesToUploadArray } from '../../utils'; import {
getFilesToDownload,
getRequestUrl,
compactParams,
createNewFilesToUploadArray,
} from '../../utils';
import InputModalStepperContext from '../../contexts/InputModal/InputModalDataManager'; import InputModalStepperContext from '../../contexts/InputModal/InputModalDataManager';
import init from './init'; import init from './init';
import reducer, { initialState } from './reducer'; import reducer, { initialState } from './reducer';
/* eslint-disable indent */
const InputModalStepperProvider = ({ const InputModalStepperProvider = ({
allowedTypes, allowedTypes,
children, children,
@ -47,6 +55,48 @@ const InputModalStepperProvider = ({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [isOpen, params]); }, [isOpen, params]);
const downloadFiles = async () => {
const files = getFilesToDownload(filesToUpload);
try {
await Promise.all(
files.map(file => {
const { source } = file;
return axios
.get(file.fileURL, {
headers: new Headers({ Origin: window.location.origin, mode: 'cors' }),
responseType: 'blob',
cancelToken: source.token,
})
.then(({ data }) => {
const createdFile = new File([data], file.fileURL, {
type: data.type,
});
dispatch({
type: 'FILE_DOWNLOADED',
blob: createdFile,
originalIndex: file.originalIndex,
fileTempId: file.tempId,
});
})
.catch(err => {
console.error('fetch file error', err);
dispatch({
type: 'SET_FILE_TO_DOWNLOAD_ERROR',
originalIndex: file.originalIndex,
fileTempId: file.tempId,
});
});
})
);
} catch (err) {
// Silent
}
};
const handleRemoveFileToUpload = fileIndex => { const handleRemoveFileToUpload = fileIndex => {
dispatch({ dispatch({
type: 'REMOVE_FILE_TO_UPLOAD', type: 'REMOVE_FILE_TO_UPLOAD',
@ -54,11 +104,28 @@ const InputModalStepperProvider = ({
}); });
}; };
const handleFileToEditChange = ({ target: { name, value } }) => { const handleClickNextButton = () => {
// Navigate to next step
// goNext();
dispatch({ dispatch({
type: 'ON_CHANGE', type: 'ADD_URLS_TO_FILES_TO_DOWNLOAD',
nextStep: 'upload',
});
};
const handleFileToEditChange = ({ target: { name, value } }) => {
let val = value;
let type = 'ON_CHANGE';
if (name === 'url') {
val = value.split('\n');
type = 'ON_CHANGE_URLS_TO_DOWNLOAD';
}
dispatch({
type,
keys: name, keys: name,
value, value: val,
}); });
}; };
@ -186,8 +253,14 @@ const InputModalStepperProvider = ({
const handleCancelFileToUpload = fileIndex => { const handleCancelFileToUpload = fileIndex => {
const fileToCancel = get(filesToUpload, fileIndex, {}); const fileToCancel = get(filesToUpload, fileIndex, {});
const { source } = fileToCancel;
// Cancel upload // Cancel upload
if (source) {
source.cancel('Operation canceled by the user.');
} else {
fileToCancel.abortController.abort(); fileToCancel.abortController.abort();
}
handleRemoveFileToUpload(fileIndex); handleRemoveFileToUpload(fileIndex);
}; };
@ -306,11 +379,13 @@ const InputModalStepperProvider = ({
value={{ value={{
...reducerState, ...reducerState,
addFilesToUpload, addFilesToUpload,
downloadFiles,
fetchMediaLib, fetchMediaLib,
goTo, goTo,
handleAbortUpload, handleAbortUpload,
handleAllFilesSelection, handleAllFilesSelection,
handleCancelFileToUpload, handleCancelFileToUpload,
handleClickNextButton,
handleCleanFilesError, handleCleanFilesError,
handleClose, handleClose,
handleEditExistingFile, handleEditExistingFile,

View File

@ -1,12 +1,17 @@
import produce from 'immer'; import produce from 'immer';
import { intersectionWith, differenceWith, unionWith, set } from 'lodash'; import { intersectionWith, differenceWith, unionWith, set } from 'lodash';
import { createNewFilesToUploadArray, formatFileForEditing } from '../../utils'; import {
createNewFilesToDownloadArray,
createNewFilesToUploadArray,
formatFileForEditing,
} from '../../utils';
const initialState = { const initialState = {
selectedFiles: [], selectedFiles: [],
files: [], files: [],
filesToUpload: [], filesToUpload: [],
filesToDownload: [],
fileToEdit: null, fileToEdit: null,
currentTab: null, currentTab: null,
params: { params: {
@ -24,10 +29,40 @@ const reducer = (state, action) =>
// eslint-disable-next-line consistent-return // eslint-disable-next-line consistent-return
produce(state, draftState => { produce(state, draftState => {
switch (action.type) { switch (action.type) {
case 'ADD_URLS_TO_FILES_TO_DOWNLOAD': {
draftState.filesToUpload = [
...draftState.filesToUpload,
...createNewFilesToDownloadArray(draftState.filesToDownload, draftState.filesToUpload),
].map((fileToUpload, index) => ({
...fileToUpload,
originalIndex: index,
}));
draftState.currentStep = action.nextStep;
draftState.filesToDownload = [];
break;
}
case 'FILE_DOWNLOADED': {
draftState.filesToUpload.forEach((file, index) => {
if (file.tempId === action.fileTempId) {
draftState.filesToUpload[index] = {
...draftState.filesToUpload[index],
isDownloading: false,
file: action.blob,
};
}
});
break;
}
case 'ON_CHANGE': { case 'ON_CHANGE': {
set(draftState.fileToEdit, action.keys.split('.'), action.value); set(draftState.fileToEdit, action.keys.split('.'), action.value);
break; break;
} }
case 'ON_CHANGE_URLS_TO_DOWNLOAD': {
set(draftState, ['filesToDownload'], action.value);
break;
}
case 'GET_DATA_SUCCEEDED': { case 'GET_DATA_SUCCEEDED': {
draftState.files = action.files; draftState.files = action.files;
draftState.count = action.countData.count; draftState.count = action.countData.count;
@ -174,6 +209,19 @@ const reducer = (state, action) =>
); );
break; break;
} }
case 'SET_FILE_TO_DOWNLOAD_ERROR': {
draftState.filesToUpload.forEach((file, index) => {
if (file.tempId === action.fileTempId) {
draftState.filesToUpload[index] = {
...draftState.filesToUpload[index],
isDownloading: false,
hasError: true,
errorMessage: draftState.filesToUpload[index].fileURL,
};
}
});
break;
}
case 'SET_FORM_DISABLED': { case 'SET_FORM_DISABLED': {
draftState.isFormDisabled = action.isFormDisabled; draftState.isFormDisabled = action.isFormDisabled;
break; break;

View File

@ -31,7 +31,7 @@ const createNewFilesToDownloadArray = (filesURLArray, alreadyUploadedFiles) => {
fileInfo: { fileInfo: {
alternativeText: '', alternativeText: '',
caption: '', caption: '',
name: '', name: current,
}, },
fileURL: current, fileURL: current,
hasError: false, hasError: false,