Handle submit for settingviewmodel

This commit is contained in:
soupette 2019-07-05 10:54:34 +02:00
parent de9ee49878
commit 00cea393eb
13 changed files with 343 additions and 25 deletions

View File

@ -0,0 +1,11 @@
import styled from 'styled-components';
const Title = styled.div`
color: #787e8f;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.77px;
text-transform: uppercase;
`;
export default Title;

View File

@ -0,0 +1,29 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import pluginId from '../../pluginId';
import Title from './Title';
const SectionTitle = ({ isSettings }) => {
const suffix = isSettings ? 'settings' : 'layout';
const msgId = `${pluginId}.containers.SettingPage.${suffix}`;
return (
<div style={{ marginBottom: '18px' }}>
<FormattedMessage id={msgId}>
{msg => <Title>{msg}</Title>}
</FormattedMessage>
</div>
);
};
SectionTitle.propTypes = {
isSettings: PropTypes.bool,
};
SectionTitle.defaultProps = {
isSettings: false,
};
export default SectionTitle;

View File

@ -23,15 +23,15 @@ function Main({ isLoading, emitEvent }) {
return <LoadingIndicatorPage />; return <LoadingIndicatorPage />;
} }
const renderRoute = props => ( const renderRoute = (props, Component) => (
<SettingsView emitEvent={emitEvent} {...props} /> <Component emitEvent={emitEvent} {...props} />
); );
return ( return (
<Switch> <Switch>
<Route <Route
path="/plugins/content-manager/ctm-configurations/models/:name/:settingType" path="/plugins/content-manager/ctm-configurations/models/:name/:settingType"
component={SettingViewModel} component={props => renderRoute(props, SettingViewModel)}
/> />
<Route <Route
path="/plugins/content-manager/ctm-configurations/groups/:name" path="/plugins/content-manager/ctm-configurations/groups/:name"
@ -39,7 +39,7 @@ function Main({ isLoading, emitEvent }) {
/> />
<Route <Route
path="/plugins/content-manager/ctm-configurations/:type" path="/plugins/content-manager/ctm-configurations/:type"
render={renderRoute} render={props => renderRoute(props, SettingsView)}
/> />
</Switch> </Switch>
); );

View File

@ -1,4 +1,11 @@
import { GET_DATA, GET_DATA_SUCCEEDED } from './constants'; import {
GET_DATA,
GET_DATA_SUCCEEDED,
ON_CHANGE,
ON_RESET,
ON_SUBMIT,
SUBMIT_SUCCEEDED,
} from './constants';
export function getData(uid) { export function getData(uid) {
return { return {
@ -13,3 +20,30 @@ export function getDataSucceeded(layout) {
layout, layout,
}; };
} }
export function onChange({ target: { name, value } }) {
return {
type: ON_CHANGE,
keys: ['modifiedData', ...name.split('.')],
value,
};
}
export function onReset() {
return {
type: ON_RESET,
};
}
export function onSubmit(uid, emitEvent) {
return {
type: ON_SUBMIT,
uid,
emitEvent,
};
}
export function submitSucceeded() {
return {
type: SUBMIT_SUCCEEDED,
};
}

View File

@ -1,3 +1,8 @@
export const GET_DATA = 'ContentManager/SettingViewModel/GET_DATA'; export const GET_DATA = 'ContentManager/SettingViewModel/GET_DATA';
export const GET_DATA_SUCCEEDED = export const GET_DATA_SUCCEEDED =
'ContentManager/SettingViewModel/GET_DATA_SUCCEEDED'; 'ContentManager/SettingViewModel/GET_DATA_SUCCEEDED';
export const ON_CHANGE = 'ContentManager/SettingViewModel/ON_CHANGE';
export const ON_RESET = 'ContentManager/SettingViewModel/ON_RESET';
export const ON_SUBMIT = 'ContentManager/SettingViewModel/ON_SUBMIT';
export const SUBMIT_SUCCEEDED =
'ContentManager/SettingViewModel/SUBMIT_SUCCEEDED';

View File

@ -0,0 +1,65 @@
{
"list-settings": [
{
"label": { "id": "content-manager.form.Input.search" },
"customBootstrapClass": "col-md-4",
"didCheckErrors": false,
"errors": [],
"name": "settings.searchable",
"type": "toggle",
"validations": {}
},
{
"label": { "id": "content-manager.form.Input.filters" },
"customBootstrapClass": "col-md-4",
"didCheckErrors": false,
"errors": [],
"name": "settings.filterable",
"type": "toggle",
"validations": {}
},
{
"label": { "id": "content-manager.form.Input.bulkActions" },
"customBootstrapClass": "col-md-4",
"didCheckErrors": false,
"errors": [],
"name": "settings.bulkable",
"type": "toggle",
"validations": {}
},
{
"label": { "id": "content-manager.form.Input.pageEntries" },
"customBootstrapClass": "col-md-4",
"didCheckErrors": false,
"errors": [],
"inputDescription": { "id": "content-manager.form.Input.pageEntries.inputDescription" },
"name": "settings.pageSize",
"selectOptions": ["10", "20", "50", "100"],
"type": "select",
"validations": {}
},
{
"label": { "id": "content-manager.form.Input.defaultSort" },
"customBootstrapClass": "col-md-4 ml-md-auto",
"didCheckErrors": false,
"errors": [],
"style": { "marginRight": "-20px" },
"name": "settings.defaultSortBy",
"selectOptions": ["id"],
"type": "select",
"validations": {}
},
{
"label": "",
"customBootstrapClass": "col-md-1",
"didCheckErrors": false,
"errors": [],
"name": "settings.defaultSortOrder",
"selectOptions": ["ASC", "DESC"],
"type": "select",
"validations": {}
}
],
"edit-settings": []
}

View File

@ -1,42 +1,99 @@
import React, { memo, useEffect } from 'react'; import React, { memo, useEffect, useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { bindActionCreators, compose } from 'redux'; import { bindActionCreators, compose } from 'redux';
import { upperFirst } from 'lodash'; import { get, isEqual, upperFirst } from 'lodash';
import { BackHeader, HeaderNav, PluginHeader } from 'strapi-helper-plugin'; import {
BackHeader,
HeaderNav,
InputsIndex as Input,
PluginHeader,
PopUpWarning,
} from 'strapi-helper-plugin';
import pluginId from '../../pluginId'; import pluginId from '../../pluginId';
import Block from '../../components/Block';
import Container from '../../components/Container'; import Container from '../../components/Container';
import SectionTitle from '../../components/SectionTitle';
import { getData } from './actions'; import { getData, onChange, onReset, onSubmit } from './actions';
import reducer from './reducer'; import reducer from './reducer';
import saga from './saga'; import saga from './saga';
import makeSelectSettingViewModel from './selectors'; import makeSelectSettingViewModel from './selectors';
import forms from './forms.json';
const getUrl = (name, to) => const getUrl = (name, to) =>
`/plugins/${pluginId}/ctm-configurations/models/${name}/${to}`; `/plugins/${pluginId}/ctm-configurations/models/${name}/${to}`;
function SettingViewModel({ function SettingViewModel({
emitEvent,
getData, getData,
history: { goBack }, history: { goBack },
initialData,
match: { match: {
params: { name }, params: { name, settingType },
}, },
modifiedData,
onChange,
onReset,
onSubmit,
shouldToggleModalSubmit,
}) { }) {
strapi.useInjectReducer({ key: 'settingViewModel', reducer, pluginId }); strapi.useInjectReducer({ key: 'settingViewModel', reducer, pluginId });
strapi.useInjectSaga({ key: 'settingViewModel', saga, pluginId }); strapi.useInjectSaga({ key: 'settingViewModel', saga, pluginId });
const [showWarningSubmit, setWarningSubmit] = useState(false);
const [showWarningCancel, setWarningCancel] = useState(false);
const toggleWarningSubmit = () => setWarningSubmit(prevState => !prevState);
const toggleWarningCancel = () => setWarningCancel(prevState => !prevState);
useEffect(() => { useEffect(() => {
getData(name); getData(name);
}, []); }, []);
useEffect(() => {
if (showWarningSubmit) {
toggleWarningSubmit();
}
}, [shouldToggleModalSubmit]);
const getPluginHeaderActions = () => {
if (isEqual(modifiedData, initialData)) {
return [];
}
return [
{
label: 'content-manager.popUpWarning.button.cancel',
kind: 'secondary',
onClick: toggleWarningCancel,
type: 'button',
},
{
kind: 'primary',
label: 'content-manager.containers.Edit.submit',
onClick: () => {
toggleWarningSubmit();
emitEvent('willSaveContentTypeLayout');
},
type: 'submit',
},
];
};
const getSelectOptions = input => {
if (input.name === 'settings.defaultSortBy') {
return get(modifiedData, ['layouts', 'list'], []);
}
return input.selectOptions;
};
return ( return (
<> <>
<BackHeader onClick={() => goBack()} /> <BackHeader onClick={() => goBack()} />
<Container className="container-fluid"> <Container className="container-fluid">
<PluginHeader <PluginHeader
actions={[]} actions={getPluginHeaderActions()}
title={{ title={{
id: `${pluginId}.containers.SettingViewModel.pluginHeader.title`, id: `${pluginId}.containers.SettingViewModel.pluginHeader.title`,
values: { name: upperFirst(name) }, values: { name: upperFirst(name) },
@ -58,22 +115,81 @@ function SettingViewModel({
}, },
]} ]}
/> />
<div className="row">
<Block
style={{
marginBottom: '13px',
paddingBottom: '30px',
paddingTop: '30px',
}}
>
<SectionTitle isSettings />
<div className="row">
{forms[settingType].map(input => {
return (
<Input
key={input.name}
{...input}
onChange={onChange}
selectOptions={getSelectOptions(input)}
value={get(modifiedData, input.name)}
/>
);
})}
</div>
</Block>
</div>
</Container> </Container>
<PopUpWarning
isOpen={showWarningCancel}
toggleModal={toggleWarningCancel}
content={{
title: 'content-manager.popUpWarning.title',
message: 'content-manager.popUpWarning.warning.cancelAllSettings',
cancel: 'content-manager.popUpWarning.button.cancel',
confirm: 'content-manager.popUpWarning.button.confirm',
}}
popUpWarningType="danger"
onConfirm={() => {
onReset();
toggleWarningCancel();
}}
/>
<PopUpWarning
isOpen={showWarningSubmit}
toggleModal={toggleWarningSubmit}
content={{
title: 'content-manager.popUpWarning.title',
message: 'content-manager.popUpWarning.warning.updateAllSettings',
cancel: 'content-manager.popUpWarning.button.cancel',
confirm: 'content-manager.popUpWarning.button.confirm',
}}
popUpWarningType="danger"
onConfirm={() => onSubmit(name, emitEvent)}
/>
</> </>
); );
} }
SettingViewModel.defaultProps = {}; SettingViewModel.defaultProps = {};
SettingViewModel.propTypes = { SettingViewModel.propTypes = {
emitEvent: PropTypes.func.isRequired,
getData: PropTypes.func.isRequired, getData: PropTypes.func.isRequired,
history: PropTypes.shape({ history: PropTypes.shape({
goBack: PropTypes.func, goBack: PropTypes.func,
}).isRequired, }).isRequired,
initialData: PropTypes.object.isRequired,
match: PropTypes.shape({ match: PropTypes.shape({
params: PropTypes.shape({ params: PropTypes.shape({
name: PropTypes.string, name: PropTypes.string,
}), }),
}).isRequired, }).isRequired,
modifiedData: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
onReset: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
shouldToggleModalSubmit: PropTypes.bool.isRequired,
}; };
const mapStateToProps = makeSelectSettingViewModel(); const mapStateToProps = makeSelectSettingViewModel();
@ -82,6 +198,9 @@ export function mapDispatchToProps(dispatch) {
return bindActionCreators( return bindActionCreators(
{ {
getData, getData,
onChange,
onReset,
onSubmit,
}, },
dispatch dispatch
); );

View File

@ -3,20 +3,34 @@
* settingViewModel reducer * settingViewModel reducer
*/ */
import { fromJS, Map } from 'immutable'; import { fromJS } from 'immutable';
import { GET_DATA_SUCCEEDED } from './constants'; import {
GET_DATA_SUCCEEDED,
ON_CHANGE,
ON_RESET,
SUBMIT_SUCCEEDED,
} from './constants';
export const initialState = fromJS({ export const initialState = fromJS({
initialData: Map({}), initialData: fromJS({}),
modifiedData: Map({}), modifiedData: fromJS({}),
shouldToggleModalSubmit: true,
}); });
function settingViewModelReducer(state = initialState, action) { function settingViewModelReducer(state = initialState, action) {
switch (action.type) { switch (action.type) {
case GET_DATA_SUCCEEDED: case GET_DATA_SUCCEEDED:
return state return state
.update('initialData', () => Map(action.layout)) .update('initialData', () => fromJS(action.layout))
.update('modifiedData', () => Map(action.layout)); .update('modifiedData', () => fromJS(action.layout));
case ON_CHANGE:
return state.updateIn(action.keys, () => action.value);
case ON_RESET:
return state.update('modifiedData', () => state.get('initialData'));
case SUBMIT_SUCCEEDED:
return state
.update('initialData', () => state.get('modifiedData'))
.update('shouldToggleModalSubmit', v => !v);
default: default:
return state; return state;
} }

View File

@ -1,9 +1,10 @@
import { all, fork, put, call, takeLatest } from 'redux-saga/effects'; import { all, fork, put, call, takeLatest, select } from 'redux-saga/effects';
import { request } from 'strapi-helper-plugin'; import { request } from 'strapi-helper-plugin';
import pluginId from '../../pluginId'; import pluginId from '../../pluginId';
import { getDataSucceeded } from './actions'; import { getDataSucceeded, submitSucceeded } from './actions';
import { GET_DATA } from './constants'; import { GET_DATA, ON_SUBMIT } from './constants';
import { makeSelectModifiedData } from './selectors';
const getRequestUrl = path => `/${pluginId}/fixtures/${path}`; const getRequestUrl = path => `/${pluginId}/fixtures/${path}`;
@ -19,9 +20,27 @@ export function* getData({ uid }) {
} }
} }
export function* submit({ emitEvent, uid }) {
try {
const body = yield select(makeSelectModifiedData());
yield call(request, getRequestUrl(`layouts/${uid}`), {
method: 'PUT',
body,
});
emitEvent('didSaveContentTypeLayout');
yield put(submitSucceeded());
} catch (err) {
strapi.notification.error('notification.error');
}
}
function* defaultSaga() { function* defaultSaga() {
try { try {
yield all([fork(takeLatest, GET_DATA, getData)]); yield all([
fork(takeLatest, GET_DATA, getData),
fork(takeLatest, ON_SUBMIT, submit),
]);
} catch (err) { } catch (err) {
// Do nothing // Do nothing
} }

View File

@ -24,5 +24,13 @@ const makeSelectSettingViewModel = () =>
} }
); );
const makeSelectModifiedData = () =>
createSelector(
settingViewModelDomain(),
substate => {
return substate.get('modifiedData').toJS();
}
);
export default makeSelectSettingViewModel; export default makeSelectSettingViewModel;
export { settingViewModelDomain }; export { settingViewModelDomain, makeSelectModifiedData };

View File

@ -32,7 +32,7 @@
"didCheckErrors": false, "didCheckErrors": false,
"errors": [], "errors": [],
"inputDescription": { "id": "content-manager.form.Input.pageEntries.inputDescription" }, "inputDescription": { "id": "content-manager.form.Input.pageEntries.inputDescription" },
"name": "pageEntries", "name": "pageSize",
"selectOptions": ["10", "20", "50", "100"], "selectOptions": ["10", "20", "50", "100"],
"type": "select", "type": "select",
"validations": {} "validations": {}

View File

@ -48,6 +48,14 @@
"policies": [] "policies": []
} }
}, },
{
"method": "PUT",
"path": "/fixtures/layouts/:uid",
"handler": "ContentManagerFixtures.updateLayout",
"config": {
"policies": []
}
},
{ {
"method": "GET", "method": "GET",
"path": "/explorer/:model", "path": "/explorer/:model",

View File

@ -3,7 +3,7 @@ module.exports = {
const generalSettings = { const generalSettings = {
bulkable: true, bulkable: true,
filters: true, filters: true,
pageEntries: 10, pageSize: 20,
search: true, search: true,
}; };
@ -23,7 +23,6 @@ module.exports = {
}, },
getLayout: ctx => { getLayout: ctx => {
console.log('ooo');
const layouts = { const layouts = {
article: { article: {
uid: 'article', uid: 'article',
@ -48,6 +47,7 @@ module.exports = {
settings: { settings: {
mainField: 'id', mainField: 'id',
defaultSortBy: 'id', defaultSortBy: 'id',
defaultSortOrder: 'ASC',
searchable: true, searchable: true,
filterable: true, filterable: true,
bulkable: false, bulkable: false,
@ -158,6 +158,7 @@ module.exports = {
settings: { settings: {
mainField: 'id', mainField: 'id',
defaultSortBy: 'id', defaultSortBy: 'id',
defaultSortOrder: 'ASC',
searchable: true, searchable: true,
filterable: true, filterable: true,
bulkable: false, bulkable: false,
@ -311,4 +312,9 @@ module.exports = {
// Here it should update all the other settings // Here it should update all the other settings
ctx.body = { ok: true }; ctx.body = { ok: true };
}, },
updateLayout: ctx => {
// Update specific layout
ctx.body = { ok: true };
},
}; };