mirror of
https://github.com/strapi/strapi.git
synced 2025-12-26 06:35:47 +00:00
Merge branch 'features/media-lib' of github.com:strapi/strapi into media-lib/listview-fixes
This commit is contained in:
commit
c76769ee3d
@ -22,12 +22,12 @@
|
||||
"@babel/preset-env": "^7.4.3",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/runtime": "^7.4.3",
|
||||
"@buffetjs/core": "3.0.1",
|
||||
"@buffetjs/custom": "3.0.1",
|
||||
"@buffetjs/hooks": "3.0.0",
|
||||
"@buffetjs/icons": "3.0.0",
|
||||
"@buffetjs/styles": "3.0.0",
|
||||
"@buffetjs/utils": "3.0.0",
|
||||
"@buffetjs/core": "3.0.2",
|
||||
"@buffetjs/custom": "3.0.2",
|
||||
"@buffetjs/hooks": "3.0.2",
|
||||
"@buffetjs/icons": "3.0.2",
|
||||
"@buffetjs/styles": "3.0.2",
|
||||
"@buffetjs/utils": "3.0.2",
|
||||
"@fortawesome/fontawesome-free": "^5.11.2",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.25",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.11.2",
|
||||
|
||||
@ -37,14 +37,15 @@ const getFilterType = type => {
|
||||
id: 'components.FilterOptions.FILTER_TYPES._containss',
|
||||
value: '_containss',
|
||||
},
|
||||
{
|
||||
id: 'components.FilterOptions.FILTER_TYPES._in',
|
||||
value: '_in',
|
||||
},
|
||||
{
|
||||
id: 'components.FilterOptions.FILTER_TYPES._nin',
|
||||
value: '_nin',
|
||||
},
|
||||
// These lines are commented until we support the array in the admin
|
||||
// {
|
||||
// id: 'components.FilterOptions.FILTER_TYPES._in',
|
||||
// value: '_in',
|
||||
// },
|
||||
// {
|
||||
// id: 'components.FilterOptions.FILTER_TYPES._nin',
|
||||
// value: '_nin',
|
||||
// },
|
||||
];
|
||||
case 'integer':
|
||||
case 'biginteger':
|
||||
|
||||
@ -35,14 +35,15 @@ describe('HELPER PLUGIN | utils | getFilterType', () => {
|
||||
id: 'components.FilterOptions.FILTER_TYPES._containss',
|
||||
value: '_containss',
|
||||
},
|
||||
{
|
||||
id: 'components.FilterOptions.FILTER_TYPES._in',
|
||||
value: '_in',
|
||||
},
|
||||
{
|
||||
id: 'components.FilterOptions.FILTER_TYPES._nin',
|
||||
value: '_nin',
|
||||
},
|
||||
// Commenting until we support them
|
||||
// {
|
||||
// id: 'components.FilterOptions.FILTER_TYPES._in',
|
||||
// value: '_in',
|
||||
// },
|
||||
// {
|
||||
// id: 'components.FilterOptions.FILTER_TYPES._nin',
|
||||
// value: '_nin',
|
||||
// },
|
||||
];
|
||||
|
||||
it('should generate the expected array if type is text', () => {
|
||||
|
||||
@ -22,7 +22,6 @@ module.exports = {
|
||||
const contentTypes = Object.keys(strapi.contentTypes)
|
||||
.filter(uid => {
|
||||
if (uid.startsWith('strapi::')) return false;
|
||||
if (uid === 'plugins::upload.file') return false;
|
||||
|
||||
if (kind && _.get(strapi.contentTypes[uid], 'kind', 'collectionType') !== kind) {
|
||||
return false;
|
||||
|
||||
@ -81,13 +81,7 @@ const EditForm = forwardRef(
|
||||
if (prefixedFileURL) {
|
||||
setSrc(prefixedFileURL);
|
||||
} else {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onloadend = () => {
|
||||
setSrc(reader.result);
|
||||
};
|
||||
|
||||
reader.readAsDataURL(fileToEdit.file);
|
||||
setSrc(URL.createObjectURL(fileToEdit.file));
|
||||
}
|
||||
}
|
||||
}, [isImg, isVideo, fileToEdit, prefixedFileURL]);
|
||||
|
||||
@ -1,25 +1,34 @@
|
||||
import React, { useReducer } from 'react';
|
||||
import React, { useReducer, useEffect, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Select } from '@buffetjs/core';
|
||||
import { getFilterType } from 'strapi-helper-plugin';
|
||||
|
||||
import { getFilterType, request } from 'strapi-helper-plugin';
|
||||
import { getTrad } from '../../../utils';
|
||||
|
||||
import reducer, { initialState } from './reducer';
|
||||
|
||||
import filtersForm from './utils/filtersForm';
|
||||
|
||||
import Wrapper from './Wrapper';
|
||||
import InputWrapper from './InputWrapper';
|
||||
import FilterButton from './FilterButton';
|
||||
import FilterInput from './FilterInput';
|
||||
|
||||
const FiltersCard = ({ onChange, filters }) => {
|
||||
// Not using the hook from buffet.js because it appears that when the component is closed we the hooks returns false
|
||||
// Until we make a PR on @buffetjs/hooks I rather use this custom one
|
||||
const isMounted = useRef(true);
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
|
||||
const { name, filter, value } = state.toJS();
|
||||
useEffect(() => {
|
||||
if (isMounted.current) {
|
||||
fetchTimestampNames();
|
||||
}
|
||||
|
||||
return () => (isMounted.current = false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isMounted]);
|
||||
|
||||
const { name, filter, filtersForm, value } = state.toJS();
|
||||
const type = filtersForm[name].type;
|
||||
const filtersOptions = getFilterType(type);
|
||||
const options = ['image', 'video', 'file'].filter(
|
||||
@ -36,7 +45,7 @@ const FiltersCard = ({ onChange, filters }) => {
|
||||
};
|
||||
|
||||
const addFilter = () => {
|
||||
onChange({ target: { value: state.toJS() } });
|
||||
onChange({ target: { value: { name, filter, value } } });
|
||||
|
||||
dispatch({
|
||||
type: 'RESET_FORM',
|
||||
@ -61,6 +70,25 @@ const FiltersCard = ({ onChange, filters }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const fetchTimestampNames = async () => {
|
||||
try {
|
||||
const result = await request('/content-manager/content-types/plugins::upload.file', {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
if (isMounted.current) {
|
||||
dispatch({
|
||||
type: 'HANDLE_CUSTOM_TIMESTAMPS',
|
||||
timestamps: result.data.contentType.schema.options.timestamps,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
if (isMounted.current) {
|
||||
strapi.notification.error('notification.error');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<InputWrapper>
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import { fromJS } from 'immutable';
|
||||
import moment from 'moment';
|
||||
|
||||
import filters from './utils/filtersForm';
|
||||
import filtersForm from './utils/filtersForm';
|
||||
|
||||
const initialState = fromJS({
|
||||
name: 'created_at',
|
||||
filter: '=',
|
||||
value: moment(),
|
||||
filtersForm,
|
||||
});
|
||||
|
||||
function reducer(state, action) {
|
||||
@ -17,14 +18,46 @@ function reducer(state, action) {
|
||||
if (name === 'name') {
|
||||
return state
|
||||
.update(name, () => value)
|
||||
.update('filter', () => filters[value].defaultFilter)
|
||||
.update('value', () => defaultValue || filters[value].defaultValue);
|
||||
.update('filter', () => state.getIn(['filtersForm', value, 'defaultFilter']))
|
||||
.update(
|
||||
'value',
|
||||
() => defaultValue || state.getIn(['filtersForm', value, 'defaultValue'])
|
||||
);
|
||||
}
|
||||
|
||||
return state.update(name, () => value);
|
||||
}
|
||||
case 'HANDLE_CUSTOM_TIMESTAMPS': {
|
||||
const {
|
||||
timestamps: [created_at, updated_at],
|
||||
} = action;
|
||||
|
||||
return state
|
||||
.update('name', () => created_at)
|
||||
.updateIn(['filtersForm'], object => {
|
||||
return object.keySeq().reduce((acc, current) => {
|
||||
if (current === 'created_at') {
|
||||
return acc.set(created_at, object.get('created_at')).remove('created_at');
|
||||
}
|
||||
|
||||
if (current === 'updated_at') {
|
||||
return acc.set(updated_at, object.get('updated_at')).remove('updated_at');
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, object);
|
||||
});
|
||||
}
|
||||
case 'RESET_FORM':
|
||||
return initialState;
|
||||
return initialState
|
||||
.set(
|
||||
'name',
|
||||
state
|
||||
.get('filtersForm')
|
||||
.keySeq()
|
||||
.first()
|
||||
)
|
||||
.update('filtersForm', () => state.get('filtersForm'));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { fromJS } from 'immutable';
|
||||
import reducer, { initialState } from '../reducer';
|
||||
|
||||
describe('Upload | components | FiltersCard | reducer', () => {
|
||||
@ -30,6 +31,70 @@ describe('Upload | components | FiltersCard | reducer', () => {
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
it('should return the updated filters form with custom timestamps', () => {
|
||||
const state = fromJS({
|
||||
name: 'created_at',
|
||||
filter: '=',
|
||||
value: 'test',
|
||||
filtersForm: {
|
||||
created_at: {
|
||||
type: 'datetime',
|
||||
defaultFilter: '=',
|
||||
defaultValue: 'test1',
|
||||
},
|
||||
updated_at: {
|
||||
type: 'datetime',
|
||||
defaultFilter: '=',
|
||||
defaultValue: 'test2',
|
||||
},
|
||||
size: {
|
||||
type: 'integer',
|
||||
defaultFilter: '=',
|
||||
defaultValue: '0KB',
|
||||
},
|
||||
mime: {
|
||||
type: 'enum',
|
||||
defaultFilter: '_contains',
|
||||
defaultValue: 'image',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const action = {
|
||||
type: 'HANDLE_CUSTOM_TIMESTAMPS',
|
||||
timestamps: ['createdAtCustom', 'updatedAtCustom'],
|
||||
};
|
||||
|
||||
const expected = fromJS({
|
||||
name: 'createdAtCustom',
|
||||
filter: '=',
|
||||
value: 'test',
|
||||
filtersForm: {
|
||||
createdAtCustom: {
|
||||
type: 'datetime',
|
||||
defaultFilter: '=',
|
||||
defaultValue: 'test1',
|
||||
},
|
||||
updatedAtCustom: {
|
||||
type: 'datetime',
|
||||
defaultFilter: '=',
|
||||
defaultValue: 'test2',
|
||||
},
|
||||
size: {
|
||||
type: 'integer',
|
||||
defaultFilter: '=',
|
||||
defaultValue: '0KB',
|
||||
},
|
||||
mime: {
|
||||
type: 'enum',
|
||||
defaultFilter: '_contains',
|
||||
defaultValue: 'image',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(reducer(state, action)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should return the initialState on reset', () => {
|
||||
const state = initialState.set('filter', '>');
|
||||
|
||||
@ -20,7 +20,7 @@ import Input from '../Input';
|
||||
const InputMedia = ({ label, onChange, name, attribute, value, type }) => {
|
||||
const [modal, setModal] = useState({
|
||||
isOpen: false,
|
||||
step: null,
|
||||
step: 'list',
|
||||
fileToEdit: null,
|
||||
});
|
||||
const [fileToDisplay, setFileToDisplay] = useState(0);
|
||||
@ -123,19 +123,17 @@ const InputMedia = ({ label, onChange, name, attribute, value, type }) => {
|
||||
<Input type="file" name={name} />
|
||||
</CardPreviewWrapper>
|
||||
|
||||
{modal.isOpen && (
|
||||
<InputModalStepper
|
||||
isOpen={modal.isOpen}
|
||||
step={modal.step}
|
||||
fileToEdit={modal.fileToEdit}
|
||||
filesToUpload={modal.filesToUpload}
|
||||
multiple={attribute.multiple}
|
||||
onInputMediaChange={handleChange}
|
||||
selectedFiles={value}
|
||||
onToggle={handleClickToggleModal}
|
||||
allowedTypes={attribute.allowedTypes}
|
||||
/>
|
||||
)}
|
||||
<InputModalStepper
|
||||
isOpen={modal.isOpen}
|
||||
step={modal.step}
|
||||
fileToEdit={modal.fileToEdit}
|
||||
filesToUpload={modal.filesToUpload}
|
||||
multiple={attribute.multiple}
|
||||
onInputMediaChange={handleChange}
|
||||
selectedFiles={value}
|
||||
onToggle={handleClickToggleModal}
|
||||
allowedTypes={attribute.allowedTypes}
|
||||
/>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@ -5,10 +5,11 @@ import PropTypes from 'prop-types';
|
||||
import Wrapper from './Wrapper';
|
||||
import { getTrad } from '../../utils';
|
||||
|
||||
const InputUploadURL = ({ onChange, value }) => {
|
||||
const InputUploadURL = ({ errors, onChange, value }) => {
|
||||
const { formatMessage } = useGlobalContext();
|
||||
const label = formatMessage({ id: getTrad('input.url.label') });
|
||||
const description = formatMessage({ id: getTrad('input.url.description') });
|
||||
const error = errors ? formatMessage({ id: errors.id }, { number: errors.number }) : null;
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
@ -16,6 +17,7 @@ const InputUploadURL = ({ onChange, value }) => {
|
||||
<div className="col-12">
|
||||
<Inputs
|
||||
autoFocus
|
||||
error={error}
|
||||
type="textarea"
|
||||
name="url"
|
||||
onChange={onChange}
|
||||
@ -30,11 +32,13 @@ const InputUploadURL = ({ onChange, value }) => {
|
||||
};
|
||||
|
||||
InputUploadURL.defaultProps = {
|
||||
errors: null,
|
||||
onChange: () => {},
|
||||
value: [],
|
||||
};
|
||||
|
||||
InputUploadURL.propTypes = {
|
||||
errors: PropTypes.object,
|
||||
onChange: PropTypes.func,
|
||||
value: PropTypes.arrayOf(PropTypes.string),
|
||||
};
|
||||
|
||||
@ -8,6 +8,7 @@ import ModalSection from '../ModalSection';
|
||||
const UploadForm = ({
|
||||
addFilesToUpload,
|
||||
filesToDownload,
|
||||
formErrors,
|
||||
onChange,
|
||||
setShouldDisplayNextButton,
|
||||
}) => {
|
||||
@ -32,7 +33,9 @@ const UploadForm = ({
|
||||
{to => (
|
||||
<ModalSection>
|
||||
{to === 'computer' && <InputFileModal onChange={addFilesToUpload} />}
|
||||
{to === 'url' && <InputUploadURL onChange={onChange} value={filesToDownload} />}
|
||||
{to === 'url' && (
|
||||
<InputUploadURL errors={formErrors} onChange={onChange} value={filesToDownload} />
|
||||
)}
|
||||
</ModalSection>
|
||||
)}
|
||||
</ModalNavWrapper>
|
||||
@ -42,6 +45,7 @@ const UploadForm = ({
|
||||
UploadForm.defaultProps = {
|
||||
addFilesToUpload: () => {},
|
||||
filesToDownload: [],
|
||||
formErrors: null,
|
||||
onChange: () => {},
|
||||
setShouldDisplayNextButton: () => {},
|
||||
};
|
||||
@ -49,6 +53,7 @@ UploadForm.defaultProps = {
|
||||
UploadForm.propTypes = {
|
||||
addFilesToUpload: PropTypes.func,
|
||||
filesToDownload: PropTypes.arrayOf(PropTypes.string),
|
||||
formErrors: PropTypes.object,
|
||||
onChange: PropTypes.func,
|
||||
setShouldDisplayNextButton: PropTypes.func,
|
||||
};
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import React, { useReducer, useState, useEffect } from 'react';
|
||||
import React, { useReducer, useState, useRef, useEffect } from 'react';
|
||||
import { includes, isEmpty, toString } from 'lodash';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { Header } from '@buffetjs/custom';
|
||||
import { useDebounce, useIsMounted } from '@buffetjs/hooks';
|
||||
import { useDebounce } from '@buffetjs/hooks';
|
||||
import {
|
||||
HeaderSearch,
|
||||
PageFooter,
|
||||
@ -46,7 +46,7 @@ const HomePage = () => {
|
||||
const [searchValue, setSearchValue] = useState(query.get('_q') || '');
|
||||
const { push } = useHistory();
|
||||
const { search } = useLocation();
|
||||
const isMounted = useIsMounted();
|
||||
const isMounted = useRef(true);
|
||||
const { data, dataCount, dataToDelete, isLoading } = reducerState.toJS();
|
||||
const pluginName = formatMessage({ id: getTrad('plugin.name') });
|
||||
const paramsKeys = ['_limit', '_start', '_q', '_sort'];
|
||||
@ -61,6 +61,7 @@ const HomePage = () => {
|
||||
useEffect(() => {
|
||||
fetchListData();
|
||||
|
||||
return () => (isMounted.current = false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [search]);
|
||||
|
||||
@ -72,7 +73,7 @@ const HomePage = () => {
|
||||
method: 'DELETE',
|
||||
});
|
||||
} catch (err) {
|
||||
if (isMounted) {
|
||||
if (isMounted.current) {
|
||||
strapi.notification.error('notification.error');
|
||||
}
|
||||
}
|
||||
@ -83,7 +84,7 @@ const HomePage = () => {
|
||||
|
||||
const [data, count] = await Promise.all([fetchData(), fetchDataCount()]);
|
||||
|
||||
if (isMounted) {
|
||||
if (isMounted.current) {
|
||||
dispatch({
|
||||
type: 'GET_DATA_SUCCEEDED',
|
||||
data,
|
||||
@ -103,7 +104,7 @@ const HomePage = () => {
|
||||
|
||||
return Promise.resolve(data);
|
||||
} catch (err) {
|
||||
if (isMounted) {
|
||||
if (isMounted.current) {
|
||||
dispatch({ type: 'GET_DATA_ERROR' });
|
||||
strapi.notification.error('notification.error');
|
||||
}
|
||||
@ -122,7 +123,7 @@ const HomePage = () => {
|
||||
|
||||
return Promise.resolve(count);
|
||||
} catch (err) {
|
||||
if (isMounted) {
|
||||
if (isMounted.current) {
|
||||
dispatch({ type: 'GET_DATA_ERROR' });
|
||||
strapi.notification.error('notification.error');
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ const InputModalStepper = ({ isOpen, onToggle, onInputMediaChange }) => {
|
||||
filesToDownload,
|
||||
filesToUpload,
|
||||
fileToEdit,
|
||||
formErrors,
|
||||
goTo,
|
||||
handleAbortUpload,
|
||||
handleCancelFileToUpload,
|
||||
@ -205,14 +206,16 @@ const InputModalStepper = ({ isOpen, onToggle, onInputMediaChange }) => {
|
||||
const handleToggle = () => {
|
||||
if (filesToUploadLength > 0 || selectedFiles.length > 0) {
|
||||
// eslint-disable-next-line no-alert
|
||||
const confirm = window.confirm(formatMessage({ id: getTrad('window.confirm.close-modal') }));
|
||||
const confirm = window.confirm(
|
||||
formatMessage({ id: getTrad('window.confirm.close-modal.files') })
|
||||
);
|
||||
|
||||
if (!confirm) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
onToggle();
|
||||
onToggle(true);
|
||||
};
|
||||
|
||||
const shouldDisplayNextButton = currentStep === 'browse' && displayNextButton;
|
||||
@ -236,6 +239,7 @@ const InputModalStepper = ({ isOpen, onToggle, onInputMediaChange }) => {
|
||||
filesToDownload={filesToDownload}
|
||||
filesToUpload={filesToUpload}
|
||||
fileToEdit={fileToEdit}
|
||||
formErrors={formErrors}
|
||||
isEditingUploadedFile={currentStep === 'edit'}
|
||||
isFormDisabled={isFormDisabled}
|
||||
onAbortUpload={handleAbortUpload}
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
import React, { useReducer, useEffect } from 'react';
|
||||
import React, { useReducer, useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { request, generateSearchFromFilters } from 'strapi-helper-plugin';
|
||||
import { get } from 'lodash';
|
||||
import { get, isEmpty } from 'lodash';
|
||||
import axios from 'axios';
|
||||
import pluginId from '../../pluginId';
|
||||
import {
|
||||
getFilesToDownload,
|
||||
getRequestUrl,
|
||||
getYupError,
|
||||
compactParams,
|
||||
createNewFilesToUploadArray,
|
||||
urlSchema,
|
||||
} from '../../utils';
|
||||
import InputModalStepperContext from '../../contexts/InputModal/InputModalDataManager';
|
||||
import init from './init';
|
||||
@ -28,6 +30,7 @@ const InputModalStepperProvider = ({
|
||||
selectedFiles,
|
||||
step,
|
||||
}) => {
|
||||
const [formErrors, setFormErrors] = useState(null);
|
||||
const [reducerState, dispatch] = useReducer(reducer, initialState, state =>
|
||||
init({
|
||||
...state,
|
||||
@ -46,7 +49,7 @@ const InputModalStepperProvider = ({
|
||||
},
|
||||
})
|
||||
);
|
||||
const { params, filesToUpload, fileToEdit } = reducerState;
|
||||
const { params, filesToDownload, filesToUpload, fileToEdit } = reducerState;
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
@ -104,11 +107,24 @@ const InputModalStepperProvider = ({
|
||||
});
|
||||
};
|
||||
|
||||
const handleClickNextButton = () => {
|
||||
dispatch({
|
||||
type: 'ADD_URLS_TO_FILES_TO_UPLOAD',
|
||||
nextStep: 'upload',
|
||||
});
|
||||
const handleClickNextButton = async () => {
|
||||
try {
|
||||
await urlSchema.validate(
|
||||
{ filesToDownload: filesToDownload.filter(url => !isEmpty(url)) },
|
||||
{ abortEarly: false }
|
||||
);
|
||||
|
||||
setFormErrors(null);
|
||||
// Navigate to next step
|
||||
dispatch({
|
||||
type: 'ADD_URLS_TO_FILES_TO_UPLOAD',
|
||||
nextStep: 'upload',
|
||||
});
|
||||
} catch (err) {
|
||||
const formattedErrors = getYupError(err);
|
||||
|
||||
setFormErrors(formattedErrors.filesToDownload);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileToEditChange = ({ target: { name, value } }) => {
|
||||
@ -116,6 +132,8 @@ const InputModalStepperProvider = ({
|
||||
let type = 'ON_CHANGE';
|
||||
|
||||
if (name === 'url') {
|
||||
setFormErrors(null);
|
||||
|
||||
val = value.split('\n');
|
||||
type = 'ON_CHANGE_URLS_TO_DOWNLOAD';
|
||||
}
|
||||
@ -168,6 +186,8 @@ const InputModalStepperProvider = ({
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setFormErrors(null);
|
||||
|
||||
dispatch({
|
||||
type: 'RESET_PROPS',
|
||||
});
|
||||
@ -381,6 +401,7 @@ const InputModalStepperProvider = ({
|
||||
addFilesToUpload,
|
||||
downloadFiles,
|
||||
fetchMediaLib,
|
||||
formErrors,
|
||||
goTo,
|
||||
handleAbortUpload,
|
||||
handleAllFilesSelection,
|
||||
|
||||
@ -5,7 +5,7 @@ import { isEqual, isEmpty, get } from 'lodash';
|
||||
import { Modal, ModalFooter, PopUpWarning, useGlobalContext, request } from 'strapi-helper-plugin';
|
||||
import { Button } from '@buffetjs/core';
|
||||
import pluginId from '../../pluginId';
|
||||
import { getFilesToDownload, getTrad } from '../../utils';
|
||||
import { getFilesToDownload, getTrad, getYupError, urlSchema } from '../../utils';
|
||||
import ModalHeader from '../../components/ModalHeader';
|
||||
import stepper from './stepper';
|
||||
import init from './init';
|
||||
@ -23,6 +23,7 @@ const ModalStepper = ({
|
||||
const [isWarningDeleteOpen, setIsWarningDeleteOpen] = useState(false);
|
||||
const [shouldDeleteFile, setShouldDeleteFile] = useState(false);
|
||||
const [isFormDisabled, setIsFormDisabled] = useState(false);
|
||||
const [formErrors, setFormErrors] = useState(null);
|
||||
const [displayNextButton, setDisplayNextButton] = useState(false);
|
||||
const [reducerState, dispatch] = useReducer(reducer, initialState, init);
|
||||
const { currentStep, fileToEdit, filesToDownload, filesToUpload } = reducerState.toJS();
|
||||
@ -150,6 +151,8 @@ const ModalStepper = ({
|
||||
let type = 'ON_CHANGE';
|
||||
|
||||
if (name === 'url') {
|
||||
setFormErrors(null);
|
||||
|
||||
val = value.split('\n');
|
||||
type = 'ON_CHANGE_URLS_TO_DOWNLOAD';
|
||||
}
|
||||
@ -166,12 +169,24 @@ const ModalStepper = ({
|
||||
toggleModalWarning();
|
||||
};
|
||||
|
||||
const handleClickNextButton = () => {
|
||||
// Navigate to next step
|
||||
dispatch({
|
||||
type: 'ADD_URLS_TO_FILES_TO_UPLOAD',
|
||||
nextStep: next,
|
||||
});
|
||||
const handleClickNextButton = async () => {
|
||||
try {
|
||||
await urlSchema.validate(
|
||||
{ filesToDownload: filesToDownload.filter(url => !isEmpty(url)) },
|
||||
{ abortEarly: false }
|
||||
);
|
||||
|
||||
setFormErrors(null);
|
||||
// Navigate to next step
|
||||
dispatch({
|
||||
type: 'ADD_URLS_TO_FILES_TO_UPLOAD',
|
||||
nextStep: next,
|
||||
});
|
||||
} catch (err) {
|
||||
const formattedErrors = getYupError(err);
|
||||
|
||||
setFormErrors(formattedErrors.filesToDownload);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClickDeleteFile = async () => {
|
||||
@ -197,6 +212,7 @@ const ModalStepper = ({
|
||||
onClosed();
|
||||
setIsFormDisabled(false);
|
||||
setDisplayNextButton(false);
|
||||
setFormErrors(null);
|
||||
|
||||
dispatch({
|
||||
type: 'RESET_PROPS',
|
||||
@ -317,7 +333,7 @@ const ModalStepper = ({
|
||||
}
|
||||
}
|
||||
|
||||
onToggle();
|
||||
onToggle(true);
|
||||
};
|
||||
|
||||
const handleUploadFiles = async () => {
|
||||
@ -416,6 +432,7 @@ const ModalStepper = ({
|
||||
fileToEdit={fileToEdit}
|
||||
filesToDownload={filesToDownload}
|
||||
filesToUpload={filesToUpload}
|
||||
formErrors={formErrors}
|
||||
components={components}
|
||||
isEditingUploadedFile={currentStep === 'edit'}
|
||||
isFormDisabled={isFormDisabled}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import React, { useEffect, useReducer, useRef } from 'react';
|
||||
import { Header, Inputs } from '@buffetjs/custom';
|
||||
import { useIsMounted } from '@buffetjs/hooks';
|
||||
import { isEqual } from 'lodash';
|
||||
import { LoadingIndicatorPage, useGlobalContext, request } from 'strapi-helper-plugin';
|
||||
|
||||
@ -16,7 +15,7 @@ const SettingsPage = () => {
|
||||
const { formatMessage } = useGlobalContext();
|
||||
const [reducerState, dispatch] = useReducer(reducer, initialState, init);
|
||||
const { initialData, isLoading, modifiedData } = reducerState.toJS();
|
||||
const isMounted = useIsMounted();
|
||||
const isMounted = useRef(true);
|
||||
const getDataRef = useRef();
|
||||
const abortController = new AbortController();
|
||||
|
||||
@ -25,7 +24,7 @@ const SettingsPage = () => {
|
||||
const { signal } = abortController;
|
||||
const { data } = await request(getRequestUrl('settings', { method: 'GET', signal }));
|
||||
|
||||
if (isMounted) {
|
||||
if (isMounted.current) {
|
||||
dispatch({
|
||||
type: 'GET_DATA_SUCCEEDED',
|
||||
data,
|
||||
@ -41,6 +40,7 @@ const SettingsPage = () => {
|
||||
|
||||
return () => {
|
||||
abortController.abort();
|
||||
isMounted.current = false;
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
@ -52,7 +52,7 @@ const SettingsPage = () => {
|
||||
body: modifiedData,
|
||||
});
|
||||
|
||||
if (isMounted) {
|
||||
if (isMounted.current) {
|
||||
dispatch({
|
||||
type: 'SUBMIT_SUCCEEDED',
|
||||
});
|
||||
|
||||
@ -14,6 +14,8 @@
|
||||
"form.input.decription.file-alt": "This text will be displayed if the asset can’t be shown.",
|
||||
"form.input.label.file-caption": "Caption",
|
||||
"form.input.label.file-name": "File name",
|
||||
"form.upload-url.error.url.invalid": "One URL is invalid",
|
||||
"form.upload-url.error.url.invalids": "{number} URLs are invalids",
|
||||
"header.actions.upload-assets": "Upload assets",
|
||||
"header.content.assets-empty": "No asset",
|
||||
"header.content.assets-multiple": "{number} assets",
|
||||
|
||||
17
packages/strapi-plugin-upload/admin/src/utils/getYupError.js
Normal file
17
packages/strapi-plugin-upload/admin/src/utils/getYupError.js
Normal file
@ -0,0 +1,17 @@
|
||||
import { get } from 'lodash';
|
||||
|
||||
const getYupError = error => {
|
||||
return get(error, 'inner', []).reduce((acc, curr) => {
|
||||
acc[
|
||||
curr.path
|
||||
.split('[')
|
||||
.join('.')
|
||||
.split(']')
|
||||
.join('')
|
||||
] = { id: curr.message, number: curr.params.wrongURLsNumber };
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
||||
export default getYupError;
|
||||
@ -13,4 +13,6 @@ export { default as getFilesToDownload } from './getFilesToDownload';
|
||||
export { default as getRequestUrl } from './getRequestUrl';
|
||||
export { default as getTrad } from './getTrad';
|
||||
export { default as getType } from './getType';
|
||||
export { default as getYupError } from './getYupError';
|
||||
export { default as ItemTypes } from './ItemTypes';
|
||||
export { default as urlSchema } from './urlYupSchema';
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
/* eslint-disable no-template-curly-in-string */
|
||||
import * as yup from 'yup';
|
||||
import { translatedErrors as errorsTrads } from 'strapi-helper-plugin';
|
||||
import getTrad from './getTrad';
|
||||
|
||||
const urlSchema = yup.object().shape({
|
||||
filesToDownload: yup
|
||||
.array()
|
||||
.of(yup.string())
|
||||
|
||||
.test({
|
||||
name: 'isUrlValid',
|
||||
message: '${path}',
|
||||
test(values) {
|
||||
const filtered = values.filter(val => {
|
||||
return !val.startsWith('http');
|
||||
});
|
||||
|
||||
const filteredLength = filtered.length;
|
||||
|
||||
if (filteredLength === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const errorMessage =
|
||||
filteredLength > 1
|
||||
? 'form.upload-url.error.url.invalids'
|
||||
: 'form.upload-url.error.url.invalid';
|
||||
|
||||
return this.createError({
|
||||
path: this.path,
|
||||
message: getTrad(errorMessage),
|
||||
params: { wrongURLsNumber: filtered.length },
|
||||
});
|
||||
},
|
||||
})
|
||||
.min(0, errorsTrads.min)
|
||||
.max(20, errorsTrads.max),
|
||||
});
|
||||
|
||||
export default urlSchema;
|
||||
62
yarn.lock
62
yarn.lock
@ -844,15 +844,15 @@
|
||||
lodash "^4.17.13"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@buffetjs/core@3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@buffetjs/core/-/core-3.0.1.tgz#d508415a34d31f187008245b2e209e242c27e019"
|
||||
integrity sha512-OLtHynNaaUJ03YRundxHRiXVs//IdFwep/fS3Xt8aYJH/xtVhPO5nqLGAkN8T6jyLVn+fxYZ7zy+T9ccrta3TQ==
|
||||
"@buffetjs/core@3.0.2":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@buffetjs/core/-/core-3.0.2.tgz#b7ab4600f142c581b7ec70fca2042168be820f05"
|
||||
integrity sha512-2pqMusTjI3JB9YzPFAya7ixai3cOwolITaPgP6e1xJ/5/nCyJOX1k90Zg2eJvQPRIUWvQrk7dMHiUeBXHuJAZw==
|
||||
dependencies:
|
||||
"@buffetjs/hooks" "3.0.0"
|
||||
"@buffetjs/icons" "3.0.0"
|
||||
"@buffetjs/styles" "3.0.0"
|
||||
"@buffetjs/utils" "3.0.0"
|
||||
"@buffetjs/hooks" "3.0.2"
|
||||
"@buffetjs/icons" "3.0.2"
|
||||
"@buffetjs/styles" "3.0.2"
|
||||
"@buffetjs/utils" "3.0.2"
|
||||
"@fortawesome/fontawesome-svg-core" "^1.2.25"
|
||||
"@fortawesome/free-regular-svg-icons" "^5.11.2"
|
||||
"@fortawesome/free-solid-svg-icons" "^5.11.2"
|
||||
@ -863,31 +863,31 @@
|
||||
react-dates "^21.5.1"
|
||||
react-moment-proptypes "^1.7.0"
|
||||
|
||||
"@buffetjs/custom@3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@buffetjs/custom/-/custom-3.0.1.tgz#41ced07dc35e0442ecc4ec263e304b181f576e8e"
|
||||
integrity sha512-TmsO8iKsOZPFigwEoJ2xxwzk7w0qT26QbKldmKepV1Y9LZHjWaSjjHEJpSlpzgU1/5FxOhaeQmTJTzWHMrvcUQ==
|
||||
"@buffetjs/custom@3.0.2":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@buffetjs/custom/-/custom-3.0.2.tgz#6bf426b61e98fc8455a225a66657cb055864fd69"
|
||||
integrity sha512-UWuYxusniCih9TM/zfJHjcWiq8qIgto4cJ0NHQHNReDfpNG0DWvbZBrg8yqABrcve8yRBoYVrNiXs9AoapRM/Q==
|
||||
dependencies:
|
||||
"@buffetjs/core" "3.0.1"
|
||||
"@buffetjs/styles" "3.0.0"
|
||||
"@buffetjs/utils" "3.0.0"
|
||||
"@buffetjs/core" "3.0.2"
|
||||
"@buffetjs/styles" "3.0.2"
|
||||
"@buffetjs/utils" "3.0.2"
|
||||
moment "^2.24.0"
|
||||
react-moment-proptypes "^1.7.0"
|
||||
|
||||
"@buffetjs/hooks@3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@buffetjs/hooks/-/hooks-3.0.0.tgz#998a502a9acc419b49d0e437d60bc5d6df4727c5"
|
||||
integrity sha512-4fi0DTgDElk5cKr7Z5jmRneasioQVUEWsv2oTXH+HA0IPiZS7Ob1dUcPsRltYcOFg/VygGHmbs0VPb6NCtl6Qw==
|
||||
"@buffetjs/hooks@3.0.2":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@buffetjs/hooks/-/hooks-3.0.2.tgz#8c2530f38124c249606b5dc76bf110890c74cd43"
|
||||
integrity sha512-Sf4PFGXM6CB/lB1qwDWwEyu75j6wsMJsuBheYhPeBHHnQgFS2v5gx3PaC+1Xm8DRxilsY6r6rT+07jntg67mKg==
|
||||
|
||||
"@buffetjs/icons@3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@buffetjs/icons/-/icons-3.0.0.tgz#7c210df2cc741a303f3250bbac3e877da3d8bdad"
|
||||
integrity sha512-6SFbizWbsTFzjTfIAK1/9PsnTJ3L5NwA3fqcgIZpGxSIdWH9+RIJXOPUfWaqFVpq9BChlul6HludcuZQex0Ceg==
|
||||
"@buffetjs/icons@3.0.2":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@buffetjs/icons/-/icons-3.0.2.tgz#65b4c98b7f739d3a66f275c32d9bd31b88c953fc"
|
||||
integrity sha512-aYV5drQNfzQk5RzZ7Gwgd4EmorEwJTjHv4lKqFRmEm20URPRsQkDhApE7IENKMw/G+hhKUJHXnAKQxgW8lkGHw==
|
||||
|
||||
"@buffetjs/styles@3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@buffetjs/styles/-/styles-3.0.0.tgz#0c453adf264cac62404829d363a8daf953f1b79f"
|
||||
integrity sha512-HSaQkYhkWRKIZq9vHnXrG+oBe43xw73BJd5DfmUdkDXPMQe6x5tkBZf4OhHtUlgemjuamiuR5qw4OYl+mmEp9Q==
|
||||
"@buffetjs/styles@3.0.2":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@buffetjs/styles/-/styles-3.0.2.tgz#9f800460f95b31bdd50832104888d7c9f0973dcc"
|
||||
integrity sha512-dKiNIEmN+eDxoRHU3IxcKgfcKa8C6T74yXekbTEtzUeBSMIly4BbTa2vVAKR89aSXeEg9KhG+F+9brlbi71BOA==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-free" "^5.12.0"
|
||||
"@fortawesome/fontawesome-svg-core" "^1.2.22"
|
||||
@ -896,10 +896,10 @@
|
||||
"@fortawesome/react-fontawesome" "^0.1.4"
|
||||
react-dates "^21.1.0"
|
||||
|
||||
"@buffetjs/utils@3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@buffetjs/utils/-/utils-3.0.0.tgz#ed78f60996c7486a745bd87669dd8f78add03901"
|
||||
integrity sha512-xO/CQIRyYkWcMuHmGuullpJVUEUhXLq2pF46+x27XXxD3OzuDnoJQP6rKnqi4p2sZOeQSMTgo3yXBA+ZcSJLeA==
|
||||
"@buffetjs/utils@3.0.2":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@buffetjs/utils/-/utils-3.0.2.tgz#dd18a85f50e1668a3e48f4533bd0c677bd026171"
|
||||
integrity sha512-QUTFncBU0hViTDYfwaRi8aQvL/2hDBPStqtVOOe+MuhkeTlv8t1mdu51nA0+X7Z9Q6ygEpboaZT+N0QiBi0V2A==
|
||||
dependencies:
|
||||
yup "^0.27.0"
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user