mirror of
https://github.com/strapi/strapi.git
synced 2025-11-02 02:44:55 +00:00
Merge branch 'master' into doc-poly-update
This commit is contained in:
commit
b2d545edac
@ -9,6 +9,7 @@ import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { isFunction, isObject } from 'lodash';
|
||||
import cn from 'classnames';
|
||||
import LoadingBar from 'components/LoadingBar';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
@ -27,6 +28,8 @@ function Sub({ bordered, content, link, name, style, title, underline }) {
|
||||
return (
|
||||
<a className={cn(styles.subWrapper, bordered && styles.subBordered, styles.link)} href={`https://blog.strapi.io/${link}`} target="_blank">
|
||||
<span>{title}</span>
|
||||
{title === '' && <LoadingBar />}
|
||||
{content === '' && <LoadingBar style={{ width: '40%' }} />}
|
||||
<p style={style}>
|
||||
{isFunction(content) ? content() : content}
|
||||
</p>
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import SupportUsTitle from 'components/SupportUsTitle/Loadable';
|
||||
import SupportUsCta from 'components/SupportUsCta/Loadable';
|
||||
import SupportUsTitle from 'components/SupportUsTitle';
|
||||
import SupportUsCta from 'components/SupportUsCta';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
|
||||
@ -185,6 +185,10 @@ export class AdminPage extends React.Component {
|
||||
const header = this.showLeftMenu() ? <Header /> : '';
|
||||
const style = this.showLeftMenu() ? {} : { width: '100%' };
|
||||
|
||||
if (adminPage.isLoading) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.adminPage}>
|
||||
{this.showLeftMenu() && (
|
||||
|
||||
@ -16,6 +16,7 @@ import {
|
||||
const initialState = fromJS({
|
||||
allowGa: true,
|
||||
currentEnvironment: 'development',
|
||||
isLoading: true,
|
||||
layout: Map({}),
|
||||
strapiVersion: '3',
|
||||
});
|
||||
@ -23,7 +24,9 @@ const initialState = fromJS({
|
||||
function adminPageReducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case GET_CURR_ENV_SUCCEEDED:
|
||||
return state.update('currentEnvironment', () => action.currentEnvironment);
|
||||
return state
|
||||
.update('isLoading', () => false)
|
||||
.update('currentEnvironment', () => action.currentEnvironment);
|
||||
case GET_GA_STATUS_SUCCEEDED:
|
||||
return state.update('allowGa', () => action.allowGa);
|
||||
case GET_LAYOUT_SUCCEEDED:
|
||||
|
||||
@ -3,9 +3,9 @@
|
||||
*/
|
||||
import Loadable from 'react-loadable';
|
||||
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import LoadingIndicatorPage from 'components/LoadingIndicatorPage';
|
||||
|
||||
export default Loadable({
|
||||
loader: () => import('./index'),
|
||||
loading: LoadingIndicator,
|
||||
loading: LoadingIndicatorPage,
|
||||
});
|
||||
|
||||
@ -36,15 +36,27 @@ function getSrc(name) {
|
||||
}
|
||||
}
|
||||
|
||||
function SocialLink({ link, name }) {
|
||||
return (
|
||||
<div className={cn(styles.socialLink, 'col-md-6 col-lg-6')}>
|
||||
<a href={link} target="_blank">
|
||||
<div><img src={getSrc(name)} /></div>
|
||||
<span>{name}</span>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
class SocialLink extends React.PureComponent {
|
||||
state = { imgLoaded: false };
|
||||
|
||||
handleImgLoaded = () => this.setState({ imgLoaded: true });
|
||||
|
||||
render() {
|
||||
const { link, name } = this.props;
|
||||
const { imgLoaded } = this.state;
|
||||
|
||||
return (
|
||||
<div className={cn(styles.socialLink, 'col-md-6 col-lg-6')}>
|
||||
<a href={link} target="_blank">
|
||||
<div>
|
||||
{!imgLoaded && <div className={styles.spinner}><div /></div>}
|
||||
<img src={getSrc(name)} onLoad={this.handleImgLoaded} />
|
||||
</div>
|
||||
<span>{name}</span>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SocialLink.propTypes = {
|
||||
|
||||
@ -14,12 +14,12 @@ import PropTypes from 'prop-types';
|
||||
import { get, isEmpty, upperFirst } from 'lodash';
|
||||
import cn from 'classnames';
|
||||
|
||||
import Block from 'components/HomePageBlock/Loadable';
|
||||
import Block from 'components/HomePageBlock';
|
||||
import Button from 'components/Button';
|
||||
import Sub from 'components/Sub/Loadable';
|
||||
import Sub from 'components/Sub';
|
||||
import Input from 'components/InputText';
|
||||
import SupportUsCta from 'components/SupportUsCta/Loadable';
|
||||
import SupportUsTitle from 'components/SupportUsTitle/Loadable';
|
||||
import SupportUsCta from 'components/SupportUsCta';
|
||||
import SupportUsTitle from 'components/SupportUsTitle';
|
||||
|
||||
import { selectPlugins } from 'containers/App/selectors';
|
||||
|
||||
|
||||
@ -8,7 +8,10 @@ import { fromJS, List, Map } from 'immutable';
|
||||
import { GET_ARTICLES_SUCCEEDED, ON_CHANGE, SUBMIT_SUCCEEDED } from './constants';
|
||||
|
||||
const initialState = fromJS({
|
||||
articles: List([]),
|
||||
articles: List([
|
||||
{content: '', title: '', link: ''},
|
||||
{content: '', title: '', link: ''},
|
||||
]),
|
||||
body: Map({
|
||||
email: '',
|
||||
}),
|
||||
|
||||
@ -267,3 +267,23 @@
|
||||
max-width: 55rem !important;
|
||||
margin-bottom: 31px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
> div {
|
||||
border: 2px solid #f3f3f3; /* Light grey */
|
||||
border-top: 2px solid #3498db; /* Blue */
|
||||
border-radius: 50%;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import Loadable from 'react-loadable';
|
||||
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import LoadingIndicatorPage from 'components/LoadingIndicatorPage';
|
||||
|
||||
export default Loadable({
|
||||
loader: () => import('./index'),
|
||||
loading: LoadingIndicator,
|
||||
loading: LoadingIndicatorPage,
|
||||
});
|
||||
|
||||
@ -1,64 +0,0 @@
|
||||
{
|
||||
"availablePlugins": [
|
||||
{
|
||||
"description": "content-manager.plugin.description",
|
||||
"id": "content-manager",
|
||||
"icon": "plug",
|
||||
"isCompatible": true,
|
||||
"name": "Content Manager",
|
||||
"price": 0,
|
||||
"ratings": 1,
|
||||
"longDescription": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
|
||||
"downloads_nb": 3,
|
||||
"logo": "http://blog.sqreen.io/wp-content/uploads/2016/07/Sqreen-Logo-Vertical-1.png"
|
||||
},
|
||||
{
|
||||
"description": "content-type-builder.plugin.description",
|
||||
"id": "content-type-builder",
|
||||
"icon": "paint-brush",
|
||||
"isCompatible": true,
|
||||
"name": "content type builder",
|
||||
"price": 0,
|
||||
"ratings": 4.4,
|
||||
"longDescription": "lorem",
|
||||
"downloads_nb": 3,
|
||||
"logo": "http://blog.sqreen.io/wp-content/uploads/2016/07/Sqreen-Logo-Vertical-1.png"
|
||||
},
|
||||
{
|
||||
"description": "settings-manager.plugin.description",
|
||||
"id": "settings-manager",
|
||||
"icon": "wrench",
|
||||
"isCompatible": true,
|
||||
"name": "settings manager",
|
||||
"price": 0,
|
||||
"ratings": 2.6,
|
||||
"longDescription": "lorem",
|
||||
"downloads_nb": 3,
|
||||
"logo": "http://blog.sqreen.io/wp-content/uploads/2016/07/Sqreen-Logo-Vertical-1.png"
|
||||
},
|
||||
{
|
||||
"description": "users-permissions.plugin.description",
|
||||
"id": "users-permissions",
|
||||
"icon": "users",
|
||||
"isCompatible": true,
|
||||
"name": "Auth & Permissions",
|
||||
"price": 0,
|
||||
"ratings": 1,
|
||||
"longDescription": "lorem",
|
||||
"downloads_nb": 3,
|
||||
"logo": "http://blog.sqreen.io/wp-content/uploads/2016/07/Sqreen-Logo-Vertical-1.png"
|
||||
},
|
||||
{
|
||||
"description": "users-permissions.plugin.description",
|
||||
"id": "test",
|
||||
"icon": "users",
|
||||
"isCompatible": true,
|
||||
"name": "test",
|
||||
"price": 0,
|
||||
"ratings": 1,
|
||||
"longDescription": "lorem",
|
||||
"downloads_nb": 3,
|
||||
"logo": "http://blog.sqreen.io/wp-content/uploads/2016/07/Sqreen-Logo-Vertical-1.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -24,7 +24,8 @@ import DownloadInfo from 'components/DownloadInfo';
|
||||
import OverlayBlocker from 'components/OverlayBlocker';
|
||||
import PluginCard from 'components/PluginCard';
|
||||
import PluginHeader from 'components/PluginHeader';
|
||||
import SupportUsBanner from 'components/SupportUsBanner/Loadable';
|
||||
import SupportUsBanner from 'components/SupportUsBanner';
|
||||
import LoadingIndicatorPage from 'components/LoadingIndicatorPage';
|
||||
|
||||
import injectSaga from 'utils/injectSaga';
|
||||
import injectReducer from 'utils/injectReducer';
|
||||
@ -64,6 +65,10 @@ export class InstallPluginPage extends React.Component { // eslint-disable-line
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.props.didFetchPlugins) {
|
||||
return <LoadingIndicatorPage />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<OverlayBlocker isOpen={this.props.blockApp}>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import Loadable from 'react-loadable';
|
||||
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import LoadingIndicatorPage from 'components/LoadingIndicatorPage';
|
||||
|
||||
export default Loadable({
|
||||
loader: () => import('./index'),
|
||||
loading: LoadingIndicator,
|
||||
loading: LoadingIndicatorPage,
|
||||
});
|
||||
|
||||
@ -15,10 +15,11 @@ import cn from 'classnames';
|
||||
|
||||
import PluginHeader from 'components/PluginHeader';
|
||||
import ListPlugins from 'components/ListPlugins';
|
||||
import LoadingIndicatorPage from 'components/LoadingIndicatorPage';
|
||||
|
||||
import injectSaga from 'utils/injectSaga';
|
||||
import injectReducer from 'utils/injectReducer';
|
||||
import { makeSelectCurrentEnv, makeSelectPluginDeleteAction, makeSelectPlugins } from './selectors';
|
||||
import { makeSelectCurrentEnv, makeSelectPluginDeleteAction, makeSelectPlugins, makeSelectIsLoading } from './selectors';
|
||||
import { getPlugins, onDeletePluginClick, onDeletePluginConfirm } from './actions';
|
||||
import reducer from './reducer';
|
||||
import saga from './saga';
|
||||
@ -36,6 +37,10 @@ export class ListPluginsPage extends React.Component { // eslint-disable-line re
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.isLoading) {
|
||||
return <LoadingIndicatorPage />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormattedMessage id="app.components.ListPluginsPage.helmet.title">
|
||||
@ -79,6 +84,7 @@ ListPluginsPage.propTypes = {
|
||||
currentEnvironment: PropTypes.string.isRequired,
|
||||
getPlugins: PropTypes.func.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
onDeletePluginClick: PropTypes.func.isRequired,
|
||||
onDeletePluginConfirm: PropTypes.func.isRequired,
|
||||
pluginActionSucceeded: PropTypes.bool.isRequired,
|
||||
@ -87,6 +93,7 @@ ListPluginsPage.propTypes = {
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentEnvironment: makeSelectCurrentEnv(),
|
||||
isLoading: makeSelectIsLoading(),
|
||||
pluginActionSucceeded: makeSelectPluginDeleteAction(),
|
||||
plugins: makeSelectPlugins(),
|
||||
});
|
||||
|
||||
@ -15,6 +15,7 @@ import {
|
||||
const initialState = fromJS({
|
||||
currentEnvironment: 'development',
|
||||
deleteActionSucceeded: false,
|
||||
isLoading: true,
|
||||
plugins: Map({}),
|
||||
pluginToDelete: '',
|
||||
});
|
||||
@ -35,7 +36,8 @@ function listPluginsPageReducer(state = initialState, action) {
|
||||
return state.update('currentEnvironment', () => action.currentEnvironment);
|
||||
case GET_PLUGINS_SUCCEEDED:
|
||||
return state
|
||||
.set('plugins', Map(action.plugins));
|
||||
.set('plugins', Map(action.plugins))
|
||||
.update('isLoading', () => false);
|
||||
case ON_DELETE_PLUGIN_CLICK:
|
||||
return state
|
||||
.set('pluginToDelete', action.pluginToDelete);
|
||||
|
||||
@ -39,9 +39,15 @@ const makeSelectCurrentEnv = () => createSelector(
|
||||
(substate) => substate.get('currentEnvironment'),
|
||||
);
|
||||
|
||||
const makeSelectIsLoading = () => createSelector(
|
||||
selectListPluginsPageDomain(),
|
||||
(substate) => substate.get('isLoading'),
|
||||
);
|
||||
|
||||
export default makeSelectListPluginsPage;
|
||||
export {
|
||||
makeSelectCurrentEnv,
|
||||
makeSelectIsLoading,
|
||||
selectListPluginsPageDomain,
|
||||
makeSelectPluginToDelete,
|
||||
makeSelectPluginDeleteAction,
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import Loadable from 'react-loadable';
|
||||
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import LoadingIndicatorPage from 'components/LoadingIndicatorPage';
|
||||
|
||||
export default Loadable({
|
||||
loader: () => import('./index'),
|
||||
loading: LoadingIndicator,
|
||||
loading: LoadingIndicatorPage,
|
||||
});
|
||||
|
||||
@ -10,13 +10,18 @@
|
||||
import './public-path.js'; // eslint-disable-line import/extensions
|
||||
|
||||
import React from 'react';
|
||||
import Loadable from 'react-loadable';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import App from 'containers/App'; // eslint-disable-line
|
||||
|
||||
import LoadingIndicatorPage from 'components/LoadingIndicatorPage';
|
||||
import configureStore from './store';
|
||||
import { translationMessages } from './i18n';
|
||||
|
||||
|
||||
const LoadableApp = Loadable({
|
||||
loader: () => import('containers/App'),
|
||||
loading: LoadingIndicatorPage,
|
||||
});
|
||||
|
||||
const tryRequireRoot = (source) => {
|
||||
try {
|
||||
return require('../../../../admin/src/' + source + '.js').default; // eslint-disable-line prefer-template
|
||||
@ -62,7 +67,7 @@ const store = configureStore({}, strapi.router, pluginName);
|
||||
function Comp(props) {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<App {...props} />
|
||||
<LoadableApp {...props} />
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { isEmpty, isObject, merge } from 'lodash';
|
||||
import Loadable from 'react-loadable';
|
||||
|
||||
// Design
|
||||
import InputAddonWithErrors from 'components/InputAddonWithErrors';
|
||||
import InputCheckboxWithErrors from 'components/InputCheckboxWithErrors';
|
||||
@ -21,13 +21,8 @@ import InputPasswordWithErrors from 'components/InputPasswordWithErrors';
|
||||
import InputTextAreaWithErrors from 'components/InputTextAreaWithErrors';
|
||||
import InputTextWithErrors from 'components/InputTextWithErrors';
|
||||
import InputToggleWithErrors from 'components/InputToggleWithErrors';
|
||||
import WysiwygWithErrors from 'components/WysiwygWithErrors';
|
||||
import InputJSONWithErrors from 'components/InputJSONWithErrors';
|
||||
// import WysiwygWithErrors from 'components/WysiwygWithErrors';
|
||||
const Loading = () => <div>Loading ...</div>;
|
||||
const LoadableWysiwyg = Loadable({
|
||||
loader: () => import('components/WysiwygWithErrors'),
|
||||
loading: Loading,
|
||||
});
|
||||
|
||||
const DefaultInputError = ({ type }) => <div>Your input type: <b>{type}</b> does not exist</div>;
|
||||
|
||||
@ -46,7 +41,7 @@ const inputs = {
|
||||
text: InputTextWithErrors,
|
||||
textarea: InputTextAreaWithErrors,
|
||||
toggle: InputToggleWithErrors,
|
||||
wysiwyg: LoadableWysiwyg,
|
||||
wysiwyg: WysiwygWithErrors,
|
||||
};
|
||||
|
||||
function InputsIndex(props) {
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
const LoadingBar = ({ style }) => <div className={styles.loaderBar} style={style} />;
|
||||
|
||||
LoadingBar.defaultProps = {
|
||||
style: {},
|
||||
};
|
||||
|
||||
LoadingBar.propTypes = {
|
||||
style: PropTypes.object,
|
||||
};
|
||||
|
||||
export default LoadingBar;
|
||||
@ -0,0 +1,28 @@
|
||||
.loaderBar {
|
||||
height: 6px;
|
||||
width: 20%;
|
||||
margin-top: 10px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-color: #F3F3F4;
|
||||
border-radius: 2px;
|
||||
&:before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
content: '';
|
||||
left: -200px;
|
||||
width: 200px;
|
||||
height: 6px;
|
||||
background-color: rgb(227, 227, 231);
|
||||
animation: loading 2s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
from {left: -200px; width: 30%;}
|
||||
50% {width: 30%;}
|
||||
70% {width: 70%;}
|
||||
80% { left: 50%;}
|
||||
95% {left: 120%;}
|
||||
to {left: 100%;}
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Div = styled.div``;
|
||||
|
||||
export default Div;
|
||||
@ -6,8 +6,12 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Div from './Div';
|
||||
import styles from './styles.scss';
|
||||
|
||||
const LoadingIndicator = () => <Div>Loading....</Div>;
|
||||
const LoadingIndicator = () => {
|
||||
return (
|
||||
<div className={styles.loader}><div /></div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoadingIndicator;
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
.loader {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
> div {
|
||||
border: 4px solid #f3f3f3; /* Light grey */
|
||||
border-top: 4px solid #555555 !important; /* Blue */
|
||||
border-radius: 50%;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
/**
|
||||
*
|
||||
* LoadingIndicatorPage
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
const LoadingIndicatorPage = () => {
|
||||
return (
|
||||
<div className={styles.loaderPage}><div /></div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoadingIndicatorPage;
|
||||
@ -0,0 +1,21 @@
|
||||
.loaderPage {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
> div {
|
||||
margin: auto;
|
||||
border: 6px solid #f3f3f3; /* Light grey */
|
||||
border-top: 6px solid #1C91E7; /* Blue */
|
||||
border-radius: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
import Loadable from 'react-loadable';
|
||||
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
|
||||
export default Loadable({
|
||||
loader: () => import('./index'),
|
||||
loading: LoadingIndicator,
|
||||
});
|
||||
@ -13,13 +13,14 @@ import PluginHeaderActions from 'components/PluginHeaderActions';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
function PluginHeader({ actions, description, overrideRendering, subActions, title }) {
|
||||
function PluginHeader({ actions, description, overrideRendering, subActions, title, withDescriptionAnim }) {
|
||||
return (
|
||||
<div className={cn(styles.pluginHeader, 'row')}>
|
||||
<div className="col-lg-7">
|
||||
<PluginHeaderTitle
|
||||
title={title}
|
||||
description={description}
|
||||
withDescriptionAnim={withDescriptionAnim}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-lg-2 justify-content-end">
|
||||
@ -43,6 +44,7 @@ PluginHeader.defaultProps = {
|
||||
overrideRendering: false,
|
||||
subActions: [],
|
||||
title: '',
|
||||
withDescriptionAnim: false,
|
||||
};
|
||||
|
||||
PluginHeader.propTypes = {
|
||||
@ -68,6 +70,7 @@ PluginHeader.propTypes = {
|
||||
values: PropTypes.object,
|
||||
}),
|
||||
]),
|
||||
withDescriptionAnim: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default PluginHeader;
|
||||
|
||||
@ -9,9 +9,11 @@ import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { isFunction, isObject } from 'lodash';
|
||||
|
||||
import LoadingBar from 'components/LoadingBar';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
function PluginHeaderTitle({ description, title }) {
|
||||
function PluginHeaderTitle({ description, title, withDescriptionAnim }) {
|
||||
const contentTitle = formatData(title);
|
||||
const contentDescription = formatData(description);
|
||||
|
||||
@ -20,9 +22,15 @@ function PluginHeaderTitle({ description, title }) {
|
||||
<h1 className={styles.pluginHeaderTitleName}>
|
||||
{contentTitle}
|
||||
</h1>
|
||||
<p className={styles.pluginHeaderTitleDescription}>
|
||||
{contentDescription}
|
||||
</p>
|
||||
{ withDescriptionAnim ?
|
||||
(
|
||||
<LoadingBar />
|
||||
) : (
|
||||
<p className={styles.pluginHeaderTitleDescription}>
|
||||
{contentDescription}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -43,6 +51,7 @@ const formatData = (data) => {
|
||||
PluginHeaderTitle.defaultProps = {
|
||||
description: '',
|
||||
title: '',
|
||||
withDescriptionAnim: false,
|
||||
};
|
||||
|
||||
PluginHeaderTitle.propTypes = {
|
||||
@ -62,6 +71,7 @@ PluginHeaderTitle.propTypes = {
|
||||
values: PropTypes.object,
|
||||
}),
|
||||
]),
|
||||
withDescriptionAnim: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default PluginHeaderTitle;
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
import Loadable from 'react-loadable';
|
||||
import Loader from './Loader';
|
||||
|
||||
export default Loadable({
|
||||
loader: () => import('./index'),
|
||||
loading: Loader,
|
||||
});
|
||||
@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
const Loader = () => (
|
||||
<div className="col-md-12">
|
||||
<div className={styles.wysLoader}><div /></div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Loader;
|
||||
@ -3,3 +3,22 @@
|
||||
font-size: 1.3rem;
|
||||
font-family: 'Lato';
|
||||
}
|
||||
|
||||
.wysLoader {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
> div {
|
||||
border: 4px solid #f3f3f3; /* Light grey */
|
||||
border-top: 4px solid #3498db; /* Blue */
|
||||
border-radius: 50%;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
@ -12,22 +12,29 @@ import PropTypes from 'prop-types';
|
||||
import Button from 'components/CustomButton';
|
||||
import Logo from '../../assets/images/icon_filter.png';
|
||||
|
||||
const imgStyle = {
|
||||
marginTop: '-3px',
|
||||
marginRight: '10px',
|
||||
height: '7px',
|
||||
fontSize: '12px',
|
||||
};
|
||||
import styles from './styles.scss';
|
||||
|
||||
function AddFilterCTA({ onClick, showHideText }) {
|
||||
const id = showHideText ? 'hide' : 'add';
|
||||
class AddFilterCTA extends React.Component {
|
||||
state = { imgLoaded: false };
|
||||
|
||||
return (
|
||||
<Button type="button" onClick={onClick} marginTop>
|
||||
<img src={Logo} alt="filter_logo" style={imgStyle} />
|
||||
<FormattedMessage id={`content-manager.components.AddFilterCTA.${id}`} />
|
||||
</Button>
|
||||
);
|
||||
handleImgLoaded = () => this.setState({ imgLoaded: true });
|
||||
|
||||
render() {
|
||||
const { onClick, showHideText } = this.props;
|
||||
const { imgLoaded } = this.state;
|
||||
const id = showHideText ? 'hide' : 'add';
|
||||
|
||||
return (
|
||||
<Button type="button" onClick={onClick} marginTop>
|
||||
<div className={styles.ctaWrapper}>
|
||||
{!imgLoaded && <div className={styles.spinner}><div /></div>}
|
||||
<img src={Logo} onLoad={this.handleImgLoaded} alt="filter_logo" className={styles.imgCta} />
|
||||
<FormattedMessage id={`content-manager.components.AddFilterCTA.${id}`} />
|
||||
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AddFilterCTA.defaultProps = {
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
.spinner {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
> div {
|
||||
border: 2px solid #f3f3f3; /* Light grey */
|
||||
border-top: 2px solid #3498db; /* Blue */
|
||||
border-radius: 50%;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.imgCta {
|
||||
height: 7px;
|
||||
margin: auto;
|
||||
margin-right: 0px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ctaWrapper {
|
||||
display: flex;
|
||||
> span {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
@ -17,6 +17,9 @@ const CustomButton = styled.button`
|
||||
&:hover {
|
||||
background: #F7F8F8;
|
||||
}
|
||||
&:focus, &:active {
|
||||
outline:0;
|
||||
}
|
||||
`;
|
||||
|
||||
export default CustomButton;
|
||||
|
||||
@ -12,6 +12,7 @@ import TableDelete from 'components/TableDelete';
|
||||
import TableHeader from 'components/TableHeader';
|
||||
import TableRow from 'components/TableRow';
|
||||
import TableEmpty from 'components/TableEmpty';
|
||||
import TableLoading from 'components/TableLoading';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
@ -61,7 +62,7 @@ class Table extends React.Component {
|
||||
onToggleDeleteAll={this.props.onToggleDeleteAll}
|
||||
/>
|
||||
)}
|
||||
{rows}
|
||||
{this.props.showLoader ? <TableLoading colspan={this.props.headers.length + 1} /> : rows}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
@ -76,6 +77,7 @@ Table.defaultProps = {
|
||||
entriesToDelete: [],
|
||||
handleDelete: () => {},
|
||||
search: '',
|
||||
showLoader: false,
|
||||
};
|
||||
|
||||
Table.propTypes = {
|
||||
@ -98,6 +100,7 @@ Table.propTypes = {
|
||||
route: PropTypes.object.isRequired,
|
||||
routeParams: PropTypes.object.isRequired,
|
||||
search: PropTypes.string,
|
||||
showLoader: PropTypes.bool,
|
||||
sort: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
/**
|
||||
*
|
||||
* TableLoading
|
||||
*/
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
function TableLoading({ colspan }) {
|
||||
return (
|
||||
<tr className={styles.tableLoading}>
|
||||
<td colSpan={colspan + 1}>
|
||||
<LoadingIndicator />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
TableLoading.propTypes = {
|
||||
colspan: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
export default TableLoading;
|
||||
@ -0,0 +1,17 @@
|
||||
.tableLoading { /* stylelint-disable */
|
||||
width: 100%;
|
||||
height: 108px;
|
||||
background: #ffffff;
|
||||
|
||||
td{
|
||||
height: 106px;
|
||||
vertical-align: middle;
|
||||
line-height: 106px;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 400;
|
||||
color: #333740;
|
||||
text-align: center;
|
||||
border-collapse: collapse;
|
||||
border-top: 1px solid #F1F1F2 !important;
|
||||
}
|
||||
}
|
||||
@ -19,10 +19,10 @@ import getQueryParameters from 'utils/getQueryParameters';
|
||||
import Home from 'containers/Home';
|
||||
import EditPage from 'containers/EditPage';
|
||||
import ListPage from 'containers/ListPage';
|
||||
import LoadingIndicatorPage from 'components/LoadingIndicatorPage';
|
||||
import EmptyAttributesView from 'components/EmptyAttributesView';
|
||||
|
||||
import {
|
||||
emptyStore,
|
||||
loadModels,
|
||||
} from './actions';
|
||||
import { makeSelectLoading, makeSelectModels, makeSelectModelEntries } from './selectors';
|
||||
@ -34,13 +34,9 @@ class App extends React.Component {
|
||||
this.props.loadModels();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.emptyStore();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.loading) {
|
||||
return <div />;
|
||||
return <LoadingIndicatorPage />;
|
||||
}
|
||||
|
||||
const currentModelName = this.props.location.pathname.split('/')[3];
|
||||
@ -69,7 +65,6 @@ App.contextTypes = {
|
||||
};
|
||||
|
||||
App.propTypes = {
|
||||
emptyStore: PropTypes.func.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
loading: PropTypes.bool.isRequired,
|
||||
loadModels: PropTypes.func.isRequired,
|
||||
@ -84,8 +79,6 @@ App.propTypes = {
|
||||
export function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators(
|
||||
{
|
||||
emptyStore,
|
||||
// getModelEntries,
|
||||
loadModels,
|
||||
},
|
||||
dispatch,
|
||||
|
||||
@ -19,7 +19,7 @@ const initialState = fromJS({
|
||||
function appReducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case EMPTY_STORE:
|
||||
return initialState;
|
||||
return state;
|
||||
case GET_MODEL_ENTRIES_SUCCEEDED:
|
||||
return state.set('modelEntries', action.count);
|
||||
case LOAD_MODELS:
|
||||
|
||||
@ -17,6 +17,7 @@ import cn from 'classnames';
|
||||
// ./node_modules/strapi-helper-plugin/lib/src
|
||||
// or strapi/packages/strapi-helper-plugin/lib/src
|
||||
import BackHeader from 'components/BackHeader';
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import PluginHeader from 'components/PluginHeader';
|
||||
|
||||
// Plugin's components
|
||||
@ -108,7 +109,7 @@ export class EditPage extends React.Component {
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
getLayout = () => (
|
||||
bindLayout.call(this, this.props.editPage.layout)
|
||||
bindLayout.call(this, get(this.props.editPage, ['layout', this.getModelName()], {}))
|
||||
)
|
||||
|
||||
/**
|
||||
@ -150,6 +151,14 @@ export class EditPage extends React.Component {
|
||||
get(this.props.schema, ['plugins', this.getSource(), this.getModelName()])
|
||||
: get(this.props.schema, [this.getModelName()]);
|
||||
|
||||
getPluginHeaderTitle = () => {
|
||||
if (this.isCreating()) {
|
||||
return toString(this.props.editPage.pluginHeaderTitle);
|
||||
}
|
||||
|
||||
return this.props.match.params.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the model's source
|
||||
* @return {String}
|
||||
@ -236,6 +245,7 @@ export class EditPage extends React.Component {
|
||||
kind: 'secondary',
|
||||
onClick: this.props.onCancel,
|
||||
type: 'button',
|
||||
disabled: this.showLoaders(),
|
||||
},
|
||||
{
|
||||
kind: 'primary',
|
||||
@ -244,10 +254,17 @@ export class EditPage extends React.Component {
|
||||
type: 'submit',
|
||||
loader: this.props.editPage.showLoader,
|
||||
style: this.props.editPage.showLoader ? { marginRight: '18px' } : {},
|
||||
disabled: this.showLoaders(),
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
showLoaders = () => {
|
||||
const { editPage: { isLoading, layout } } = this.props;
|
||||
|
||||
return isLoading && !this.isCreating() || isLoading && get(layout, this.getModelName()) === undefined;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { editPage } = this.props;
|
||||
|
||||
@ -258,24 +275,28 @@ export class EditPage extends React.Component {
|
||||
<div className={cn('container-fluid', styles.containerFluid)}>
|
||||
<PluginHeader
|
||||
actions={this.pluginHeaderActions()}
|
||||
title={{ id: toString(editPage.pluginHeaderTitle) }}
|
||||
title={{ id: this.getPluginHeaderTitle() }}
|
||||
/>
|
||||
<div className="row">
|
||||
<div className={this.isRelationComponentNull() ? 'col-lg-12' : 'col-lg-9'}>
|
||||
<div className={styles.main_wrapper}>
|
||||
<Edit
|
||||
attributes={this.getModelAttributes()}
|
||||
didCheckErrors={editPage.didCheckErrors}
|
||||
formValidations={editPage.formValidations}
|
||||
formErrors={editPage.formErrors}
|
||||
layout={this.getLayout()}
|
||||
modelName={this.getModelName()}
|
||||
onBlur={this.handleBlur}
|
||||
onChange={this.handleChange}
|
||||
record={editPage.record}
|
||||
resetProps={editPage.resetProps}
|
||||
schema={this.getSchema()}
|
||||
/>
|
||||
{this.showLoaders() ? (
|
||||
<LoadingIndicator />
|
||||
) : (
|
||||
<Edit
|
||||
attributes={this.getModelAttributes()}
|
||||
didCheckErrors={editPage.didCheckErrors}
|
||||
formValidations={editPage.formValidations}
|
||||
formErrors={editPage.formErrors}
|
||||
layout={this.getLayout()}
|
||||
modelName={this.getModelName()}
|
||||
onBlur={this.handleBlur}
|
||||
onChange={this.handleChange}
|
||||
record={editPage.record}
|
||||
resetProps={editPage.resetProps}
|
||||
schema={this.getSchema()}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!this.isRelationComponentNull() && (
|
||||
@ -360,3 +381,5 @@ export default compose(
|
||||
withSaga,
|
||||
withConnect,
|
||||
)(EditPage);
|
||||
|
||||
|
||||
|
||||
@ -27,7 +27,8 @@ const initialState = fromJS({
|
||||
isCreating: false,
|
||||
id: '',
|
||||
initialRecord: Map({}),
|
||||
layout: Map({}),
|
||||
isLoading: true,
|
||||
layout: fromJS({}),
|
||||
modelName: '',
|
||||
pluginHeaderTitle: 'New Entry',
|
||||
record: Map({}),
|
||||
@ -44,11 +45,14 @@ function editPageReducer(state = initialState, action) {
|
||||
case GET_DATA_SUCCEEDED:
|
||||
return state
|
||||
.update('id', () => action.id)
|
||||
.update('isLoading', () => false)
|
||||
.update('initialRecord', () => Map(action.data))
|
||||
.update('pluginHeaderTitle', () => action.pluginHeaderTitle)
|
||||
.update('record', () => Map(action.data));
|
||||
case GET_LAYOUT_SUCCEEDED:
|
||||
return state.update('layout', () => Map(action.layout));
|
||||
return state
|
||||
.update('isLoading', () => false)
|
||||
.updateIn(['layout', state.get('modelName')], () => Map(action.layout));
|
||||
case INIT_MODEL_PROPS:
|
||||
return state
|
||||
.update('formValidations', () => List(action.formValidations))
|
||||
@ -63,7 +67,7 @@ function editPageReducer(state = initialState, action) {
|
||||
.update('record', () => state.get('initialRecord'))
|
||||
.update('resetProps', (v) => v = !v);
|
||||
case RESET_PROPS:
|
||||
return initialState;
|
||||
return initialState.update('layout', () => state.get('layout'));
|
||||
case SET_FILE_RELATIONS:
|
||||
return state.set('fileRelations', List(action.fileRelations));
|
||||
case SET_FORM_ERRORS:
|
||||
|
||||
@ -72,10 +72,11 @@ export function deleteSeveralDataSuccess() {
|
||||
};
|
||||
}
|
||||
|
||||
export function getData(currentModel, source) {
|
||||
export function getData(currentModel, source, setUpdatingParams = false) {
|
||||
return {
|
||||
type: GET_DATA,
|
||||
currentModel,
|
||||
setUpdatingParams,
|
||||
source,
|
||||
};
|
||||
}
|
||||
|
||||
@ -87,7 +87,7 @@ export class ListPage extends React.Component {
|
||||
}
|
||||
|
||||
if (search !== this.props.location.search) {
|
||||
this.getData(this.props);
|
||||
this.getData(this.props, true);
|
||||
}
|
||||
|
||||
if (prevProps.listPage.filtersUpdated !== filtersUpdated) {
|
||||
@ -120,7 +120,7 @@ export class ListPage extends React.Component {
|
||||
* Function to fetch data
|
||||
* @param {Object} props
|
||||
*/
|
||||
getData = props => {
|
||||
getData = (props, setUpdatingParams = false) => {
|
||||
const source = getQueryParameters(props.location.search, 'source');
|
||||
const _limit = toInteger(getQueryParameters(props.location.search, '_limit')) || 10;
|
||||
const _page = toInteger(getQueryParameters(props.location.search, '_page')) || 1;
|
||||
@ -130,7 +130,7 @@ export class ListPage extends React.Component {
|
||||
const filters = generateFiltersFromSearch(props.location.search);
|
||||
|
||||
this.props.setParams(params, filters);
|
||||
this.props.getData(props.match.params.slug, source);
|
||||
this.props.getData(props.match.params.slug, source, setUpdatingParams);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -157,8 +157,9 @@ export class ListPage extends React.Component {
|
||||
* Generate the redirect URI when editing an entry
|
||||
* @type {String}
|
||||
*/
|
||||
generateRedirectURI = () =>
|
||||
`?redirectUrl=/plugins/content-manager/${this.getCurrentModelName().toLowerCase()}${this.generateSearch()}`;
|
||||
generateRedirectURI = () => (
|
||||
`?redirectUrl=/plugins/content-manager/${this.getCurrentModelName().toLowerCase()}${this.generateSearch()}`
|
||||
);
|
||||
|
||||
generateSearch = () => {
|
||||
const {
|
||||
@ -194,7 +195,7 @@ export class ListPage extends React.Component {
|
||||
areAllEntriesSelected = () => {
|
||||
const { listPage: { entriesToDelete, records } } = this.props;
|
||||
|
||||
return entriesToDelete.length === records.length && records.length > 0;
|
||||
return entriesToDelete.length === get(records, this.getCurrentModelName(), []).length && get(records, this.getCurrentModelName(), []).length > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -304,6 +305,12 @@ export class ListPage extends React.Component {
|
||||
|
||||
};
|
||||
|
||||
showLoaders = () => {
|
||||
const { listPage: { isLoading, records, updatingParams } } = this.props;
|
||||
|
||||
return updatingParams || isLoading && get(records, this.getCurrentModelName()) === undefined;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
addFilter,
|
||||
@ -311,9 +318,11 @@ export class ListPage extends React.Component {
|
||||
listPage,
|
||||
listPage: {
|
||||
appliedFilters,
|
||||
count,
|
||||
entriesToDelete,
|
||||
filters,
|
||||
filterToFocus,
|
||||
records,
|
||||
params,
|
||||
showFilter,
|
||||
showWarningDeleteAll,
|
||||
@ -356,16 +365,17 @@ export class ListPage extends React.Component {
|
||||
actions={pluginHeaderActions}
|
||||
description={{
|
||||
id:
|
||||
listPage.count > 1
|
||||
get(count, this.getCurrentModelName(), 0) > 1
|
||||
? 'content-manager.containers.List.pluginHeaderDescription'
|
||||
: 'content-manager.containers.List.pluginHeaderDescription.singular',
|
||||
values: {
|
||||
label: listPage.count,
|
||||
label: get(count, this.getCurrentModelName(), 0),
|
||||
},
|
||||
}}
|
||||
title={{
|
||||
id: this.getCurrentModelName() || 'Content Manager',
|
||||
}}
|
||||
withDescriptionAnim={this.showLoaders()}
|
||||
/>
|
||||
<div className={cn(styles.wrapper)}>
|
||||
<FiltersPickWrapper
|
||||
@ -416,11 +426,12 @@ export class ListPage extends React.Component {
|
||||
onClickSelect={onClickSelect}
|
||||
onToggleDeleteAll={onToggleDeleteAll}
|
||||
primaryKey={this.getCurrentModel().primaryKey || 'id'}
|
||||
records={listPage.records}
|
||||
records={get(records, this.getCurrentModelName(), [])}
|
||||
redirectUrl={this.generateRedirectURI()}
|
||||
route={this.props.match}
|
||||
routeParams={this.props.match.params}
|
||||
search={params._q}
|
||||
showLoader={this.showLoaders()}
|
||||
sort={params._sort}
|
||||
/>
|
||||
<PopUpWarning
|
||||
@ -450,7 +461,7 @@ export class ListPage extends React.Component {
|
||||
}}
|
||||
/>
|
||||
<PageFooter
|
||||
count={listPage.count}
|
||||
count={get(count, this.getCurrentModelName(), 0)}
|
||||
onChangeParams={this.handleChangeParams}
|
||||
params={listPage.params}
|
||||
style={{ marginTop: '2.9rem', padding: '0 15px 0 15px' }}
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
CHANGE_PARAMS,
|
||||
DELETE_DATA_SUCCESS,
|
||||
DELETE_SEVERAL_DATA_SUCCESS,
|
||||
GET_DATA,
|
||||
GET_DATA_SUCCEEDED,
|
||||
ON_CHANGE,
|
||||
ON_CLICK_REMOVE,
|
||||
@ -29,20 +30,23 @@ import {
|
||||
|
||||
const initialState = fromJS({
|
||||
appliedFilters: List([]),
|
||||
count: 0,
|
||||
count: fromJS({}),
|
||||
currentModel: '',
|
||||
entriesToDelete: List([]),
|
||||
filters: List([]),
|
||||
filtersUpdated: false,
|
||||
filterToFocus: null,
|
||||
isLoading: true,
|
||||
params: Map({
|
||||
_limit: 10,
|
||||
_page: 1,
|
||||
_sort: '',
|
||||
_q: '',
|
||||
}),
|
||||
records: List([]),
|
||||
records: fromJS({}),
|
||||
showFilter: false,
|
||||
showWarningDeleteAll: false,
|
||||
updatingParams: false,
|
||||
});
|
||||
|
||||
function listPageReducer(state = initialState, action) {
|
||||
@ -51,7 +55,7 @@ function listPageReducer(state = initialState, action) {
|
||||
return state.update('appliedFilters', list => list.push(Map(action.filter)));
|
||||
case DELETE_DATA_SUCCESS:
|
||||
return state
|
||||
.update('records', (list) => (
|
||||
.updateIn(['records', state.get('currentModel')], (list) => (
|
||||
list.filter(obj => {
|
||||
if (obj._id) {
|
||||
return obj._id !== action.id;
|
||||
@ -60,7 +64,7 @@ function listPageReducer(state = initialState, action) {
|
||||
return obj.id !== parseInt(action.id, 10);
|
||||
})
|
||||
))
|
||||
.update('count', (v) => v = v - 1);
|
||||
.updateIn(['count', state.get('currentModel')], v => v = v - 1);
|
||||
case DELETE_SEVERAL_DATA_SUCCESS:
|
||||
return state
|
||||
.update('showWarningDeleteAll', () => false)
|
||||
@ -81,13 +85,26 @@ function listPageReducer(state = initialState, action) {
|
||||
return !v;
|
||||
}
|
||||
|
||||
return v;
|
||||
})
|
||||
.update('updatingParams', () => true);
|
||||
case GET_DATA:
|
||||
return state
|
||||
.update('isLoading', () => true)
|
||||
.update('currentModel', () => action.currentModel)
|
||||
.update('updatingParams', v => {
|
||||
if (action.setUpdatingParams) {
|
||||
return true;
|
||||
}
|
||||
return v;
|
||||
});
|
||||
case GET_DATA_SUCCEEDED:
|
||||
return state
|
||||
.update('entriesToDelete', () => List([]))
|
||||
.update('count', () => action.data[0].count)
|
||||
.update('records', () => List(action.data[1]));
|
||||
.updateIn(['count', state.get('currentModel')], () => action.data[0].count)
|
||||
.update('isLoading', () => false)
|
||||
.updateIn(['records', state.get('currentModel')], () => List(action.data[1]))
|
||||
.update('updatingParams', () => false);
|
||||
case ON_CHANGE:
|
||||
return state.updateIn(['appliedFilters', action.index, action.key], () => action.value);
|
||||
case ON_CLICK_REMOVE:
|
||||
@ -109,7 +126,7 @@ function listPageReducer(state = initialState, action) {
|
||||
return state.update('entriesToDelete', () => {
|
||||
if (state.get('entriesToDelete').size === 0) {
|
||||
return state
|
||||
.get('records')
|
||||
.getIn(['records', state.get('currentModel')])
|
||||
.reduce((acc, current) => acc.concat(List([toString(current.id)])), List([]));
|
||||
}
|
||||
|
||||
|
||||
@ -99,6 +99,7 @@ export function* dataDelete({ id, modelName, source }) {
|
||||
export function* dataDeleteAll({ entriesToDelete, model, source }) {
|
||||
try {
|
||||
const params = Object.assign(entriesToDelete, source !== undefined ? { source } : {});
|
||||
|
||||
yield call(request, `/content-manager/explorer/deleteAll/${model}`, {
|
||||
method: 'DELETE',
|
||||
params,
|
||||
|
||||
@ -5,7 +5,7 @@ export default function checkAttributeValidations(errors) {
|
||||
const attributeIndex = split(this.props.hash, '::')[3];
|
||||
const sameAttributes = filter(this.props.contentTypeData.attributes, (attr) => attr.name === this.props.modifiedDataAttribute.name);
|
||||
const sameParamsKey = filter(this.props.contentTypeData.attributes, (attr) =>
|
||||
attr.params.key === this.props.modifiedDataAttribute.params.key && attr.params.target === this.props.modifiedDataAttribute.params.target);
|
||||
attr.params.key !== '-' && attr.params.key === this.props.modifiedDataAttribute.params.key && attr.params.target === this.props.modifiedDataAttribute.params.target);
|
||||
const sameParamsKeyAndName = filter(this.props.contentTypeData.attributes, (attr) => attr.name === this.props.modifiedDataAttribute.params.key);
|
||||
const formErrors = concat(errors, hasNestedValue(this.props.modifiedDataAttribute));
|
||||
const isEditingParamsKey = this.props.modifiedDataAttribute.params.key !== get(this.props.contentTypeData.attributes, [attributeIndex, 'params', 'key']);
|
||||
|
||||
@ -7,7 +7,9 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { get } from 'lodash';
|
||||
import cn from 'classnames';
|
||||
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import Input from 'components/InputsIndex';
|
||||
|
||||
import styles from './styles.scss';
|
||||
@ -25,6 +27,14 @@ class EditForm extends React.Component { // eslint-disable-line react/prefer-sta
|
||||
)
|
||||
|
||||
render() {
|
||||
if (this.props.showLoaders) {
|
||||
return (
|
||||
<div className={cn(styles.editForm, this.props.showLoaders && styles.loadIndicatorContainer)}>
|
||||
<LoadingIndicator />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.editForm}>
|
||||
<div className="row">
|
||||
@ -94,6 +104,7 @@ class EditForm extends React.Component { // eslint-disable-line react/prefer-sta
|
||||
|
||||
EditForm.propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
showLoaders: PropTypes.bool.isRequired,
|
||||
values: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@ -13,3 +13,11 @@
|
||||
margin-bottom: 2.1rem;
|
||||
background: #F6F6F6;
|
||||
}
|
||||
|
||||
.loadIndicatorContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
min-height: 209px;
|
||||
padding-top: 88px;
|
||||
}
|
||||
@ -8,6 +8,11 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { map, omitBy, size } from 'lodash';
|
||||
import cn from 'classnames';
|
||||
|
||||
// Components from strapi-helper-plugin
|
||||
import LoadingBar from 'components/LoadingBar';
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
|
||||
// Design
|
||||
import Button from 'components/Button';
|
||||
@ -48,14 +53,14 @@ const generateListTitle = (data, settingType) => {
|
||||
}
|
||||
};
|
||||
|
||||
function List({ data, deleteData, noButton, onButtonClick, settingType, values }) {
|
||||
function List({ data, deleteData, noButton, onButtonClick, settingType, showLoaders, values }) {
|
||||
const object = omitBy(data, (v) => v.name === 'server'); // Remove the server key when displaying providers
|
||||
|
||||
return (
|
||||
<div className={styles.list}>
|
||||
<div className={styles.flex}>
|
||||
<div className={styles.titleContainer}>
|
||||
{generateListTitle(data, settingType)}
|
||||
{showLoaders ? <LoadingBar style={{ marginTop: '0' }} /> : generateListTitle(data, settingType)}
|
||||
</div>
|
||||
<div className={styles.buttonContainer}>
|
||||
{noButton ? (
|
||||
@ -67,18 +72,20 @@ function List({ data, deleteData, noButton, onButtonClick, settingType, values }
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.ulContainer}>
|
||||
<ul className={noButton ? styles.listPadded : ''}>
|
||||
{map(object, item => (
|
||||
<ListRow
|
||||
deleteData={deleteData}
|
||||
item={item}
|
||||
key={item.name}
|
||||
settingType={settingType}
|
||||
values={values}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
<div className={cn(styles.ulContainer, showLoaders && styles.loadingContainer, showLoaders && settingType === 'roles' && styles.loadingContainerRole )}>
|
||||
{showLoaders ? <LoadingIndicator /> : (
|
||||
<ul className={noButton ? styles.listPadded : ''}>
|
||||
{map(object, item => (
|
||||
<ListRow
|
||||
deleteData={deleteData}
|
||||
item={item}
|
||||
key={item.name}
|
||||
settingType={settingType}
|
||||
values={values}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -87,6 +94,7 @@ function List({ data, deleteData, noButton, onButtonClick, settingType, values }
|
||||
List.defaultProps = {
|
||||
noButton: false,
|
||||
onButtonClick: () => {},
|
||||
showLoaders: true,
|
||||
};
|
||||
|
||||
List.propTypes = {
|
||||
@ -95,6 +103,7 @@ List.propTypes = {
|
||||
noButton: PropTypes.bool,
|
||||
onButtonClick: PropTypes.func,
|
||||
settingType: PropTypes.string.isRequired,
|
||||
showLoaders: PropTypes.bool,
|
||||
values: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@ -11,6 +11,8 @@
|
||||
}
|
||||
|
||||
.titleContainer {
|
||||
flex: 2;
|
||||
width: 20%;
|
||||
color: #333740;
|
||||
font-family: Lato;
|
||||
font-size: 1.8rem;
|
||||
@ -33,3 +35,16 @@
|
||||
.listPadded {
|
||||
padding-top: 3px !important;
|
||||
}
|
||||
|
||||
.loadingContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
min-height: 162px;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.loadingContainerRole {
|
||||
min-height: 142px !important;
|
||||
padding-top: 0;
|
||||
}
|
||||
@ -46,7 +46,7 @@ class Plugin extends React.Component { // eslint-disable-line react/prefer-state
|
||||
|
||||
render() {
|
||||
const divStyle = this.state.collapse ? { marginBottom: '.4rem' } : {};
|
||||
const icon = get(this.context.plugins.toJS(), [this.props.name, 'icon']);
|
||||
const icon = get(this.props.plugin, ['information', 'logo']);
|
||||
const emptyApplication = !isEmpty(get(this.props.plugin, 'controllers'));
|
||||
|
||||
if (!emptyApplication) {
|
||||
@ -57,11 +57,11 @@ class Plugin extends React.Component { // eslint-disable-line react/prefer-state
|
||||
<div className={styles.plugin} style={divStyle}>
|
||||
<div className={styles.banner} onClick={this.handleClick}>
|
||||
<div>
|
||||
{ icon ? (
|
||||
{this.props.name !== 'application' && (
|
||||
<div className={styles.iconContainer}>
|
||||
<img src={this.props.plugin.information.logo} alt="icon" />
|
||||
{icon && <img src={icon} alt="icon" />}
|
||||
</div>
|
||||
) : ''}
|
||||
)}
|
||||
<div className={styles.name}>{this.props.name}</div>
|
||||
—
|
||||
<div className={styles.description}>
|
||||
|
||||
@ -104,7 +104,7 @@ class PopUpForm extends React.Component { // eslint-disable-line react/prefer-st
|
||||
<Input
|
||||
inputDescription={{ id: 'users-permissions.PopUpForm.Providers.enabled.description' }}
|
||||
label={{ id: 'users-permissions.PopUpForm.Providers.enabled.label' }}
|
||||
name={`${dataToEdit}.enabled`}
|
||||
name={`${settingType}.${dataToEdit}.enabled`}
|
||||
onChange={this.handleChange}
|
||||
type="toggle"
|
||||
validations={{}}
|
||||
@ -121,7 +121,7 @@ class PopUpForm extends React.Component { // eslint-disable-line react/prefer-st
|
||||
errors={get(this.props.formErrors, [findIndex(this.props.formErrors, ['name', value]), 'errors'], [])}
|
||||
key={value}
|
||||
label={{ id: `users-permissions.PopUpForm.Providers.${ includes(value, 'callback') || includes(value, 'redirect_uri') ? 'redirectURL.front-end' : value}.label` }}
|
||||
name={`${dataToEdit}.${value}`}
|
||||
name={`${settingType}.${dataToEdit}.${value}`}
|
||||
onFocus={includes(value, 'callback') || includes(value, 'redirect_uri') ? this.handleFocus : () => {}}
|
||||
onBlur={includes(value, 'callback') || includes(value, 'redirect_uri') ? this.handleBlur : false}
|
||||
onChange={this.props.onChange}
|
||||
@ -163,7 +163,7 @@ class PopUpForm extends React.Component { // eslint-disable-line react/prefer-st
|
||||
didCheckErrors={this.props.didCheckErrors}
|
||||
errors={get(this.props.formErrors, [findIndex(this.props.formErrors, ['name', value]), 'errors'], [])}
|
||||
label={{ id: `users-permissions.PopUpForm.Email.${value}.label` }}
|
||||
name={`${dataToEdit}.${value}`}
|
||||
name={`${settingType}.${dataToEdit}.${value}`}
|
||||
onChange={this.props.onChange}
|
||||
placeholder={`users-permissions.PopUpForm.Email.${value}.placeholder`}
|
||||
type={includes(value, 'email') ? 'email' : 'text'}
|
||||
@ -179,7 +179,7 @@ class PopUpForm extends React.Component { // eslint-disable-line react/prefer-st
|
||||
didCheckErrors={this.props.didCheckErrors}
|
||||
errors={get(this.props.formErrors, [findIndex(this.props.formErrors, ['name', value]), 'errors'], [])}
|
||||
label={{ id: `users-permissions.PopUpForm.Email.${value}.label` }}
|
||||
name={`${dataToEdit}.${value}`}
|
||||
name={`${settingType}.${dataToEdit}.${value}`}
|
||||
inputDescription={{
|
||||
id: includes(value, 'object') ? 'users-permissions.PopUpForm.Email.email_templates.inputDescription' : '',
|
||||
params,
|
||||
|
||||
@ -20,6 +20,7 @@ import {
|
||||
ON_CHANGE_INPUT,
|
||||
ON_CLICK_ADD,
|
||||
ON_CLICK_DELETE,
|
||||
RESET_PROPS,
|
||||
RESET_SHOULD_DISPLAY_POLICIES_HINT,
|
||||
SELECT_ALL_ACTIONS,
|
||||
SET_ACTION_TYPE,
|
||||
@ -147,6 +148,10 @@ export function onClickDelete(itemToDelete) {
|
||||
};
|
||||
}
|
||||
|
||||
export const resetProps = () => ({
|
||||
type: RESET_PROPS,
|
||||
});
|
||||
|
||||
export function resetShouldDisplayPoliciesHint() {
|
||||
return {
|
||||
type: RESET_SHOULD_DISPLAY_POLICIES_HINT,
|
||||
|
||||
@ -18,6 +18,7 @@ export const ON_CANCEL = 'UsersPermissions/EditPage/ON_CANCEL';
|
||||
export const ON_CHANGE_INPUT = 'UsersPermissions/EditPage/ON_CHANGE_INPUT';
|
||||
export const ON_CLICK_ADD = 'UsersPermissions/EditPage/ON_CLICK_ADD';
|
||||
export const ON_CLICK_DELETE = 'UsersPermissions/EditPage/ON_CLICK_DELETE';
|
||||
export const RESET_PROPS = 'UsersPermissions/EditPage/RESET_PROPS';
|
||||
export const RESET_SHOULD_DISPLAY_POLICIES_HINT = 'UsersPermissions/EditPage/RESET_SHOULD_DISPLAY_POLICIES_HINT';
|
||||
export const SELECT_ALL_ACTIONS = 'UsersPermissions/EditPage/SELECT_ALL_ACTIONS';
|
||||
export const SET_ACTION_TYPE = 'UsersPermissions/EditPage/SET_ACTION_TYPE';
|
||||
|
||||
@ -17,6 +17,8 @@ import cn from 'classnames';
|
||||
import BackHeader from 'components/BackHeader';
|
||||
import Input from 'components/InputsIndex';
|
||||
import InputSearch from 'components/InputSearchContainer';
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import LoadingIndicatorPage from 'components/LoadingIndicatorPage';
|
||||
import PluginHeader from 'components/PluginHeader';
|
||||
import Plugins from 'components/Plugins';
|
||||
import Policies from 'components/Policies';
|
||||
@ -43,6 +45,7 @@ import {
|
||||
setRoleId,
|
||||
setShouldDisplayPolicieshint,
|
||||
submit,
|
||||
resetProps,
|
||||
resetShouldDisplayPoliciesHint,
|
||||
} from './actions';
|
||||
|
||||
@ -90,6 +93,8 @@ export class EditPage extends React.Component { // eslint-disable-line react/pre
|
||||
componentWillUnmount() {
|
||||
// Empty formErrors
|
||||
this.props.setErrors([]);
|
||||
// Empty modifiedData so prev values aren't displayed when loading
|
||||
this.props.resetProps();
|
||||
this.props.resetShouldDisplayPoliciesHint();
|
||||
}
|
||||
|
||||
@ -102,6 +107,73 @@ export class EditPage extends React.Component { // eslint-disable-line react/pre
|
||||
this.props.submit();
|
||||
}
|
||||
|
||||
showLoaderForm = () => {
|
||||
const { editPage: { modifiedData }, match: { params: { actionType } } } = this.props;
|
||||
|
||||
return actionType !== 'create' && get(modifiedData, ['name'], '') === '';
|
||||
}
|
||||
|
||||
showLoaderPermissions = () => {
|
||||
const { editPage: { modifiedData } } = this.props;
|
||||
|
||||
return isEmpty(get(modifiedData, ['permissions']));
|
||||
}
|
||||
|
||||
renderFirstBlock = () => (
|
||||
<React.Fragment>
|
||||
<div className="col-md-6">
|
||||
<div className="row">
|
||||
<Input
|
||||
autoFocus
|
||||
customBootstrapClass="col-md-12"
|
||||
errors={get(this.props.editPage, ['formErrors', findIndex(this.props.editPage.formErrors, ['name', 'name']), 'errors'])}
|
||||
didCheckErrors={this.props.editPage.didCheckErrors}
|
||||
label={{ id: 'users-permissions.EditPage.form.roles.label.name' }}
|
||||
name="name"
|
||||
onChange={this.props.onChangeInput}
|
||||
type="text"
|
||||
validations={{ required: true }}
|
||||
value={get(this.props.editPage, ['modifiedData', 'name'])}
|
||||
/>
|
||||
</div>
|
||||
<div className="row">
|
||||
<Input
|
||||
customBootstrapClass="col-md-12"
|
||||
label={{ id: 'users-permissions.EditPage.form.roles.label.description' }}
|
||||
name="description"
|
||||
onChange={this.props.onChangeInput}
|
||||
type="textarea"
|
||||
validations={{ required: true }}
|
||||
value={get(this.props.editPage, ['modifiedData', 'description'])}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<InputSearch
|
||||
addUser={this.props.addUser}
|
||||
didDeleteUser={this.props.editPage.didDeleteUser}
|
||||
didFetchUsers={this.props.editPage.didFetchUsers}
|
||||
didGetUsers={this.props.editPage.didGetUsers}
|
||||
getUser={this.props.getUser}
|
||||
label={{
|
||||
id: 'users-permissions.EditPage.form.roles.label.users',
|
||||
params: {
|
||||
number: size(get(this.props.editPage, ['modifiedData', 'users'])),
|
||||
},
|
||||
}}
|
||||
onClickAdd={this.props.onClickAdd}
|
||||
onClickDelete={this.props.onClickDelete}
|
||||
name="users"
|
||||
type="text"
|
||||
users={get(this.props.editPage, 'users')}
|
||||
validations={{ required: true }}
|
||||
values={get(this.props.editPage, ['modifiedData', 'users'])}
|
||||
/>
|
||||
<div className="col-md-12">
|
||||
<div className={styles.separator} />
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)
|
||||
|
||||
render() {
|
||||
const pluginHeaderTitle = this.props.match.params.actionType === 'create' ?
|
||||
'users-permissions.EditPage.header.title.create'
|
||||
@ -124,6 +196,10 @@ export class EditPage extends React.Component { // eslint-disable-line react/pre
|
||||
disabled: isEqual(this.props.editPage.modifiedData, this.props.editPage.initialData),
|
||||
},
|
||||
];
|
||||
|
||||
if (this.showLoaderForm()) {
|
||||
return <LoadingIndicatorPage />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -152,61 +228,21 @@ export class EditPage extends React.Component { // eslint-disable-line react/pre
|
||||
</div>
|
||||
<form className={styles.form}>
|
||||
<div className="row">
|
||||
<div className="col-md-6">
|
||||
<div className="row">
|
||||
<Input
|
||||
autoFocus
|
||||
customBootstrapClass="col-md-12"
|
||||
errors={get(this.props.editPage, ['formErrors', findIndex(this.props.editPage.formErrors, ['name', 'name']), 'errors'])}
|
||||
didCheckErrors={this.props.editPage.didCheckErrors}
|
||||
label={{ id: 'users-permissions.EditPage.form.roles.label.name' }}
|
||||
name="name"
|
||||
onChange={this.props.onChangeInput}
|
||||
type="text"
|
||||
validations={{ required: true }}
|
||||
value={get(this.props.editPage, ['modifiedData', 'name'])}
|
||||
/>
|
||||
</div>
|
||||
<div className="row">
|
||||
<Input
|
||||
customBootstrapClass="col-md-12"
|
||||
label={{ id: 'users-permissions.EditPage.form.roles.label.description' }}
|
||||
name="description"
|
||||
onChange={this.props.onChangeInput}
|
||||
type="textarea"
|
||||
validations={{ required: true }}
|
||||
value={get(this.props.editPage, ['modifiedData', 'description'])}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<InputSearch
|
||||
addUser={this.props.addUser}
|
||||
didDeleteUser={this.props.editPage.didDeleteUser}
|
||||
didFetchUsers={this.props.editPage.didFetchUsers}
|
||||
didGetUsers={this.props.editPage.didGetUsers}
|
||||
getUser={this.props.getUser}
|
||||
label={{
|
||||
id: 'users-permissions.EditPage.form.roles.label.users',
|
||||
params: {
|
||||
number: size(get(this.props.editPage, ['modifiedData', 'users'])),
|
||||
},
|
||||
}}
|
||||
onClickAdd={this.props.onClickAdd}
|
||||
onClickDelete={this.props.onClickDelete}
|
||||
name="users"
|
||||
type="text"
|
||||
users={get(this.props.editPage, 'users')}
|
||||
validations={{ required: true }}
|
||||
values={get(this.props.editPage, ['modifiedData', 'users'])}
|
||||
/>
|
||||
<div className="col-md-12">
|
||||
<div className={styles.separator} />
|
||||
</div>
|
||||
{this.showLoaderForm() ? (
|
||||
<div className={styles.loaderWrapper}><LoadingIndicator /></div>
|
||||
) : this.renderFirstBlock()}
|
||||
</div>
|
||||
<div className="row" style={{ marginRight: '-30px'}}>
|
||||
<Plugins
|
||||
plugins={get(this.props.editPage, ['modifiedData', 'permissions'])}
|
||||
/>
|
||||
{this.showLoaderPermissions() && (
|
||||
<div className={styles.loaderWrapper} style={{ minHeight: '400px' }}>
|
||||
<LoadingIndicator />
|
||||
</div>
|
||||
)}
|
||||
{!this.showLoaderPermissions() && (
|
||||
<Plugins
|
||||
plugins={get(this.props.editPage, ['modifiedData', 'permissions'])}
|
||||
/>
|
||||
)}
|
||||
<Policies
|
||||
shouldDisplayPoliciesHint={this.props.editPage.shouldDisplayPoliciesHint}
|
||||
inputSelectName={this.props.editPage.inputPoliciesPath}
|
||||
@ -246,6 +282,7 @@ EditPage.propTypes = {
|
||||
onChangeInput: PropTypes.func.isRequired,
|
||||
onClickAdd: PropTypes.func.isRequired,
|
||||
onClickDelete: PropTypes.func.isRequired,
|
||||
resetProps: PropTypes.func.isRequired,
|
||||
resetShouldDisplayPoliciesHint: PropTypes.func.isRequired,
|
||||
selectAllActions: PropTypes.func.isRequired,
|
||||
setActionType: PropTypes.func.isRequired,
|
||||
@ -281,6 +318,7 @@ function mapDispatchToProps(dispatch) {
|
||||
setRoleId,
|
||||
setShouldDisplayPolicieshint,
|
||||
submit,
|
||||
resetProps,
|
||||
resetShouldDisplayPoliciesHint,
|
||||
},
|
||||
dispatch,
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
ON_CHANGE_INPUT,
|
||||
ON_CLICK_ADD,
|
||||
ON_CLICK_DELETE,
|
||||
RESET_PROPS,
|
||||
RESET_SHOULD_DISPLAY_POLICIES_HINT,
|
||||
SELECT_ALL_ACTIONS,
|
||||
SET_ACTION_TYPE,
|
||||
@ -85,6 +86,11 @@ function editPageReducer(state = initialState, action) {
|
||||
return state
|
||||
.set('didDeleteUser', !state.get('didDeleteUser'))
|
||||
.updateIn(['modifiedData', 'users'], list => list.filter(o => o[o.id ? 'id' : '_id'] !== action.itemToDelete[o.id ? 'id' : '_id']));
|
||||
case RESET_PROPS:
|
||||
return state
|
||||
.updateIn(['modifiedData'], () => Map({}))
|
||||
.update('initialData', () => Map({}))
|
||||
.update('users', () => List([]));
|
||||
case RESET_SHOULD_DISPLAY_POLICIES_HINT:
|
||||
return state.set('shouldDisplayPoliciesHint', true);
|
||||
case SELECT_ALL_ACTIONS: {
|
||||
|
||||
@ -30,3 +30,11 @@
|
||||
font-weight: bold;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.loaderWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
min-height: 260px;
|
||||
margin: auto;
|
||||
}
|
||||
@ -96,6 +96,8 @@ export class HomePage extends React.Component {
|
||||
this.props.resetProps();
|
||||
}
|
||||
|
||||
getEndPoint = () => this.props.match.params.settingType;
|
||||
|
||||
handleKeyBoardShortCut = (e) => {
|
||||
if (includes(keyBoardShortCuts, e.keyCode)) {
|
||||
const mapKey = clone(this.state.mapKey);
|
||||
@ -122,8 +124,8 @@ export class HomePage extends React.Component {
|
||||
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const modifiedObject = get(this.props.modifiedData, this.props.dataToEdit);
|
||||
const initObject = get(this.props.initialData, this.props.dataToEdit);
|
||||
const modifiedObject = get(this.props.modifiedData, [this.getEndPoint(), this.props.dataToEdit]);
|
||||
const initObject = get(this.props.initialData, [this.getEndPoint(), this.props.dataToEdit]);
|
||||
const formErrors = checkFormValidity(this.props.match.params.settingType, modifiedObject, this.props.dataToEdit);
|
||||
|
||||
if (isEqual(initObject, modifiedObject)) {
|
||||
@ -153,23 +155,31 @@ export class HomePage extends React.Component {
|
||||
},
|
||||
];
|
||||
|
||||
showLoaders = () => {
|
||||
const { data, isLoading, modifiedData } = this.props;
|
||||
const isAdvanded = this.getEndPoint() === 'advanced';
|
||||
|
||||
return isLoading && get(data, this.getEndPoint()) === undefined && !isAdvanded || isLoading && isAdvanded && get(modifiedData, this.getEndPoint()) === undefined;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { didCheckErrors, formErrors, modifiedData, initialData, match, dataToEdit } = this.props;
|
||||
const { data, didCheckErrors, formErrors, modifiedData, initialData, match, dataToEdit } = this.props;
|
||||
const headerActions = match.params.settingType === 'advanced' && !isEqual(modifiedData, initialData) ?
|
||||
this.pluginHeaderActions : [];
|
||||
const noButtonList = match.params.settingType === 'email-templates' || match.params.settingType === 'providers';
|
||||
const component = match.params.settingType === 'advanced' ?
|
||||
<EditForm onChange={this.props.onChange} values={modifiedData} /> : (
|
||||
<EditForm onChange={this.props.onChange} values={get(modifiedData, this.getEndPoint(), {})} showLoaders={this.showLoaders()} /> : (
|
||||
<List
|
||||
data={this.props.data}
|
||||
data={get(data, this.getEndPoint(), [])}
|
||||
deleteData={this.props.deleteData}
|
||||
noButton={noButtonList}
|
||||
onButtonClick={this.handleButtonClick}
|
||||
settingType={match.params.settingType}
|
||||
values={modifiedData}
|
||||
showLoaders={this.showLoaders()}
|
||||
values={get(modifiedData, this.getEndPoint(), {})}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={(e) => e.preventDefault()}>
|
||||
@ -191,7 +201,7 @@ export class HomePage extends React.Component {
|
||||
onChange={this.props.onChange}
|
||||
onSubmit={this.handleSubmit}
|
||||
settingType={match.params.settingType}
|
||||
values={modifiedData[dataToEdit] || {}}
|
||||
values={get(modifiedData,[this.getEndPoint(), dataToEdit], {})}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
@ -208,7 +218,7 @@ HomePage.defaultProps = {};
|
||||
|
||||
HomePage.propTypes = {
|
||||
cancelChanges: PropTypes.func.isRequired,
|
||||
data: PropTypes.array.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
dataToEdit: PropTypes.string.isRequired,
|
||||
deleteData: PropTypes.func.isRequired,
|
||||
didCheckErrors: PropTypes.bool.isRequired,
|
||||
@ -217,6 +227,7 @@ HomePage.propTypes = {
|
||||
formErrors: PropTypes.array.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
initialData: PropTypes.object.isRequired,
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
location: PropTypes.object.isRequired,
|
||||
match: PropTypes.object.isRequired,
|
||||
modifiedData: PropTypes.object.isRequired,
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
CANCEL_CHANGES,
|
||||
DELETE_DATA,
|
||||
DELETE_DATA_SUCCEEDED,
|
||||
FETCH_DATA,
|
||||
FETCH_DATA_SUCCEEDED,
|
||||
ON_CHANGE,
|
||||
RESET_PROPS,
|
||||
@ -21,16 +22,18 @@ import {
|
||||
} from './constants';
|
||||
|
||||
const initialState = fromJS({
|
||||
data: List([]),
|
||||
data: fromJS({}),
|
||||
dataToDelete: Map({}),
|
||||
dataToEdit: '',
|
||||
deleteEndPoint: '',
|
||||
didCheckErrors: false,
|
||||
formErrors: List([]),
|
||||
initialData: Map({}),
|
||||
isLoading: true,
|
||||
modifiedData: Map({}),
|
||||
showButtons: false,
|
||||
didDeleteData: false,
|
||||
endPoint: 'roles',
|
||||
});
|
||||
|
||||
function homePageReducer(state = initialState, action) {
|
||||
@ -45,27 +48,36 @@ function homePageReducer(state = initialState, action) {
|
||||
.set('deleteEndPoint', action.deleteEndPoint);
|
||||
case DELETE_DATA_SUCCEEDED:
|
||||
return state
|
||||
.update('data', list => list.splice(action.indexDataToDelete, 1))
|
||||
.updateIn(['data', state.get('endPoint')], list => list.splice(action.indexDataToDelete, 1))
|
||||
.set('deleteEndPoint', '')
|
||||
.set('dataToDelete', Map({}))
|
||||
.update('didDeleteData', (v) => !v);
|
||||
case FETCH_DATA:
|
||||
return state
|
||||
.update('endPoint', () => action.endPoint)
|
||||
.update('isLoading', () => true);
|
||||
case FETCH_DATA_SUCCEEDED:
|
||||
return state
|
||||
.set('data', List(action.data))
|
||||
.set('initialData', action.modifiedData)
|
||||
.set('modifiedData', action.modifiedData);
|
||||
.updateIn(['data', state.get('endPoint')], () => List(action.data))
|
||||
.updateIn(['initialData', state.get('endPoint')], () => action.modifiedData)
|
||||
.update('isLoading', () => false)
|
||||
.updateIn(['modifiedData', state.get('endPoint')], () => action.modifiedData);
|
||||
case ON_CHANGE:
|
||||
return state
|
||||
.updateIn(action.keys, () => action.value);
|
||||
case RESET_PROPS:
|
||||
return initialState;
|
||||
return initialState
|
||||
.update('data', () => state.get('data'))
|
||||
.update('initialData', () => state.get('initialData'))
|
||||
.update('modifiedData', () => state.get('modifiedData'))
|
||||
.update('endPoint', () => 'roles');
|
||||
case SET_DATA_TO_EDIT:
|
||||
return state.update('dataToEdit', () => action.dataToEdit);
|
||||
case SET_FORM:
|
||||
return state
|
||||
.set('formErrors', List([]))
|
||||
.set('initialData', action.form)
|
||||
.set('modifiedData', action.form);
|
||||
.updateIn(['initialData', state.get('endPoint')], () => action.form)
|
||||
.updateIn(['modifiedData', state.get('endPoint')], () => action.form);
|
||||
case SET_FORM_ERRORS:
|
||||
return state
|
||||
.update('didCheckErrors', (v) => v = !v)
|
||||
|
||||
@ -17,9 +17,6 @@ import {
|
||||
SUBMIT,
|
||||
} from './constants';
|
||||
|
||||
// TODO uncomment to test design providers and so on...
|
||||
// import data from './data.json';
|
||||
|
||||
import {
|
||||
makeSelectAllData,
|
||||
makeSelectDataToDelete,
|
||||
@ -31,8 +28,8 @@ export function* dataDelete() {
|
||||
try {
|
||||
const allData = yield select(makeSelectAllData());
|
||||
const dataToDelete = yield select(makeSelectDataToDelete());
|
||||
const indexDataToDelete = findIndex(allData, ['name', dataToDelete.name]);
|
||||
const endPointAPI = yield select(makeSelectDeleteEndPoint());
|
||||
const indexDataToDelete = findIndex(allData[endPointAPI], ['name', dataToDelete.name]);
|
||||
|
||||
if (indexDataToDelete !== -1) {
|
||||
const id = dataToDelete.id;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user