Split component and put logic into the reducer

This commit is contained in:
Aurelsicoko 2018-08-02 15:27:30 +02:00
parent bbab1d47de
commit 6a4a07428a
10 changed files with 236 additions and 102 deletions

View File

@ -28,18 +28,33 @@ function EditRelations(props) {
{map(filterRelationsUpload(props.schema.relations), (relation, key) => {
if (relation.nature.toLowerCase().includes('morph') && relation[key]) return '';
const Select = ['oneWay', 'oneToOne', 'manyToOne', 'oneToManyMorph', 'oneToOneMorph'].includes(relation.nature) ? SelectOne : SelectMany;
if(['oneWay', 'oneToOne', 'manyToOne', 'oneToManyMorph', 'oneToOneMorph'].includes(relation.nature)) {
return (
<SelectOne
currentModelName={props.currentModelName}
key={key}
record={props.record}
relation={relation}
schema={props.schema}
setRecordAttribute={props.changeData}
location={props.location}
onRedirect={props.onRedirect}
/>
);
}
return (
<Select
<SelectMany
currentModelName={props.currentModelName}
key={key}
record={props.record}
relation={relation}
schema={props.schema}
setRecordAttribute={props.changeData}
location={props.location}
onAddRelationalItem={props.onAddRelationalItem}
onRedirect={props.onRedirect}
onRemoveRelationItem={props.onRemoveRelationItem}
onSort={props.onSort}
/>
);
})}
@ -53,10 +68,12 @@ EditRelations.defaultProps = {
};
EditRelations.propTypes = {
changeData: PropTypes.func.isRequired,
currentModelName: PropTypes.string.isRequired,
location: PropTypes.object.isRequired,
onAddRelationalItem: PropTypes.func.isRequired,
onRedirect: PropTypes.func.isRequired,
onRemoveRelationItem: PropTypes.func.isRequired,
onSort: PropTypes.func.isRequired,
record: PropTypes.object,
schema: PropTypes.object,
};

View File

@ -0,0 +1,48 @@
/**
*
* SortableItem
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { SortableElement } from 'react-sortable-hoc';
// Icons.
import IconRemove from '../../assets/images/icon_remove.svg';
// CSS.
import styles from './styles.scss';
const SortableItem = SortableElement(({ item, onClick, onRemove, sortIndex }) => {
return (
<li className={styles.sortableListItem}>
<div>
<div className={styles.dragHandle}><span></span></div>
<FormattedMessage id='content-manager.containers.Edit.clickToJump'>
{title => (
<span
className='sortable-item--value'
onClick={() => onClick(item)}
title={title}
>
{item.label}
</span>
)}
</FormattedMessage>
</div>
<div className={styles.sortableListItemActions}>
<img src={IconRemove} alt="Remove Icon" onClick={() => onRemove(sortIndex)} />
</div>
</li>
);
});
SortableItem.propTypes = {
item: PropTypes.object.isRequired,
onClick: PropTypes.func.isRequired,
onRemove: PropTypes.func.isRequired,
sortIndex: PropTypes.number.isRequired,
};
export default SortableItem;

View File

@ -0,0 +1,36 @@
/**
*
* SortableList
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { SortableContainer } from 'react-sortable-hoc';
import SortableItem from './SortableItem';
// CSS.
import styles from './styles.scss';
const SortableList = SortableContainer(({ items, onClick, onRemove }) => {
const shadowList = (items.length > 4 ? <div className={styles.sortableListLong}></div> : '');
return (
<div className={cn(styles.sortableList)}>
<ul>
{items.map((item, index) => (
<SortableItem key={`item-${index}`} index={index} sortIndex={index} item={item} onRemove={onRemove} onClick={onClick} />
))}
</ul>
{shadowList}
</div>
);
});
SortableList.propTypes = {
items: PropTypes.array.isRequired,
onClick: PropTypes.func.isRequired,
onRemove: PropTypes.func.isRequired,
};
export default SortableList;

View File

@ -7,10 +7,8 @@
import React from 'react';
import Select from 'react-select';
import { FormattedMessage } from 'react-intl';
import { SortableContainer, SortableElement, arrayMove } from 'react-sortable-hoc';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { cloneDeep, isArray, isNull, isUndefined, get, findIndex, includes, isEmpty } from 'lodash';
import { cloneDeep, isArray, isNull, isUndefined, get, findIndex, isEmpty } from 'lodash';
// Utils.
import request from 'utils/request';
@ -18,52 +16,11 @@ import templateObject from 'utils/templateObject';
// CSS.
import 'react-select/dist/react-select.css';
// Icons.
import IconRemove from '../../assets/images/icon_remove.svg';
// Component.
import SortableList from './SortableList';
// CSS.
import styles from './styles.scss';
const SortableItem = SortableElement(({idx, onRemove, item, onClick}) => {
return (
<li className={styles.sortableListItem}>
<div>
<div className={styles.dragHandle}><span></span></div>
<FormattedMessage id='content-manager.containers.Edit.clickToJump'>
{title => (
<span
className='sortable-item--value'
onClick={() => onClick(item)}
title={title}
>
{item.label}
</span>
)}
</FormattedMessage>
</div>
<div className={styles.sortableListItemActions}>
<img src={IconRemove} alt="Remove Icon" onClick={() => onRemove(idx)} />
</div>
</li>
);
});
const SortableList = SortableContainer(({items, onRemove, onClick}) => {
const shadowList = (items.length > 4 ? <div className={styles.sortableListLong}></div> : '');
return (
<div className={cn(styles.sortableList)}>
<ul>
{items.map((item, index) => (
<SortableItem key={`item-${index}`} index={index} idx={index} item={item} onRemove={onRemove} onClick={onClick} />
))}
</ul>
{shadowList}
</div>
);
});
class SelectMany extends React.PureComponent {
state = {
isLoading: true,
@ -147,24 +104,15 @@ class SelectMany extends React.PureComponent {
};
handleChange = value => {
const values = get(this.props.record, this.props.relation.alias) || [];
const target = {
name: `record.${this.props.relation.alias}`,
type: 'select',
value: [...values, value.value],
};
// Remove new added value from available option;
this.state.options = this.state.options.filter(el => {
if (el.value._id || el.value.id === value.value.id || value.value._id) {
return false;
}
this.state.options = this.state.options.filter(el =>
!((el.value._id || el.value.id) === (value.value.id || value.value._id))
);
return true;
this.props.onAddRelationalItem({
key: this.props.relation.alias,
value: value.value,
});
this.props.setRecordAttribute({ target });
};
handleBottomScroll = () => {
@ -175,33 +123,16 @@ class SelectMany extends React.PureComponent {
});
}
handleInputChange = (value) => {
const clonedOptions = this.state.options;
const filteredValues = clonedOptions.filter(data => includes(data.label, value));
if (filteredValues.length === 0) {
return this.getOptions(value);
}
}
handleSortEnd = ({oldIndex, newIndex}) => {
const values = get(this.props.record, this.props.relation.alias);
const target = {
name: `record.${this.props.relation.alias}`,
type: 'select',
value: arrayMove(values, oldIndex, newIndex),
};
this.props.setRecordAttribute({ target });
handleSortEnd = ({ oldIndex, newIndex }) => {
this.props.onSort({
key: this.props.relation.alias,
oldIndex,
newIndex,
});
};
handleRemove = (index) => {
const values = get(this.props.record, this.props.relation.alias);
const target = {
name: `record.${this.props.relation.alias}`,
type: 'select',
value: values.filter( (item, idx) => idx !== index),
};
// Add removed value from available option;
this.state.options.push({
@ -210,7 +141,10 @@ class SelectMany extends React.PureComponent {
.mainField,
});
this.props.setRecordAttribute({ target });
this.props.onRemoveRelationItem({
key: this.props.relation.alias,
index,
});
}
// Redirect to the edit page
@ -237,11 +171,12 @@ class SelectMany extends React.PureComponent {
<label htmlFor={this.props.relation.alias}>{this.props.relation.alias} <span>({value.length})</span></label>
{description}
<Select
onChange={this.handleChange}
options={this.state.options}
className={`${styles.select}`}
id={this.props.relation.alias}
isLoading={this.state.isLoading}
onChange={this.handleChange}
onMenuScrollToBottom={this.handleBottomScroll}
options={this.state.options}
placeholder={<FormattedMessage id='content-manager.containers.Edit.addAnItem' />}
/>
<SortableList
@ -273,10 +208,12 @@ class SelectMany extends React.PureComponent {
}
SelectMany.propTypes = {
onAddRelationalItem: PropTypes.func.isRequired,
onRedirect: PropTypes.func.isRequired,
onRemoveRelationItem: PropTypes.func.isRequired,
onSort: PropTypes.func.isRequired,
record: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]).isRequired,
relation: PropTypes.object.isRequired,
setRecordAttribute: PropTypes.func.isRequired,
};
export default SelectMany;

View File

@ -1,7 +1,6 @@
.selectMany { /* stylelint-disable */
padding-bottom: 19px;
margin-bottom: 0px;
overflow-x: hidden;
label{
font-size: 1.3rem;
@ -36,14 +35,19 @@
}
.selectManyUpdate{
padding-bottom: 18px !important;
padding-bottom: 15px !important;
}
.select{
margin-bottom: 0px !important;
}
.sortableList {
max-height: 110px;
overflow: hidden;
max-height: 116px;
> ul {
margin: -21px -20px 0;
margin: 4px -20px 0;
padding: 0 20px !important;
list-style: none !important;
overflow: scroll;

View File

@ -8,6 +8,7 @@ import { get } from 'lodash';
import { getValidationsFromForm } from 'utils/formValidations';
import {
ADD_RELATION_ITEM,
CHANGE_DATA,
GET_DATA,
GET_DATA_SUCCEEDED,
@ -15,15 +16,25 @@ import {
GET_LAYOUT_SUCCEEDED,
INIT_MODEL_PROPS,
ON_CANCEL,
REMOVE_RELATION_ITEM,
RESET_PROPS,
SET_FILE_RELATIONS,
SET_LOADER,
SET_FORM_ERRORS,
SORT_RELATIONS,
SUBMIT,
SUBMIT_SUCCESS,
UNSET_LOADER,
} from './constants';
export function addRelationItem({ key, value }) {
return {
type: ADD_RELATION_ITEM,
key,
value,
};
}
export function changeData({ target }) {
return {
type: CHANGE_DATA,
@ -92,6 +103,14 @@ export function onCancel() {
};
}
export function removeRelationItem({ key, index }) {
return {
type: REMOVE_RELATION_ITEM,
key,
index,
};
}
export function resetProps() {
return {
type: RESET_PROPS,
@ -118,6 +137,15 @@ export function setLoader() {
};
}
export function sortRelations({ key, oldIndex, newIndex }) {
return {
type: SORT_RELATIONS,
key,
oldIndex,
newIndex,
};
}
export function submit() {
return {
type: SUBMIT,

View File

@ -4,6 +4,7 @@
*
*/
export const ADD_RELATION_ITEM = 'ContentManager/EditPage/ADD_RELATION_ITEM';
export const CHANGE_DATA = 'ContentManager/EditPage/CHANGE_DATA';
export const GET_DATA = 'ContentManager/EditPage/GET_DATA';
export const GET_DATA_SUCCEEDED = 'ContentManager/EditPage/GET_DATA_SUCCEEDED';
@ -11,10 +12,12 @@ export const GET_LAYOUT = 'ContentManager/EditPage/GET_LAYOUT';
export const GET_LAYOUT_SUCCEEDED = 'ContentManager/EditPage/GET_LAYOUT_SUCCEEDED';
export const INIT_MODEL_PROPS = 'ContentManager/EditPage/INIT_MODEL_PROPS';
export const ON_CANCEL = 'ContentManager/EditPage/ON_CANCEL';
export const REMOVE_RELATION_ITEM = 'ContentManager/EditPage/REMOVE_RELATION_ITEM';
export const RESET_PROPS = 'ContentManager/EditPage/RESET_PROPS';
export const SET_FILE_RELATIONS = 'ContentManager/EditPage/SET_FILE_RELATIONS';
export const SET_LOADER = 'ContentManager/EditPage/SET_LOADER';
export const SET_FORM_ERRORS = 'ContentManager/EditPage/SET_FORM_ERRORS';
export const SORT_RELATIONS = 'ContentManager/EditPage/SORT_RELATIONS';
export const SUBMIT = 'ContentManager/EditPage/SUBMIT';
export const SUBMIT_SUCCESS = 'ContentManager/EditPage/SUBMIT_SUCCESS';
export const UNSET_LOADER = 'ContentManager/EditPage/UNSET_LOADER';

View File

@ -38,14 +38,17 @@ import { generateRedirectURI } from 'containers/ListPage/utils';
import { checkFormValidity } from 'utils/formValidations';
import {
addRelationItem,
changeData,
getData,
getLayout,
initModelProps,
onCancel,
removeRelationItem,
resetProps,
setFileRelations,
setFormErrors,
sortRelations,
submit,
} from './actions';
@ -181,6 +184,13 @@ export class EditPage extends React.Component {
this.props.setFileRelations(fileRelations);
}
handleAddRelationItem = ({ key, value }) => {
this.props.addRelationItem({
key,
value,
});
}
handleBlur = ({ target }) => {
const defaultValue = get(this.getModelAttribute(target.name), 'default');
@ -246,6 +256,15 @@ export class EditPage extends React.Component {
/* eslint-enable */
}
handleRemoveRelationItem = ({ key, index }) => {
this.props.removeRelationItem({ key, index });
}
handleSortRelations = ({ key, oldIndex, newIndex }) => {
console.log(key, oldIndex, newIndex);
this.props.sortRelations({ key, oldIndex, newIndex });
}
handleSubmit = (e) => {
e.preventDefault();
const formErrors = checkFormValidity(this.generateFormFromRecord(), this.props.editPage.formValidations);
@ -364,7 +383,10 @@ export class EditPage extends React.Component {
changeData={this.props.changeData}
record={editPage.record}
schema={this.getSchema()}
onAddRelationalItem={this.handleAddRelationItem}
onRedirect={this.handleRedirect}
onRemoveRelationItem={this.handleRemoveRelationItem}
onSort={this.handleSortRelations}
/>
)}
</div>
@ -387,6 +409,7 @@ EditPage.defaultProps = {
};
EditPage.propTypes = {
addRelationItem: PropTypes.func.isRequired,
changeData: PropTypes.func.isRequired,
editPage: PropTypes.object.isRequired,
getData: PropTypes.func.isRequired,
@ -396,24 +419,29 @@ EditPage.propTypes = {
location: PropTypes.object.isRequired,
match: PropTypes.object.isRequired,
onCancel: PropTypes.func.isRequired,
removeRelationItem: PropTypes.func.isRequired,
resetProps: PropTypes.func.isRequired,
schema: PropTypes.object,
setFileRelations: PropTypes.func.isRequired,
setFormErrors: PropTypes.func.isRequired,
sortRelations: PropTypes.func.isRequired,
submit: PropTypes.func.isRequired,
};
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
addRelationItem,
changeData,
getData,
getLayout,
initModelProps,
onCancel,
removeRelationItem,
resetProps,
setFileRelations,
setFormErrors,
sortRelations,
submit,
},
dispatch,

View File

@ -6,15 +6,18 @@
import { fromJS, Map, List } from 'immutable';
import {
ADD_RELATION_ITEM,
CHANGE_DATA,
GET_DATA_SUCCEEDED,
GET_LAYOUT_SUCCEEDED,
INIT_MODEL_PROPS,
ON_CANCEL,
REMOVE_RELATION_ITEM,
RESET_PROPS,
SET_FILE_RELATIONS,
SET_FORM_ERRORS,
SET_LOADER,
SORT_RELATIONS,
SUBMIT_SUCCESS,
UNSET_LOADER,
} from './constants';
@ -31,7 +34,7 @@ const initialState = fromJS({
layout: fromJS({}),
modelName: '',
pluginHeaderTitle: 'New Entry',
record: Map({}),
record: fromJS({}),
resetProps: false,
showLoader: false,
source: 'content-manager',
@ -40,15 +43,21 @@ const initialState = fromJS({
function editPageReducer(state = initialState, action) {
switch (action.type) {
case ADD_RELATION_ITEM:
return state
.updateIn(['record', action.key], (list) => {
return list
.push(action.value);
});
case CHANGE_DATA:
return state.updateIn(action.keys, () => action.value);
case GET_DATA_SUCCEEDED:
return state
.update('id', () => action.id)
.update('isLoading', () => false)
.update('initialRecord', () => Map(action.data))
.update('initialRecord', () => fromJS(action.data))
.update('pluginHeaderTitle', () => action.pluginHeaderTitle)
.update('record', () => Map(action.data));
.update('record', () => fromJS(action.data));
case GET_LAYOUT_SUCCEEDED:
return state
.update('isLoading', () => false)
@ -58,7 +67,7 @@ function editPageReducer(state = initialState, action) {
.update('formValidations', () => List(action.formValidations))
.update('isCreating', () => action.isCreating)
.update('modelName', () => action.modelName)
.update('record', () => Map(action.record))
.update('record', () => fromJS(action.record))
.update('source', () => action.source);
case ON_CANCEL:
return state
@ -66,6 +75,12 @@ function editPageReducer(state = initialState, action) {
.update('formErrors', () => List([]))
.update('record', () => state.get('initialRecord'))
.update('resetProps', (v) => v = !v);
case REMOVE_RELATION_ITEM:
return state
.updateIn(['record', action.key], (list) => {
return list
.delete(action.index);
});
case RESET_PROPS:
return initialState.update('layout', () => state.get('layout'));
case SET_FILE_RELATIONS:
@ -77,6 +92,16 @@ function editPageReducer(state = initialState, action) {
case SET_LOADER:
return state
.update('showLoader', () => true);
case SORT_RELATIONS: {
const item = state.getIn(['record', action.key, action.oldIndex]);
return state
.updateIn(['record', action.key], (list) => {
return list
.delete(action.oldIndex)
.insert(action.newIndex, item);
});
}
case SUBMIT_SUCCESS:
return state.update('submitSuccess', (v) => v = !v);
case UNSET_LOADER:

View File

@ -38,6 +38,14 @@
"via": "users",
"plugin": "users-permissions",
"configurable": false
},
"products": {
"via": "users",
"collection": "product"
},
"product": {
"via": "creator",
"model": "product"
}
}
}