Merge pull request #5610 from strapi/features/media-lib-fix-bugs

ML fix front-end bugs
This commit is contained in:
cyril lopez 2020-03-26 21:19:25 +07:00 committed by GitHub
commit 6cdf0a7891
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 220 additions and 65 deletions

View File

@ -1,4 +1,5 @@
{
"kind": "collectionType",
"connection": "default",
"collectionName": "addresses",
"info": {
@ -7,14 +8,13 @@
},
"options": {
"increments": true,
"timestamps": ["created_at", "updated_at"],
"timestamps": [
"created_at",
"updated_at"
],
"comment": ""
},
"attributes": {
"geolocation": {
"type": "json",
"required": true
},
"city": {
"type": "string",
"required": true
@ -30,16 +30,22 @@
"cover": {
"model": "file",
"via": "related",
"allowedTypes": [
"files",
"images",
"videos"
],
"plugin": "upload",
"required": false,
"allowedTypes": ["images", "cover"]
"required": false
},
"images": {
"collection": "file",
"via": "related",
"allowedTypes": [
"images"
],
"plugin": "upload",
"required": false,
"allowedTypes": []
"required": false
},
"full_name": {
"type": "string",

View File

@ -1,4 +1,5 @@
{
"kind": "collectionType",
"connection": "default",
"collectionName": "categories",
"info": {

View File

@ -1,8 +1,8 @@
module.exports = {
// provider: 'aws-s3',
// provider: 'cloudinary',
// providerOptions: {
// cloud_name: '',
// api_key: '',
// api_secret: '',
// cloud_name: 'cloud-name',
// api_key: 'api-key',
// api_secret: 'api-secret',
// },
};

View File

@ -29,6 +29,7 @@
"strapi-plugin-users-permissions": "3.0.0-beta.19.3",
"strapi-provider-email-mailgun": "3.0.0-beta.19.3",
"strapi-provider-upload-aws-s3": "3.0.0-beta.19.3",
"strapi-provider-upload-cloudinary": "3.0.0-beta.19.3",
"strapi-utils": "3.0.0-beta.19.3"
},
"strapi": {

View File

@ -273,5 +273,6 @@
"notification.contentType.relations.conflict": "Content type has conflicting relations",
"notification.form.error.fields": "The form contains some errors",
"notification.form.success.fields": "Changes saved",
"notification.success.delete": "The item has been deleted",
"global.prompt.unsaved": "Are you sure you want to leave this page? All your modifications will be lost"
}

View File

@ -14,7 +14,14 @@ const Overlay = styled.div`
right: 0;
bottom: 0;
left: 0;
background: linear-gradient(rgba(0, 0, 0, 15) 0%, rgba(0, 0, 0, 0) 100%);
${({ noGradient }) => {
if (noGradient) {
return '';
}
return `background: linear-gradient(rgba(0, 0, 0, 15) 0%, rgba(0, 0, 0, 0) 100%)`;
}};
opacity: 0.5;
}
@ -25,7 +32,13 @@ const Overlay = styled.div`
right: 0;
bottom: 0;
left: 24rem;
background: linear-gradient(#fbfbfb 20%, rgba(0, 0, 100, 0) 100%);
${({ noGradient }) => {
if (noGradient) {
return '';
}
return `background: linear-gradient(#fbfbfb 20%, rgba(0, 0, 100, 0) 100%)`;
}};
box-shadow: inset 0px 2px 4px rgba(0, 0, 0, 0.1);
box-shadow: inset 0 1px 2px 0 rgba(40, 42, 49, 0.16);
}
@ -38,4 +51,9 @@ const Overlay = styled.div`
z-index: 1100;
}
`;
Overlay.defaultProps = {
noGradient: false,
};
export default Overlay;

View File

@ -110,7 +110,7 @@ class OverlayBlocker extends React.Component {
if (this.props.isOpen) {
return ReactDOM.createPortal(
<Overlay>
<Overlay noGradient={this.props.noGradient}>
<div>{content}</div>
</Overlay>,
this.overlayContainer
@ -126,6 +126,7 @@ OverlayBlocker.defaultProps = {
description: 'components.OverlayBlocker.description',
icon: 'sync-alt',
isOpen: false,
noGradient: false,
title: 'components.OverlayBlocker.title',
};
@ -134,6 +135,7 @@ OverlayBlocker.propTypes = {
description: PropTypes.string,
icon: PropTypes.string,
isOpen: PropTypes.bool,
noGradient: PropTypes.bool,
title: PropTypes.string,
};

View File

@ -13,8 +13,8 @@ import Text from './Text';
const MenuList = ({ selectProps: { changeMediaAllowedTypes, value }, ...rest }) => {
const { formatMessage } = useGlobalContext();
const Component = components.MenuList;
const areAllAllowedTypesSelected = value.value.length === 3;
const someChecked = !areAllAllowedTypesSelected && value.value.length > 0;
const areAllAllowedTypesSelected = value.value && value.value.length === 3;
const someChecked = value.value && !areAllAllowedTypesSelected && value.value.length > 0;
const options = [
{
name: 'images',
@ -58,7 +58,7 @@ const MenuList = ({ selectProps: { changeMediaAllowedTypes, value }, ...rest })
</div>
<SubUl tad="ul" isOpen>
{options.map(({ name, infos }) => {
const isChecked = value.value.includes(name);
const isChecked = value.value && value.value.includes(name);
const target = { name, value: !isChecked };
return (

View File

@ -15,8 +15,9 @@ const AllowedTypesSelect = ({ name, changeMediaAllowedTypes, styles, value }) =>
const ref = useRef();
/* eslint-disable indent */
const displayedValue =
value.length === 0
value === null || value.length === 0
? formatMessage({ id: getTrad('form.attribute.media.allowed-types.none') })
: value
.sort()
@ -35,16 +36,20 @@ const AllowedTypesSelect = ({ name, changeMediaAllowedTypes, styles, value }) =>
ref={ref}
refState={ref}
styles={styles}
value={{ label: displayedValue, value }}
value={{ label: displayedValue, value: value || '' }}
/>
);
};
AllowedTypesSelect.defaultProps = {
value: null,
};
AllowedTypesSelect.propTypes = {
changeMediaAllowedTypes: PropTypes.func.isRequired,
name: PropTypes.string.isRequired,
styles: PropTypes.object.isRequired,
value: PropTypes.array.isRequired,
value: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
};
export default AllowedTypesSelect;

View File

@ -419,8 +419,8 @@ const DataManagerProvider = ({ allIcons, children }) => {
if (!isInContentTypeView) {
emitEvent('didNotSaveComponent');
}
console.error({ err });
strapi.notification.error(err.response.payload.error || 'notification.error');
console.error({ err: err.response });
strapi.notification.error('notification.error');
}
};

View File

@ -53,6 +53,7 @@ const FormModal = () => {
const { emitEvent, formatMessage } = useGlobalContext();
const query = useQuery();
const attributeOptionRef = useRef();
const {
addAttribute,
addCreatedComponentToDynamicZone,
@ -1197,6 +1198,8 @@ const FormModal = () => {
value = retrievedValue.join('\n');
} else if (input.name === 'uid') {
value = input.value;
} else if (input.name === 'allowedTypes' && retrievedValue === '') {
value = null;
} else {
value = retrievedValue;
}

View File

@ -103,13 +103,21 @@ const reducer = (state, action) => {
return fromJS(['images', 'videos', 'files']);
}
return fromJS([]);
return null;
});
}
return state.updateIn(['modifiedData', 'allowedTypes'], list => {
return state.updateIn(['modifiedData', 'allowedTypes'], currentList => {
let list = currentList || fromJS([]);
if (list.includes(action.name)) {
return list.filter(v => v !== action.name);
list = list.filter(v => v !== action.name);
if (list.size === 0) {
return null;
}
return list;
}
return list.push(action.name);

View File

@ -248,7 +248,7 @@ describe('CTB | containers | FormModal | reducer | actions', () => {
value: false,
type: 'ON_CHANGE_ALLOWED_TYPE',
};
const expected = state.setIn(['modifiedData', 'allowedTypes'], fromJS([]));
const expected = state.setIn(['modifiedData', 'allowedTypes'], null);
expect(reducer(state, action)).toEqual(expected);
});
@ -271,7 +271,7 @@ describe('CTB | containers | FormModal | reducer | actions', () => {
expect(reducer(state, action)).toEqual(expected);
});
it('Shoul remove the type', () => {
it('Should remove the type', () => {
const state = initialState.setIn(
['modifiedData', 'allowedTypes'],
fromJS(['videos', 'images', 'files'])
@ -285,6 +285,18 @@ describe('CTB | containers | FormModal | reducer | actions', () => {
expect(reducer(state, action)).toEqual(expected);
});
it('Should remove set the allowedTypes to null if removing the last type', () => {
const state = initialState.setIn(['modifiedData', 'allowedTypes'], fromJS(['videos']));
const action = {
name: 'videos',
value: null,
type: 'ON_CHANGE_ALLOWED_TYPE',
};
const expected = state.setIn(['modifiedData', 'allowedTypes'], null);
expect(reducer(state, action)).toEqual(expected);
});
});
describe('RESET_PROPS', () => {

View File

@ -16,12 +16,7 @@ import { Inputs } from '@buffetjs/custom';
import { useGlobalContext } from 'strapi-helper-plugin';
import Cropper from 'cropperjs';
import 'cropperjs/dist/cropper.css';
import {
canDownloadFile,
createFileToDownloadName,
getTrad,
prefixFileUrlWithBackendUrl,
} from '../../utils';
import { createFileToDownloadName, getTrad, prefixFileUrlWithBackendUrl } from '../../utils';
import CardControl from '../CardControl';
import CardControlsWrapper from '../CardControlsWrapper';
import CardPreview from '../CardPreview';
@ -60,9 +55,9 @@ const EditForm = forwardRef(
const [src, setSrc] = useState(null);
const fileURL = get(fileToEdit, ['file', 'url'], null);
const isFileDownloadable = canDownloadFile(fileURL);
const prefixedFileURL = fileURL ? prefixFileUrlWithBackendUrl(fileURL) : null;
const downloadFileName = isFileDownloadable ? createFileToDownloadName(fileToEdit) : null;
const downloadFileName = createFileToDownloadName(fileToEdit);
const mimeType =
get(fileToEdit, ['file', 'type'], null) || get(fileToEdit, ['file', 'mime'], '');
const isImg = isImageType(mimeType);

View File

@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Checkbox } from '@buffetjs/core';
import { get } from 'lodash';
import { createMatrix } from '../../utils';
import Card from '../Card';
import CardControlsWrapper from '../CardControlsWrapper';
@ -19,7 +20,8 @@ const List = ({ data, onChange, onClickEditFile, selectedItems, canSelect }) =>
return (
<div className="row" key={key}>
{rowContent.map(item => {
const { id, url } = item;
const { id } = item;
const url = get(item, ['formats', 'thumbnail', 'url'], '');
const checked = selectedItems.findIndex(file => file.id === id) !== -1;
const fileUrl = url.startsWith('/') ? `${strapi.backendURL}${url}` : url;

View File

@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Checkbox } from '@buffetjs/core';
import { get } from 'lodash';
import DraggableCard from './DraggableCard';
import CardControlsWrapper from '../CardControlsWrapper';
import ListWrapper from '../ListWrapper';
@ -14,7 +15,8 @@ const SortableList = ({ data, moveAsset, onChange, onClickEditFile, selectedItem
<ListWrapper>
<div className="row">
{data.map((item, index) => {
const { id, url } = item;
const { id } = item;
const url = get(item, ['formats', 'thumbnail', 'url'], '');
const checked = selectedItems.findIndex(file => file.id === id) !== -1;
const fileUrl = url.startsWith('/') ? `${strapi.backendURL}${url}` : url;

View File

@ -71,6 +71,7 @@ const HomePage = () => {
await request(requestURL, {
method: 'DELETE',
});
strapi.notification.success('notification.success.delete');
} catch (err) {
if (isMounted) {
strapi.notification.error('notification.error');
@ -79,6 +80,8 @@ const HomePage = () => {
};
const fetchListData = async () => {
dispatch({ type: 'GET_DATA' });
const [data, count] = await Promise.all([fetchData(), fetchDataCount()]);
if (isMounted) {
@ -101,7 +104,10 @@ const HomePage = () => {
return Promise.resolve(data);
} catch (err) {
strapi.notification.error('notification.error');
if (isMounted) {
dispatch({ type: 'GET_DATA_ERROR' });
strapi.notification.error('notification.error');
}
}
return [];
@ -118,6 +124,7 @@ const HomePage = () => {
return Promise.resolve(count);
} catch (err) {
if (isMounted) {
dispatch({ type: 'GET_DATA_ERROR' });
strapi.notification.error('notification.error');
}
}
@ -204,6 +211,28 @@ const HomePage = () => {
});
};
const handleDeleteMediaFromModal = async id => {
handleClickToggleModal();
const overlayblockerParams = {
children: <div />,
noGradient: true,
};
strapi.lockApp(overlayblockerParams);
try {
await deleteMedia(id);
dispatch({
type: 'ON_DELETE_MEDIA_SUCCEEDED',
mediaId: id,
});
} catch (err) {
// Silent
} finally {
strapi.unlockApp();
}
};
const handleDeleteMedias = async () => {
await Promise.all(dataToDelete.map(item => deleteMedia(item.id)));
@ -333,7 +362,9 @@ const HomePage = () => {
initialStep={modalInitialStep}
isOpen={isModalOpen}
onClosed={handleModalClose}
onDeleteMedia={handleDeleteMediaFromModal}
onToggle={handleClickToggleModal}
refetchData={fetchListData}
/>
<PopUpWarning
isOpen={isPopupOpen}

View File

@ -11,6 +11,10 @@ const reducer = (state, action) => {
switch (action.type) {
case 'CLEAR_DATA_TO_DELETE':
return state.update('dataToDelete', () => fromJS([]));
case 'GET_DATA':
return state.update('isLoading', () => true);
case 'GET_DATA_ERROR':
return state.update('isLoading', () => false);
case 'GET_DATA_SUCCEEDED':
return state
.update('data', () => fromJS(action.data))
@ -48,6 +52,10 @@ const reducer = (state, action) => {
return dataToDelete.concat(newItems);
});
}
case 'ON_DELETE_MEDIA_SUCCEEDED':
return state
.update('data', list => list.filter(item => item.get('id') !== action.mediaId))
.update('dataCount', count => count - 1);
default:
return state;
}

View File

@ -2,6 +2,44 @@ import { fromJS } from 'immutable';
import reducer, { initialState } from '../reducer';
describe('Upload | containers | HomePage | reducer', () => {
describe('GET_DATA', () => {
it('should set isLoading to true', () => {
const state = fromJS({
isLoading: false,
test: true,
});
const action = {
type: 'GET_DATA',
};
const expected = fromJS({
isLoading: true,
test: true,
});
expect(reducer(state, action)).toEqual(expected);
});
});
describe('GET_DATA_ERROR', () => {
it('should set isLoading to false', () => {
const state = fromJS({
isLoading: true,
test: true,
});
const action = {
type: 'GET_DATA_ERROR',
};
const expected = fromJS({
isLoading: false,
test: true,
});
expect(reducer(state, action)).toEqual(expected);
});
});
it('should update data with received data', () => {
const state = initialState;

View File

@ -4,13 +4,20 @@ import { get } from 'lodash';
import { Modal, ModalFooter, PopUpWarning, useGlobalContext, request } from 'strapi-helper-plugin';
import { Button } from '@buffetjs/core';
import pluginId from '../../pluginId';
import { getRequestUrl, getTrad } from '../../utils';
import { getTrad } from '../../utils';
import ModalHeader from '../../components/ModalHeader';
import stepper from './stepper';
import init from './init';
import reducer, { initialState } from './reducer';
const ModalStepper = ({ initialFileToEdit, initialStep, isOpen, onClosed, onToggle }) => {
const ModalStepper = ({
initialFileToEdit,
initialStep,
isOpen,
onClosed,
onDeleteMedia,
onToggle,
}) => {
const { formatMessage } = useGlobalContext();
const [isWarningDeleteOpen, setIsWarningDeleteOpen] = useState(false);
const [shouldDeleteFile, setShouldDeleteFile] = useState(false);
@ -66,15 +73,15 @@ const ModalStepper = ({ initialFileToEdit, initialStep, isOpen, onClosed, onTogg
});
};
const handleCancelFileToUpload = fileIndex => {
const fileToCancel = get(filesToUpload, fileIndex, {});
const handleCancelFileToUpload = fileOriginalIndex => {
const fileToCancel = filesToUpload.find(file => file.originalIndex === fileOriginalIndex);
// Cancel upload
fileToCancel.abortController.abort();
dispatch({
type: 'REMOVE_FILE_TO_UPLOAD',
fileIndex,
fileIndex: fileOriginalIndex,
});
};
@ -123,16 +130,7 @@ const ModalStepper = ({ initialFileToEdit, initialStep, isOpen, onClosed, onTogg
if (shouldDeleteFile) {
const { id } = fileToEdit;
try {
const requestURL = getRequestUrl(`files/${id}`);
await request(requestURL, { method: 'DELETE' });
setShouldDeleteFile(false);
toggleRef.current(true);
} catch (err) {
console.log(err);
}
onDeleteMedia(id);
}
};
@ -268,11 +266,13 @@ const ModalStepper = ({ initialFileToEdit, initialStep, isOpen, onClosed, onTogg
null
);
dispatch({
type: 'SET_FILE_ERROR',
fileIndex: originalIndex,
errorMessage,
});
if (errorMessage) {
dispatch({
type: 'SET_FILE_ERROR',
fileIndex: originalIndex,
errorMessage,
});
}
}
}
);
@ -330,12 +330,12 @@ const ModalStepper = ({ initialFileToEdit, initialStep, isOpen, onClosed, onTogg
onChange={handleChange}
onClickCancelUpload={handleCancelFileToUpload}
onClickDeleteFileToUpload={
currentStep === 'edit-new' ? handleClickDeleteFileToUpload : handleClickDeleteFile
currentStep === 'edit' ? handleClickDeleteFile : handleClickDeleteFileToUpload
}
onClickEditNewFile={handleGoToEditNewFile}
onGoToAddBrowseFiles={handleGoToAddBrowseFiles}
onSubmitEdit={
currentStep === 'edit-new' ? handleSubmitEditNewFile : handleSubmitEditExistingFile
currentStep === 'edit' ? handleSubmitEditExistingFile : handleSubmitEditNewFile
}
onToggle={handleToggle}
toggleDisableForm={setIsFormDisabled}
@ -408,6 +408,7 @@ ModalStepper.defaultProps = {
initialFileToEdit: null,
initialStep: 'browse',
onClosed: () => {},
onDeleteMedia: () => {},
onToggle: () => {},
};
@ -416,6 +417,7 @@ ModalStepper.propTypes = {
initialStep: PropTypes.string,
isOpen: PropTypes.bool.isRequired,
onClosed: PropTypes.func,
onDeleteMedia: PropTypes.func,
onToggle: PropTypes.func,
};

View File

@ -7,7 +7,7 @@ const formatFileForEditing = file => {
abortController,
id: file.id,
file: {
...pick(file, ['size', 'ext', 'width', 'height', 'url', 'mime', 'name']),
...pick(file, ['size', 'ext', 'width', 'height', 'mime', 'name', 'url']),
created_at: file.created_at || file.createdAt,
},
fileInfo: pick(file, ['alternativeText', 'caption', 'name']),

View File

@ -19,8 +19,18 @@ describe('UPLOAD | utils | formatFileForEditing', () => {
updated_at: '2020-03-23T11:43:46.729Z',
alternativeText: 'test',
id: 12,
formats: null,
provider_metadata: null,
formats: {
thumbnail: {
hash: 'thumbnail_Screenshot_2020-03-26_at_13.09.24.png_df7f56f901',
ext: '.png',
mime: 'image/png',
width: 245,
height: 23,
size: 4.09,
url: '/uploads/thumbnail_Screenshot_2020-03-26_at_13.09.24.png_df7f56f901.png',
},
},
};
const abortController = new AbortController();
@ -68,7 +78,17 @@ describe('UPLOAD | utils | formatFileForEditing', () => {
updated_at: '2020-03-23T11:43:46.729Z',
alternativeText: 'test',
id: 12,
formats: null,
formats: {
thumbnail: {
hash: 'thumbnail_Screenshot_2020-03-26_at_13.09.24.png_df7f56f901',
ext: '.png',
mime: 'image/png',
width: 245,
height: 23,
size: 4.09,
url: '/uploads/thumbnail_Screenshot_2020-03-26_at_13.09.24.png_df7f56f901.png',
},
},
provider_metadata: null,
};
const abortController = new AbortController();