mirror of
https://github.com/strapi/strapi.git
synced 2025-10-27 08:02:56 +00:00
Delete useless components in ctm and remove redux from app container in upload
This commit is contained in:
parent
91f9482480
commit
c95d413716
@ -1,660 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* Input
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import PropTypes from 'prop-types';
|
||||
import { get, isEmpty, map, mapKeys, isObject, reject, includes, upperFirst } from 'lodash';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
// import DateTime from 'react-datetime';
|
||||
// import DateTimeStyle from 'react-datetime/css/react-datetime.css';
|
||||
import styles from './styles.scss';
|
||||
|
||||
class Input extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
errors: [],
|
||||
hasInitialValue: false,
|
||||
showPassword: false,
|
||||
isFocus: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.value && !isEmpty(this.props.value)) {
|
||||
this.setState({ hasInitialValue: true });
|
||||
}
|
||||
|
||||
if (!isEmpty(this.props.errors)) {
|
||||
this.setState({ errors: this.props.errors });
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// Check if errors have been updated during validations
|
||||
if (this.props.didCheckErrors !== nextProps.didCheckErrors) {
|
||||
|
||||
// Remove from the state errors that are already set
|
||||
const errors = isEmpty(nextProps.errors) ? [] : nextProps.errors;
|
||||
this.setState({ errors });
|
||||
}
|
||||
}
|
||||
|
||||
handleBlur = ({ target }) => {
|
||||
// prevent error display if input is initially empty
|
||||
if (!isEmpty(target.value) || this.state.hasInitialValue) {
|
||||
// validates basic string validations
|
||||
// add custom logic here such as alerts...
|
||||
const errors = this.validate(target.value);
|
||||
|
||||
this.setState({ errors, hasInitialValue: true });
|
||||
}
|
||||
}
|
||||
|
||||
handleChangeCheckbox = (e) => {
|
||||
const target = {
|
||||
type: 'checkbox',
|
||||
value: !this.props.value,
|
||||
name: this.props.name,
|
||||
};
|
||||
|
||||
this.props.onChange({ target });
|
||||
}
|
||||
|
||||
handleBlurEmail = (e) => {
|
||||
this.setState({ isFocus: !this.state.isFocus });
|
||||
|
||||
if (this.props.handleBlur) {
|
||||
this.props.handleBlur(e);
|
||||
} else {
|
||||
this.handleBlur(e);
|
||||
}
|
||||
}
|
||||
|
||||
handleFocusEmail = (e) => {
|
||||
this.setState({ isFocus: !this.state.isFocus });
|
||||
|
||||
if (this.props.onFocus) {
|
||||
this.props.onFocus(e);
|
||||
}
|
||||
}
|
||||
|
||||
handleToggle = (e) => {
|
||||
const target = {
|
||||
type: 'toggle',
|
||||
name: this.props.name,
|
||||
value: e.target.id === 'on',
|
||||
}
|
||||
|
||||
this.props.onChange({ target });
|
||||
}
|
||||
|
||||
handleShowPassword = () => this.setState({ showPassword: !this.state.showPassword });
|
||||
|
||||
renderErrors = (errorStyles) => { // eslint-disable-line consistent-return
|
||||
if (!this.props.noErrorsDescription) {
|
||||
const divStyle = errorStyles || styles.errorContainer;
|
||||
|
||||
return (
|
||||
map(this.state.errors, (error, key) => {
|
||||
const displayError = isObject(error) && error.id
|
||||
? <FormattedMessage {...error} values={{ errorMessage: error.errorMessage }} />
|
||||
: error;
|
||||
return (
|
||||
<div key={key} className={`form-control-feedback invalid-feedback ${divStyle}`} style={{ display: 'block' }}>{displayError}</div>
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
renderInputCheckbox = (requiredClass, inputDescription) => {
|
||||
const title = !isEmpty(this.props.title) ? <div className={styles.inputTitle}><FormattedMessage id={this.props.title} /></div> : '';
|
||||
const spacer = !inputDescription ? <div /> : <div style={{ marginBottom: '.5rem'}}></div>;
|
||||
|
||||
return (
|
||||
<div className={`${styles.inputCheckbox} ${requiredClass} ${this.props.customBootstrapClass || 'col-md-3'}`}>
|
||||
<div className="form-check">
|
||||
{title}
|
||||
<FormattedMessage id={this.props.label} values={this.props.labelValues}>
|
||||
{(message) => (
|
||||
<label className={`${styles.checkboxLabel} form-check-label`} htmlFor={this.props.name}>
|
||||
<input
|
||||
className="form-check-input"
|
||||
defaultChecked={this.props.value}
|
||||
id={this.props.name}
|
||||
name={this.props.name}
|
||||
onChange={this.handleChangeCheckbox}
|
||||
type="checkbox"
|
||||
/>
|
||||
{message}
|
||||
</label>
|
||||
)}
|
||||
</FormattedMessage>
|
||||
<div className={styles.inputCheckboxDescriptionContainer}>
|
||||
<small>{inputDescription}</small>
|
||||
</div>
|
||||
</div>
|
||||
{spacer}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// renderInputDate = (requiredClass, inputDescription) => {
|
||||
// let spacer = !isEmpty(this.props.inputDescription) ? <div className={styles.spacer} /> : <div />;
|
||||
//
|
||||
// if (!this.props.noErrorsDescription && !isEmpty(this.state.errors)) {
|
||||
// spacer = <div />;
|
||||
// }
|
||||
//
|
||||
// const value = isObject(this.props.value) && this.props.value._isAMomentObject === true ?
|
||||
// this.props.value :
|
||||
// moment(this.props.value);
|
||||
//
|
||||
// return (
|
||||
// <div className={`${styles.inputDate} ${styles.input} ${this.props.customBootstrapClass || 'col-md-4'} ${requiredClass}`}>
|
||||
// <label htmlFor={this.props.label}>
|
||||
// <FormattedMessage id={`${this.props.label}`} defaultMessage={upperFirst(this.props.label)} />
|
||||
// </label>
|
||||
// <DateTime
|
||||
// value={value}
|
||||
// dateFormat='YYYY-MM-DD'
|
||||
// timeFormat='HH:mm:ss'
|
||||
// tabIndex={this.props.tabIndex}
|
||||
// utc={true}
|
||||
// inputProps={{
|
||||
// placeholder: this.props.placeholder,
|
||||
// className: 'form-control',
|
||||
// name: this.props.name,
|
||||
// id: this.props.label,
|
||||
// }}
|
||||
// onChange={(moment) => this.props.onChange({ target: {
|
||||
// name: this.props.name,
|
||||
// value: moment
|
||||
// }})}
|
||||
// />
|
||||
// <div className={styles.inputDescriptionContainer}>
|
||||
// <small>{inputDescription}</small>
|
||||
// </div>
|
||||
// {this.renderErrors(styles.errorContainerTextArea)}
|
||||
// {spacer}
|
||||
// </div>
|
||||
// )
|
||||
// }
|
||||
|
||||
renderInputEmail = (requiredClass, inputDescription, handleBlur) => {
|
||||
let spacer = !isEmpty(this.props.inputDescription) ? <div className={styles.spacer} /> : <div />;
|
||||
|
||||
if (!this.props.noErrorsDescription && !isEmpty(this.state.errors)) {
|
||||
spacer = <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${styles.input} ${this.props.customBootstrapClass || 'col-md-6'} ${requiredClass}`}>
|
||||
<label htmlFor={this.props.label}>
|
||||
<FormattedMessage id={`${this.props.label}`} />
|
||||
</label>
|
||||
<div className={`input-group ${styles.input} ${styles.inputEmail}`} style={{ marginBottom: '1rem'}}>
|
||||
<span className={`input-group-addon ${styles.addonEmail} ${!this.props.deactivateErrorHighlight && !isEmpty(this.state.errors) && !this.state.isFocus ? styles.errorAddon: ''}`} />
|
||||
<FormattedMessage id={this.props.placeholder || this.props.label} values={this.props.labelValues}>
|
||||
{(placeholder) => (
|
||||
<input
|
||||
className={`form-control ${!this.props.deactivateErrorHighlight && !isEmpty(this.state.errors) ? `form-control-danger is-invalid ${styles.error}`: ''}`}
|
||||
onChange={this.props.onChange}
|
||||
value={this.props.value}
|
||||
name={this.props.name}
|
||||
id={this.props.label}
|
||||
onBlur={this.handleBlurEmail}
|
||||
onFocus={this.handleFocusEmail}
|
||||
placeholder={placeholder}
|
||||
disabled={this.props.disabled}
|
||||
type="email"
|
||||
autoFocus={this.props.autoFocus}
|
||||
tabIndex={this.props.tabIndex}
|
||||
/>
|
||||
)}
|
||||
</FormattedMessage>
|
||||
</div>
|
||||
<div className={styles.inputDescriptionContainer}>
|
||||
<small>{inputDescription}</small>
|
||||
</div>
|
||||
{this.renderErrors()}
|
||||
{spacer}
|
||||
</div>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
renderFormattedInput = (handleBlur, inputValue, placeholder) => (
|
||||
<FormattedMessage id={`${placeholder}`} defaultMessage={placeholder}>
|
||||
{(message) => (
|
||||
<input
|
||||
name={this.props.name}
|
||||
id={this.props.label}
|
||||
onBlur={handleBlur}
|
||||
onFocus={this.props.onFocus}
|
||||
onChange={this.props.onChange}
|
||||
value={inputValue}
|
||||
type={this.props.type}
|
||||
className={`form-control ${!this.props.deactivateErrorHighlight && !isEmpty(this.state.errors)? 'form-control-danger is-invalid' : ''}`}
|
||||
placeholder={message}
|
||||
autoComplete="off"
|
||||
disabled={this.props.disabled}
|
||||
autoFocus={this.props.autoFocus}
|
||||
tabIndex={this.props.tabIndex}
|
||||
/>
|
||||
)}
|
||||
</FormattedMessage>
|
||||
)
|
||||
|
||||
renderInputPassword = (requiredClass, inputDescription, handleBlur) => {
|
||||
let spacer = !isEmpty(this.props.inputDescription) ? <div className={styles.spacer} /> : <div />;
|
||||
|
||||
if (!this.props.noErrorsDescription && !isEmpty(this.state.errors)) {
|
||||
spacer = <div />;
|
||||
}
|
||||
|
||||
const color = this.state.showPassword ? { color: 'black' } : { color: '#9EA7B8' };
|
||||
const type = this.state.showPassword ? 'text' : 'password';
|
||||
|
||||
return (
|
||||
<div className={`${styles.input} ${this.props.customBootstrapClass || 'col-md-6'} ${requiredClass}`}>
|
||||
<label htmlFor={this.props.label}>
|
||||
<FormattedMessage id={`${this.props.label}`} defaultMessage={upperFirst(this.props.label)} />
|
||||
</label>
|
||||
<FormattedMessage id={this.props.placeholder || this.props.label} values={this.props.labelValues}>
|
||||
{(placeholder) => (
|
||||
<input
|
||||
autoComplete="new-password"
|
||||
className={`form-control ${!this.props.deactivateErrorHighlight && !isEmpty(this.state.errors) ? 'is-invalid': ''}`}
|
||||
onChange={this.props.onChange}
|
||||
value={this.props.value}
|
||||
name={this.props.name}
|
||||
id={this.props.label}
|
||||
onBlur={handleBlur}
|
||||
onFocus={this.props.onFocus}
|
||||
placeholder={placeholder}
|
||||
disabled={this.props.disabled}
|
||||
type={type}
|
||||
autoFocus={this.props.autoFocus}
|
||||
tabIndex={this.props.tabIndex}
|
||||
/>
|
||||
)}
|
||||
</FormattedMessage>
|
||||
<div className={styles.inputDescriptionContainer}>
|
||||
<small>{inputDescription}</small>
|
||||
</div>
|
||||
{this.renderErrors()}
|
||||
{spacer}
|
||||
<div className={styles.insideInput} onClick={this.handleShowPassword} style={color}>
|
||||
<i className="fa fa-eye" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderInputSelect = (requiredClass, inputDescription, handleBlur) => {
|
||||
let spacer = !isEmpty(this.props.inputDescription) ? <div className={styles.spacer} /> : <div />;
|
||||
|
||||
if (!this.props.noErrorsDescription && !isEmpty(this.state.errors)) {
|
||||
spacer = <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${styles.input} ${requiredClass} ${this.props.customBootstrapClass || 'col-md-6'}`}>
|
||||
<label htmlFor={this.props.label}>
|
||||
<FormattedMessage id={`${this.props.label}`} />
|
||||
</label>
|
||||
<select
|
||||
className={`form-control ${!this.props.deactivateErrorHighlight && !isEmpty(this.state.errors) ? 'is-invalid': ''}`}
|
||||
id={this.props.label}
|
||||
name={this.props.name}
|
||||
onChange={this.props.onChange}
|
||||
value={this.props.value}
|
||||
disabled={this.props.disabled}
|
||||
onBlur={handleBlur}
|
||||
tabIndex={this.props.tabIndex}
|
||||
autoFocus={this.props.autoFocus}
|
||||
>
|
||||
{map(this.props.selectOptions, (option, key) => (
|
||||
option.name ?
|
||||
<FormattedMessage id={option.name} defaultMessage={option.name} values={{ option: option.name }} key={key}>
|
||||
{(message) => (
|
||||
<option value={option.value}>
|
||||
{message}
|
||||
</option>
|
||||
)}
|
||||
</FormattedMessage> :
|
||||
<option value={option.value} key={key}>{option.value}</option>
|
||||
))}
|
||||
</select>
|
||||
<div className={styles.inputDescriptionContainer}>
|
||||
<small>{inputDescription}</small>
|
||||
</div>
|
||||
{this.renderErrors()}
|
||||
{spacer}
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
renderInputSearch = (requiredClass, inputDescription, handleBlur) => {
|
||||
let spacer = !isEmpty(this.props.inputDescription) ? <div className={styles.spacer} /> : <div />;
|
||||
|
||||
if (!this.props.noErrorsDescription && !isEmpty(this.state.errors)) {
|
||||
spacer = <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${styles.input} ${this.props.customBootstrapClass || 'col-md-6'} ${requiredClass}`}>
|
||||
<label htmlFor={this.props.label}>
|
||||
<FormattedMessage id={`${this.props.label}`} defaultMessage={upperFirst(this.props.label)} />
|
||||
</label>
|
||||
<div className={`input-group ${styles.inputSearch}`} style={{ marginBottom: '1rem'}}>
|
||||
<span className={`input-group-addon ${styles.addonSearch}`} />
|
||||
<FormattedMessage id={this.props.placeholder || this.props.label} defaultMessage={this.props.label}>
|
||||
{(placeholder) => (
|
||||
<input
|
||||
className={`form-control ${!this.props.deactivateErrorHighlight && !isEmpty(this.state.errors) ? 'is-invalid': ''}`}
|
||||
onChange={this.props.onChange}
|
||||
value={this.props.value}
|
||||
name={this.props.name}
|
||||
id={this.props.label}
|
||||
onBlur={handleBlur}
|
||||
onFocus={this.props.onFocus}
|
||||
placeholder={placeholder}
|
||||
disabled={this.props.disabled}
|
||||
type="text"
|
||||
autoFocus={this.props.autoFocus}
|
||||
tabIndex={this.props.tabIndex}
|
||||
/>
|
||||
)}
|
||||
</FormattedMessage>
|
||||
</div>
|
||||
<div className={styles.inputDescriptionContainer}>
|
||||
<small>{inputDescription}</small>
|
||||
</div>
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
{spacer}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderInputTextArea = (requiredClass, inputDescription, handleBlur) => {
|
||||
let spacer = !isEmpty(this.props.inputDescription) ? <div className={styles.spacer} /> : <div />;
|
||||
|
||||
if (!this.props.noErrorsDescription && !isEmpty(this.state.errors)) {
|
||||
spacer = <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${styles.inputTextArea} ${this.props.customBootstrapClass || 'col-md-6'} ${requiredClass}`}>
|
||||
<label htmlFor={this.props.label}>
|
||||
<FormattedMessage id={`${this.props.label}`} defaultMessage={upperFirst(this.props.label)} />
|
||||
</label>
|
||||
<FormattedMessage id={this.props.placeholder || this.props.label}>
|
||||
{(placeholder) => (
|
||||
<textarea
|
||||
className={`form-control ${!this.props.deactivateErrorHighlight && !isEmpty(this.state.errors) ? 'is-invalid': ''}`}
|
||||
onChange={this.props.onChange}
|
||||
value={this.props.value}
|
||||
name={this.props.name}
|
||||
id={this.props.label}
|
||||
onBlur={handleBlur}
|
||||
onFocus={this.props.onFocus}
|
||||
placeholder={placeholder}
|
||||
disabled={this.props.disabled}
|
||||
autoFocus={this.props.autoFocus}
|
||||
tabIndex={this.props.tabIndex}
|
||||
/>
|
||||
)}
|
||||
</FormattedMessage>
|
||||
<div className={styles.inputTextAreaDescriptionContainer}>
|
||||
<small>{inputDescription}</small>
|
||||
</div>
|
||||
{this.renderErrors(styles.errorContainerTextArea)}
|
||||
{spacer}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderInputToggle = () => {
|
||||
const btnClassOff = this.props.value ? 'btn' : `btn ${styles.gradientOff}`;
|
||||
const btnClassOn = this.props.value ? `btn ${styles.gradientOn}` : 'btn';
|
||||
const spacer = this.props.inputDescription ? <div className={styles.spacer} /> : <div />;
|
||||
const inputDescription = this.props.inputDescription ?
|
||||
<FormattedMessage id={this.props.inputDescription} /> : '';
|
||||
|
||||
return (
|
||||
<div className={`${this.props.customBootstrapClass || 'col-md-6'} ${styles.inputToggle}`}>
|
||||
<div className={styles.toggleLabel}>
|
||||
<FormattedMessage id={this.props.label} values={this.props.labelValues} />
|
||||
</div>
|
||||
<div className={`btn-group ${styles.inputToggleButtons}`}>
|
||||
<button type="button" className={btnClassOff} id="off" onClick={this.handleToggle}>OFF</button>
|
||||
<button type="button" className={btnClassOn} id="on" onClick={this.handleToggle}>ON</button>
|
||||
</div>
|
||||
<div className={styles.inputDescriptionContainer}>
|
||||
<small>{inputDescription}</small>
|
||||
</div>
|
||||
{spacer}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const inputValue = this.props.value || '';
|
||||
// override default onBlur
|
||||
const handleBlur = this.props.onBlur || this.handleBlur;
|
||||
const placeholder = this.props.placeholder || this.props.label;
|
||||
const label = this.props.label ?
|
||||
<label htmlFor={this.props.label}><FormattedMessage id={`${this.props.label}`} defaultMessage={upperFirst(this.props.label)} /></label>
|
||||
: <label htmlFor={this.props.label} />;
|
||||
|
||||
const requiredClass = get(this.props.validations, 'required') && this.props.addRequiredInputDesign ?
|
||||
styles.requiredClass : '';
|
||||
|
||||
const input = placeholder ? this.renderFormattedInput(handleBlur, inputValue, placeholder)
|
||||
: <input
|
||||
name={this.props.name}
|
||||
id={this.props.label}
|
||||
onBlur={handleBlur}
|
||||
onFocus={this.props.onFocus}
|
||||
onChange={this.props.onChange}
|
||||
value={inputValue}
|
||||
type={this.props.type}
|
||||
className={`form-control ${!this.props.deactivateErrorHighlight && !isEmpty(this.state.errors) ? 'is-invalid': ''}`}
|
||||
placeholder={placeholder}
|
||||
disabled={this.props.disabled}
|
||||
autoFocus={this.props.autoFocus}
|
||||
tabIndex={this.props.tabIndex}
|
||||
/>;
|
||||
|
||||
const link = !isEmpty(this.props.linkContent) ? <a href={this.props.linkContent.link} target="_blank"><FormattedMessage id={this.props.linkContent.description} /></a> : '';
|
||||
|
||||
let inputDescription = !isEmpty(this.props.inputDescription) ? <FormattedMessage id={this.props.inputDescription} /> : '';
|
||||
|
||||
if (!isEmpty(this.props.linkContent) && !isEmpty(this.props.inputDescription)) {
|
||||
inputDescription = <FormattedMessage id='input.description' defaultMessage={`{description}, {link}`} values={{link, description: <FormattedMessage id={this.props.inputDescription} /> }} />;
|
||||
}
|
||||
|
||||
let spacer = !isEmpty(this.props.inputDescription) ? <div className={styles.spacer} /> : <div />;
|
||||
|
||||
if (!this.props.noErrorsDescription && !isEmpty(this.state.errors)) {
|
||||
spacer = <div />;
|
||||
}
|
||||
|
||||
if (this.props.search) {
|
||||
return this.renderInputSearch(requiredClass, inputDescription, handleBlur);
|
||||
}
|
||||
|
||||
switch (this.props.type) {
|
||||
case 'select':
|
||||
return this.renderInputSelect(requiredClass, inputDescription, handleBlur);
|
||||
case 'textarea':
|
||||
return this.renderInputTextArea(requiredClass, inputDescription, handleBlur);
|
||||
case 'checkbox':
|
||||
return this.renderInputCheckbox(requiredClass, inputDescription);
|
||||
// case 'date':
|
||||
// return this.renderInputDate(requiredClass, inputDescription);
|
||||
case 'password':
|
||||
return this.renderInputPassword(requiredClass, inputDescription, handleBlur);
|
||||
case 'toggle':
|
||||
return this.renderInputToggle();
|
||||
case 'email':
|
||||
return this.renderInputEmail(requiredClass, inputDescription, handleBlur);
|
||||
case 'search':
|
||||
return this.renderInputSearch(requiredClass, inputDescription, handleBlur)
|
||||
default:
|
||||
}
|
||||
|
||||
const addonInput = this.props.addon ?
|
||||
<div className={`input-group ${styles.input}`} style={{ marginBottom: '1rem'}}>
|
||||
<span className={`input-group-addon ${styles.addon}`}><FormattedMessage id={this.props.addon} /></span>
|
||||
{input}
|
||||
</div> : input;
|
||||
return (
|
||||
<div className={`${styles.input} ${this.props.customBootstrapClass || 'col-md-6'} ${requiredClass}`}>
|
||||
{label}
|
||||
|
||||
{addonInput}
|
||||
<div className={styles.inputDescriptionContainer}>
|
||||
<small>{inputDescription}</small>
|
||||
</div>
|
||||
{this.renderErrors()}
|
||||
{spacer}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
validate = (value) => {
|
||||
let errors = [];
|
||||
|
||||
const emailRegex = new RegExp(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/);
|
||||
// handle i18n
|
||||
const requiredError = { id: 'components.Input.error.validation.required' };
|
||||
|
||||
mapKeys(this.props.validations, (validationValue, validationKey) => {
|
||||
switch (validationKey) {
|
||||
case 'max':
|
||||
if (parseInt(value, 10) > validationValue) {
|
||||
errors.push({ id: 'components.Input.error.validation.max' });
|
||||
}
|
||||
break;
|
||||
case 'maxLength':
|
||||
if (value.length > validationValue) {
|
||||
errors.push({ id: 'components.Input.error.validation.maxLength' });
|
||||
}
|
||||
break;
|
||||
case 'min':
|
||||
if (parseInt(value, 10) < validationValue) {
|
||||
errors.push({ id: 'components.Input.error.validation.min' });
|
||||
}
|
||||
break;
|
||||
case 'minLength':
|
||||
if (value.length < validationValue) {
|
||||
errors.push({ id: 'components.Input.error.validation.minLength' });
|
||||
}
|
||||
break;
|
||||
case 'required':
|
||||
if (value.length === 0) {
|
||||
errors.push({ id: 'components.Input.error.validation.required' });
|
||||
}
|
||||
break;
|
||||
case 'regex':
|
||||
if (!new RegExp(validationValue).test(value)) {
|
||||
errors.push({ id: 'components.Input.error.validation.regex' });
|
||||
}
|
||||
break;
|
||||
default:
|
||||
errors = [];
|
||||
}
|
||||
});
|
||||
|
||||
if (this.props.type === 'email' && !emailRegex.test(value)) {
|
||||
errors.push({ id: 'components.Input.error.validation.email' });
|
||||
}
|
||||
|
||||
if (includes(errors, requiredError)) {
|
||||
errors = reject(errors, (error) => error !== requiredError);
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
|
||||
Input.propTypes = {
|
||||
addon: PropTypes.oneOfType([
|
||||
PropTypes.bool,
|
||||
PropTypes.string,
|
||||
]),
|
||||
addRequiredInputDesign: PropTypes.bool,
|
||||
autoFocus: PropTypes.bool,
|
||||
customBootstrapClass: PropTypes.string,
|
||||
deactivateErrorHighlight: PropTypes.bool,
|
||||
didCheckErrors: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
errors: PropTypes.array,
|
||||
inputDescription: PropTypes.string,
|
||||
label: PropTypes.string.isRequired,
|
||||
labelValues: PropTypes.object,
|
||||
linkContent: PropTypes.shape({
|
||||
link: PropTypes.string,
|
||||
description: PropTypes.string,
|
||||
}),
|
||||
name: PropTypes.string.isRequired,
|
||||
noErrorsDescription: PropTypes.bool,
|
||||
onBlur: PropTypes.oneOfType([
|
||||
PropTypes.bool,
|
||||
PropTypes.func,
|
||||
]),
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onFocus: PropTypes.oneOfType([
|
||||
PropTypes.bool,
|
||||
PropTypes.func,
|
||||
]),
|
||||
placeholder: PropTypes.string,
|
||||
search: PropTypes.bool,
|
||||
selectOptions: PropTypes.array,
|
||||
selectOptionsFetchSucceeded: PropTypes.bool,
|
||||
tabIndex: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
type: PropTypes.string.isRequired,
|
||||
validations: PropTypes.object.isRequired,
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.bool,
|
||||
PropTypes.number,
|
||||
]),
|
||||
};
|
||||
|
||||
Input.defaultProps = {
|
||||
addon: false,
|
||||
addRequiredInputDesign: false,
|
||||
autoFocus: false,
|
||||
deactivateErrorHighlight: false,
|
||||
didCheckErrors: false,
|
||||
disabled: false,
|
||||
errors: [],
|
||||
inputDescription: '',
|
||||
labelValues: {},
|
||||
linkContent: {},
|
||||
noErrorsDescription: false,
|
||||
onBlur: false,
|
||||
onFocus: () => {},
|
||||
placeholder: '',
|
||||
search: false,
|
||||
selectOptions: [],
|
||||
selectOptionsFetchSucceeded: false,
|
||||
tabIndex: '0',
|
||||
value: ''
|
||||
};
|
||||
|
||||
export default Input;
|
||||
@ -1,371 +0,0 @@
|
||||
/* stylelint-disable */
|
||||
|
||||
.error {
|
||||
border-left: none!important;
|
||||
}
|
||||
|
||||
.errorAddon {
|
||||
border: 1px solid #ff203c!important;
|
||||
border-right: none!important;
|
||||
}
|
||||
|
||||
.errorContainer {
|
||||
margin-top: .5rem;
|
||||
margin-bottom: .5rem;
|
||||
line-height: 1.3rem;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.errorContainerTextArea {
|
||||
margin-top: .5rem;
|
||||
margin-bottom: .5rem;
|
||||
line-height: 1.3rem;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.addon {
|
||||
width: 5.9rem;
|
||||
height: 3.4rem;
|
||||
margin-top: .9rem;
|
||||
background-color: rgba(16, 22, 34, 0.02);
|
||||
border: 1px solid #E3E9F3;
|
||||
border-radius: 0.25rem;
|
||||
color: rgba(16, 22, 34, 0.5);
|
||||
line-height: 3.2rem;
|
||||
font-size: 1.3rem;
|
||||
font-family: 'Lato';
|
||||
font-weight: 600!important;
|
||||
text-transform: capitalize;
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.addonSearch {
|
||||
width: 3.2rem;
|
||||
height: 3.4rem;
|
||||
margin-top: .9rem;
|
||||
background-color: rgba(16, 22, 34, 0.02);
|
||||
border: 1px solid #E3E9F3!important;
|
||||
border-right: none !important;
|
||||
border-radius: 0.25rem;
|
||||
border-bottom-left-radius: 0;
|
||||
color: rgba(16, 22, 34, 0.5);
|
||||
line-height: 3.2rem;
|
||||
font-size: 1.3rem;
|
||||
font-family: 'Lato';
|
||||
font-weight: 600!important;
|
||||
text-transform: capitalize;
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
&:before {
|
||||
content: '\F002';
|
||||
display: inline-table;
|
||||
margin-top: 0px;
|
||||
margin-left: 2px;
|
||||
color: #B3B5B9;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
font-family: 'FontAwesome';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
}
|
||||
|
||||
.addonEmail {
|
||||
width: 3.4rem;
|
||||
height: 3.4rem;
|
||||
margin-top: .9rem;
|
||||
padding-left: 0.9rem;
|
||||
background-color: rgba(16, 22, 34, 0.02);
|
||||
border: 1px solid #E3E9F3;
|
||||
border-radius: 0.25rem;
|
||||
color: rgba(16, 22, 34, 0.5);
|
||||
line-height: 3.2rem;
|
||||
font-size: 1.3rem;
|
||||
font-family: 'Lato';
|
||||
font-weight: 600!important;
|
||||
text-transform: capitalize;
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
&:after {
|
||||
content: '@';
|
||||
display: inline-table;
|
||||
color: #B3B5B9;
|
||||
font-size: 16px;
|
||||
font-weight: 900;
|
||||
font-family: Lato;
|
||||
}
|
||||
|
||||
& + input {
|
||||
border-left: 0px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.insideInput {
|
||||
position: absolute;
|
||||
top: 3.5rem;
|
||||
right: 2.7rem;
|
||||
color: #9EA7B8;
|
||||
&:hover {
|
||||
color: black!important;
|
||||
}
|
||||
}
|
||||
|
||||
.inputEmail {
|
||||
> input {
|
||||
&:focus {
|
||||
border-color: #E3E9F3;
|
||||
}
|
||||
}
|
||||
|
||||
& + span {
|
||||
border-color: #E3E9F3;
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
min-width: 200px;
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 1.3rem;
|
||||
|
||||
label {
|
||||
margin-bottom: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
input {
|
||||
height: 3.4rem;
|
||||
margin-top: .9rem;
|
||||
padding-left: 1rem;
|
||||
background-size: 0 !important;
|
||||
border: 1px solid #E3E9F3;
|
||||
border-radius: 0.25rem;
|
||||
line-height: 3.4rem;
|
||||
font-size: 1.3rem;
|
||||
font-family: 'Lato' !important;
|
||||
box-shadow: 0px 1px 1px rgba(104, 118, 142, 0.05);
|
||||
}
|
||||
|
||||
select {
|
||||
height: 3.4rem !important;
|
||||
margin-top: .9rem;
|
||||
padding-top: 0rem;
|
||||
padding-left: 1rem;
|
||||
background-position: right -1px center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url('../../assets/images/background_input.svg');
|
||||
border: 1px solid #E3E9F3;
|
||||
border-radius: 0.25rem;
|
||||
line-height: 3.2rem;
|
||||
font-size: 1.3rem;
|
||||
font-family: 'Lato' !important;
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
box-shadow: 0px 1px 1px rgba(104, 118, 142, 0.05);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.inputCheckbox {
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 1.3rem;
|
||||
font-family: 'Lato';
|
||||
|
||||
> div {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.checkboxLabel {
|
||||
font-weight: 400;
|
||||
|
||||
> input {
|
||||
margin-right: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.inputCheckboxDescriptionContainer {
|
||||
width: 150%;
|
||||
padding-top: .2rem;
|
||||
padding-left: 2.5rem;
|
||||
line-height: 1.2rem;
|
||||
cursor: pointer;
|
||||
> small {
|
||||
color: #9EA7B8;
|
||||
font-family: 'Lato';
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.inputDescriptionContainer {
|
||||
width: 200%;
|
||||
margin-top: 1.3rem;
|
||||
line-height: 1.2rem;
|
||||
|
||||
> small {
|
||||
color: #9EA7B8;
|
||||
font-family: 'Lato';
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.inputTextAreaDescriptionContainer {
|
||||
margin-top: 1.3rem;
|
||||
line-height: 1.2rem;
|
||||
|
||||
> small {
|
||||
color: #9EA7B8;
|
||||
font-family: 'Lato';
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.inputTextArea {
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 1.3rem;
|
||||
font-family: 'Lato';
|
||||
|
||||
> label {
|
||||
margin-bottom: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
> textarea {
|
||||
height: 10.6rem;
|
||||
margin-top: .9rem;
|
||||
padding: .7rem 1rem .3rem 1rem;
|
||||
background-size: 0 !important;
|
||||
border: 1px solid #E3E9F3;
|
||||
border-radius: 0.25rem;
|
||||
line-height: 1.8rem;
|
||||
font-size: 1.3rem;
|
||||
font-family: 'Lato' !important;
|
||||
box-shadow: 0px 1px 1px rgba(104, 118, 142, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.inputDate{
|
||||
> div:first-of-type{
|
||||
&:before{
|
||||
content: '\f073';
|
||||
position: absolute;
|
||||
left: 1px; top: 1px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 3px 0px 0px 3px;
|
||||
background: #FAFAFB;
|
||||
color: #B3B5B9;
|
||||
text-align: center;
|
||||
font-family: 'FontAwesome';
|
||||
font-size: 1.4rem;
|
||||
line-height: 32px;
|
||||
-webkit-font-smoothing: none;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding-left: 42px;
|
||||
box-shadow: 0px 1px 1px rgba(104, 118, 142, 0.05);
|
||||
&:focus{
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.inputSearch {
|
||||
min-width: 200px;
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 1.3rem;
|
||||
|
||||
label {
|
||||
margin-bottom: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
input {
|
||||
height: 3.4rem;
|
||||
margin-top: .9rem;
|
||||
padding-left: 1rem;
|
||||
background-size: 0 !important;
|
||||
border: 1px solid #E3E9F3;
|
||||
// border-left: none;
|
||||
border-radius: 0.25rem;
|
||||
border-bottom-right-radius: 0;
|
||||
line-height: 3.4rem;
|
||||
font-size: 1.3rem;
|
||||
font-family: 'Lato' !important;
|
||||
box-shadow: 0px 1px 1px rgba(104, 118, 142, 0.05);
|
||||
&:focus {
|
||||
// border-left: 1px solid #78caff!important;
|
||||
border-color: #78caff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.inputTitle {
|
||||
margin-bottom: 1.7rem;
|
||||
font-weight: 500;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
height: .5rem;
|
||||
}
|
||||
|
||||
.inputToggle {
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 1.3rem;
|
||||
|
||||
}
|
||||
|
||||
.inputToggleButtons {
|
||||
padding-top: 9px;
|
||||
|
||||
> button {
|
||||
width: 5.3rem;
|
||||
height: 3.4rem;
|
||||
margin-bottom: 2.8rem;
|
||||
padding: 0;
|
||||
border: 1px solid #E3E9F3;
|
||||
border-radius: 0.25rem;
|
||||
color: #CED3DB;
|
||||
background-color: white;
|
||||
box-shadow: 0px 1px 1px rgba(104, 118, 142, 0.05);
|
||||
font-weight: 600;
|
||||
font-size: 1.2rem;
|
||||
letter-spacing: 0.1rem;
|
||||
font-family: Lato;
|
||||
line-height: 3.4rem;
|
||||
cursor: pointer;
|
||||
&:first-of-type {
|
||||
border-right: none;
|
||||
}
|
||||
&:nth-of-type(2) {
|
||||
border-left: none;
|
||||
}
|
||||
&:hover {
|
||||
z-index: 0 !important;
|
||||
}
|
||||
&:focus {
|
||||
outline: 0;
|
||||
box-shadow: 0 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toggleLabel {
|
||||
margin-bottom: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.gradientOff {
|
||||
background-image: linear-gradient( to bottom right, #F65A1D, #F68E0E );
|
||||
color: white !important;
|
||||
box-shadow: inset -1px 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.gradientOn {
|
||||
background-image: linear-gradient( to bottom right, #005EEA, #0097F6);
|
||||
color: white !important;
|
||||
box-shadow: inset 1px 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
@ -1,122 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* EditForm
|
||||
*
|
||||
*/
|
||||
|
||||
// Dependencies.
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { findIndex, get, omit, isFunction, merge } from 'lodash';
|
||||
|
||||
// Components.
|
||||
import Input from 'components/Input';
|
||||
|
||||
// Utils.
|
||||
import getQueryParameters from 'utils/getQueryParameters';
|
||||
|
||||
// Styles.
|
||||
import styles from './styles.scss';
|
||||
|
||||
class EditForm extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
getInputType = (type = '') => {
|
||||
switch (type.toLowerCase()) {
|
||||
case 'password':
|
||||
return 'password';
|
||||
case 'boolean':
|
||||
return 'checkbox';
|
||||
case 'text':
|
||||
return 'textarea';
|
||||
case 'email':
|
||||
return 'email';
|
||||
case 'string':
|
||||
return 'text';
|
||||
case 'date':
|
||||
case 'datetime':
|
||||
return 'date';
|
||||
case 'float':
|
||||
case 'integer':
|
||||
case 'bigint':
|
||||
case 'decimal':
|
||||
return 'number';
|
||||
default:
|
||||
return 'text';
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const source = getQueryParameters(this.props.location.search, 'source');
|
||||
const currentSchema = source !== 'content-manager' ? get(this.props.schema, ['plugins', source, this.props.currentModelName]) : get(this.props.schema, [this.props.currentModelName]);
|
||||
const currentLayout = get(this.props.layout, [this.props.currentModelName, 'attributes']);
|
||||
|
||||
// Remove `id` field
|
||||
const displayedFields = merge(get(currentLayout), omit(currentSchema.fields, 'id'));
|
||||
|
||||
// List fields inputs
|
||||
const fields = Object.keys(displayedFields).map((attr, key) => {
|
||||
const details = displayedFields[attr];
|
||||
const errorIndex = findIndex(this.props.formErrors, ['name', attr]);
|
||||
const errors = errorIndex !== -1 ? this.props.formErrors[errorIndex].errors : [];
|
||||
const validationsIndex = findIndex(this.props.formValidations, ['name', attr]);
|
||||
const validations = get(this.props.formValidations[validationsIndex], 'validations') || {};
|
||||
|
||||
const layout = Object.keys(get(currentLayout, attr, {})).reduce((acc, current) => {
|
||||
acc[current] = isFunction(currentLayout[attr][current]) ?
|
||||
currentLayout[attr][current](this) :
|
||||
currentLayout[attr][current];
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return (
|
||||
<Input
|
||||
autoFocus={key === 0}
|
||||
key={attr}
|
||||
type={get(layout, 'type', this.getInputType(details.type))}
|
||||
label={get(layout, 'label') || details.label || ''}
|
||||
name={attr}
|
||||
customBootstrapClass={get(layout, 'className') || ''}
|
||||
value={this.props.record.get(attr) || ''}
|
||||
placeholder={get(layout, 'placeholder') || details.placeholder || details.label || attr || ''}
|
||||
onChange={this.props.onChange}
|
||||
validations={get(layout, 'validations') || validations}
|
||||
errors={errors}
|
||||
didCheckErrors={this.props.didCheckErrors}
|
||||
pluginID="content-manager"
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<form className={styles.form} onSubmit={this.props.onSubmit}>
|
||||
<div className='row'>
|
||||
{fields}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
EditForm.propTypes = {
|
||||
currentModelName: PropTypes.string.isRequired,
|
||||
didCheckErrors: PropTypes.bool.isRequired,
|
||||
formErrors: PropTypes.array.isRequired,
|
||||
formValidations: PropTypes.array.isRequired,
|
||||
layout: PropTypes.object.isRequired,
|
||||
location: PropTypes.shape({
|
||||
search: PropTypes.string,
|
||||
}).isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
record: PropTypes.oneOfType([
|
||||
PropTypes.object,
|
||||
PropTypes.bool,
|
||||
]).isRequired,
|
||||
schema: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default EditForm;
|
||||
@ -1,4 +0,0 @@
|
||||
.form { /* stylelint-disable */
|
||||
width: 100%;
|
||||
padding: 0 15px;
|
||||
}
|
||||
@ -1,109 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* EditFormRelations
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { get, map, size } from 'lodash';
|
||||
|
||||
// Components.
|
||||
import SelectOne from 'components/SelectOne';
|
||||
import SelectMany from 'components/SelectMany';
|
||||
|
||||
// Utils.
|
||||
import getQueryParameters from 'utils/getQueryParameters';
|
||||
|
||||
// Style.
|
||||
import styles from './styles.scss';
|
||||
|
||||
class EditFormRelations extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
componentDidMount() {
|
||||
const source = getQueryParameters(this.props.location.search, 'source');
|
||||
const currentSchema = source !== 'content-manager' ? get(this.props.schema, ['plugins', source, this.props.currentModelName]) : get(this.props.schema, [this.props.currentModelName]);
|
||||
|
||||
if (size(get(currentSchema, ['relations'])) === 0 && !this.props.isNull) {
|
||||
this.props.toggleNull();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const source = getQueryParameters(this.props.location.search, 'source');
|
||||
const currentSchema = source !== 'content-manager' ? get(this.props.schema, ['plugins', source, this.props.currentModelName]) : get(this.props.schema, [this.props.currentModelName]);
|
||||
|
||||
const relations = map(currentSchema.relations, (relation, i) => {
|
||||
|
||||
switch (relation.nature) {
|
||||
case 'oneWay':
|
||||
case 'oneToOne':
|
||||
case 'manyToOne':
|
||||
if (relation.dominant) {
|
||||
return (
|
||||
<SelectOne
|
||||
currentModelName={this.props.currentModelName}
|
||||
key={i}
|
||||
record={this.props.record}
|
||||
relation={relation}
|
||||
schema={this.props.schema}
|
||||
setRecordAttribute={this.props.setRecordAttribute}
|
||||
location={this.props.location}
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'oneToMany':
|
||||
case 'manyToMany':
|
||||
return (
|
||||
<SelectMany
|
||||
currentModelName={this.props.currentModelName}
|
||||
key={i}
|
||||
record={this.props.record}
|
||||
relation={relation}
|
||||
schema={this.props.schema}
|
||||
setRecordAttribute={this.props.setRecordAttribute}
|
||||
location={this.props.location}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
if (!relations.length) {
|
||||
return (null);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.editFormRelations}>
|
||||
<h3>Relational data</h3>
|
||||
{relations}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
EditFormRelations.propTypes = {
|
||||
currentModelName: PropTypes.oneOfType([
|
||||
PropTypes.bool,
|
||||
PropTypes.string,
|
||||
]).isRequired,
|
||||
isNull: PropTypes.bool.isRequired,
|
||||
location: PropTypes.shape({
|
||||
search: PropTypes.string,
|
||||
}).isRequired,
|
||||
record: PropTypes.oneOfType([
|
||||
PropTypes.object,
|
||||
PropTypes.bool,
|
||||
]).isRequired,
|
||||
schema: PropTypes.oneOfType([
|
||||
PropTypes.object,
|
||||
PropTypes.bool,
|
||||
]).isRequired,
|
||||
setRecordAttribute: PropTypes.func.isRequired,
|
||||
toggleNull: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
||||
export default EditFormRelations;
|
||||
@ -1,13 +0,0 @@
|
||||
.editFormRelations { /* stylelint-disable */
|
||||
h3{
|
||||
height: 41px;
|
||||
margin: 0 -25px 14px;
|
||||
padding: 0 25px;
|
||||
background: #F3F3F3;
|
||||
line-height: 41px;
|
||||
border-radius: 2px 2px 0 0;
|
||||
letter-spacing: 0.03rem;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="23px" height="32px" viewBox="0 0 23 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Group</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Inputs/Select-(col-6)" transform="translate(-335.000000, -1.000000)">
|
||||
<g id="input">
|
||||
<g id="Number" transform="translate(335.017699, 1.000000)">
|
||||
<g id="Group">
|
||||
<path d="M-5.68434189e-14,0 L20,0 L20,0 C21.1045695,-2.02906125e-16 22,0.8954305 22,2 L22,30 L22,30 C22,31.1045695 21.1045695,32 20,32 L-5.68434189e-14,32 L-5.68434189e-14,0 Z" id="Rectangle" fill="#FAFAFB"></path>
|
||||
<g id="Carets" transform="translate(8.000000, 11.000000)" fill-rule="nonzero" fill="#B3B5B9">
|
||||
<g id="caret-down" transform="translate(0.000000, 7.000000)">
|
||||
<path d="M6,0.375 C6,0.4765625 5.96289062,0.564453125 5.88867188,0.638671875 L3.26367188,3.26367188 C3.18945312,3.33789063 3.1015625,3.375 3,3.375 C2.8984375,3.375 2.81054688,3.33789063 2.73632812,3.26367188 L0.111328125,0.638671875 C0.037109375,0.564453125 0,0.4765625 0,0.375 C0,0.2734375 0.037109375,0.185546875 0.111328125,0.111328125 C0.185546875,0.037109375 0.2734375,0 0.375,0 L5.625,0 C5.7265625,0 5.81445312,0.037109375 5.88867188,0.111328125 C5.96289062,0.185546875 6,0.2734375 6,0.375 Z" id="Shape"></path>
|
||||
</g>
|
||||
<g id="caret-top" transform="translate(3.000000, 2.375000) rotate(180.000000) translate(-3.000000, -2.375000) translate(0.000000, 0.375000)">
|
||||
<path d="M6,0.375 C6,0.4765625 5.96289062,0.564453125 5.88867187,0.638671875 L3.26367187,3.26367188 C3.18945312,3.33789063 3.1015625,3.375 3,3.375 C2.8984375,3.375 2.81054687,3.33789063 2.73632812,3.26367188 L0.111328125,0.638671875 C0.037109375,0.564453125 -1.77635684e-15,0.4765625 -1.77635684e-15,0.375 C-1.77635684e-15,0.2734375 0.037109375,0.185546875 0.111328125,0.111328125 C0.185546875,0.037109375 0.2734375,1.77635684e-15 0.375,1.77635684e-15 L5.625,1.77635684e-15 C5.7265625,1.77635684e-15 5.81445312,0.037109375 5.88867187,0.111328125 C5.96289062,0.185546875 6,0.2734375 6,0.375 Z" id="Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB |
@ -1,55 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* LimitSelect
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { map } from 'lodash';
|
||||
import styles from './styles.scss';
|
||||
|
||||
class LimitSelect extends React.Component {
|
||||
componentWillMount() {
|
||||
const id = _.uniqueId();
|
||||
this.setState({ id });
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of default values to populate the select options
|
||||
*
|
||||
* @returns {number[]}
|
||||
*/
|
||||
getOptionsValues() {
|
||||
return [10, 20, 50, 100];
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<form className="form-inline">
|
||||
|
||||
<div className={styles.selectWrapper}>
|
||||
<select
|
||||
onChange={this.props.onChangeLimit}
|
||||
className={`form-control ${styles.select}`}
|
||||
id={this.state.id}
|
||||
value={this.props.limit}
|
||||
>
|
||||
{map(this.getOptionsValues(), (optionValue, key) => <option value={optionValue} key={key}>{optionValue}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<label className={styles.label} htmlFor={this.state.id}>
|
||||
<FormattedMessage id="content-manager.components.LimitSelect.itemsPerPage" />
|
||||
</label>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
LimitSelect.propTypes = {
|
||||
limit: PropTypes.number.isRequired,
|
||||
onChangeLimit: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default LimitSelect;
|
||||
@ -1,6 +0,0 @@
|
||||
{
|
||||
"itemsPerPage": {
|
||||
"id": "contentManager.components.LimitSelect.itemsPerPage",
|
||||
"defaultMessage": "Items per page"
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
.selectWrapper {
|
||||
display: inline-block;
|
||||
|
||||
}
|
||||
|
||||
.select {
|
||||
|
||||
height: 3.2rem !important;
|
||||
min-width: 5.4rem;
|
||||
padding-top: 0rem;
|
||||
padding-left: 1rem;
|
||||
padding-right: 3rem;
|
||||
background-position: right -1px center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url('./background_input.svg');
|
||||
border: 1px solid #E3E9F3;
|
||||
border-radius: 0.25rem;
|
||||
line-height: 3.2rem;
|
||||
font-size: 1.3rem;
|
||||
font-family: 'Lato' !important;
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #465373;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
@ -1,175 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* Pagination
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { map } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
class Pagination extends React.Component {
|
||||
getLastPageNumber = () => {
|
||||
return Math.ceil(this.props.count / this.props.limit);
|
||||
}
|
||||
|
||||
handleDotsClick = (e) => {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
handlePreviousPageClick = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!this.isFirstPage()) {
|
||||
this.props.onChangePage(this.props.currentPage - 1);
|
||||
}
|
||||
}
|
||||
|
||||
handleNextPageClick = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!this.isLastPage()) {
|
||||
this.props.onChangePage(this.props.currentPage + 1);
|
||||
}
|
||||
}
|
||||
|
||||
handleFirstPageClick = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
this.props.onChangePage(1);
|
||||
}
|
||||
|
||||
handleLastPageClick = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
this.props.onChangePage(this.getLastPageNumber());
|
||||
}
|
||||
|
||||
isFirstPage = () => {
|
||||
return this.props.currentPage === 1;
|
||||
}
|
||||
|
||||
isLastPage = () => {
|
||||
return this.props.currentPage === this.getLastPageNumber();
|
||||
}
|
||||
|
||||
needAfterLinksDots = () => {
|
||||
return this.props.currentPage < this.getLastPageNumber() - 1;
|
||||
}
|
||||
|
||||
needPreviousLinksDots = () => {
|
||||
return this.props.currentPage > 3;
|
||||
}
|
||||
|
||||
renderLinks = () => {
|
||||
// Init variables
|
||||
const linksOptions = [];
|
||||
|
||||
// Add active page link
|
||||
linksOptions.push({
|
||||
value: this.props.currentPage,
|
||||
isActive: true,
|
||||
handleClick: e => {
|
||||
e.preventDefault();
|
||||
},
|
||||
});
|
||||
|
||||
// Add previous page link
|
||||
if (!this.isFirstPage()) {
|
||||
linksOptions.unshift({
|
||||
value: this.props.currentPage - 1,
|
||||
isActive: false,
|
||||
handleClick: this.handlePreviousPageClick,
|
||||
});
|
||||
}
|
||||
|
||||
// Add next page link
|
||||
if (!this.isLastPage() && this.props.count > this.props.limit) {
|
||||
linksOptions.push({
|
||||
value: this.props.currentPage + 1,
|
||||
isActive: false,
|
||||
handleClick: this.handleNextPageClick,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.needPreviousLinksDots()) {
|
||||
linksOptions.unshift({
|
||||
value: 1,
|
||||
isActive: false,
|
||||
handleClick: this.handleFirstPageClick,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.needAfterLinksDots()) {
|
||||
linksOptions.push({
|
||||
value: this.getLastPageNumber(),
|
||||
isActive: false,
|
||||
handleClick: this.handleLastPageClick,
|
||||
});
|
||||
}
|
||||
|
||||
// Generate links
|
||||
return (
|
||||
map(linksOptions, (linksOption, key) => (
|
||||
<li
|
||||
className={`${linksOption.isActive && styles.navLiActive}`}
|
||||
key={key}
|
||||
>
|
||||
<a href="" disabled={linksOption.isActive} onClick={linksOption.handleClick}>
|
||||
{linksOption.value}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={styles.pagination}>
|
||||
<div>
|
||||
<a
|
||||
href=""
|
||||
className={`
|
||||
${styles.paginationNavigator}
|
||||
${this.isFirstPage() && styles.paginationNavigatorDisabled}
|
||||
`}
|
||||
onClick={this.handlePreviousPageClick}
|
||||
disabled={this.isFirstPage()}
|
||||
>
|
||||
<i className="fa fa-angle-left" aria-hidden="true"></i>
|
||||
</a>
|
||||
<nav className={styles.nav}>
|
||||
<ul className={styles.navUl}>
|
||||
{this.renderLinks()}
|
||||
</ul>
|
||||
</nav>
|
||||
<a
|
||||
href=""
|
||||
className={`
|
||||
${styles.paginationNavigator}
|
||||
${this.isLastPage() && styles.paginationNavigatorDisabled}
|
||||
`}
|
||||
onClick={this.handleNextPageClick}
|
||||
disabled={this.isLastPage()}
|
||||
>
|
||||
<i className="fa fa-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Pagination.propTypes = {
|
||||
count: PropTypes.oneOfType([
|
||||
PropTypes.number,
|
||||
PropTypes.bool,
|
||||
]).isRequired,
|
||||
currentPage: PropTypes.number.isRequired,
|
||||
limit: PropTypes.number.isRequired,
|
||||
onChangePage: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Pagination;
|
||||
@ -1,127 +0,0 @@
|
||||
.pagination {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
>div{
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
min-width: 120px;
|
||||
height: 32px;
|
||||
background: #ffffff;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #E2E8F3;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 4px #E3E9F3;
|
||||
}
|
||||
}
|
||||
|
||||
.paginationNavigator {
|
||||
position: relative;
|
||||
width: 36px;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
font-size: 2rem;
|
||||
|
||||
&:first-of-type{
|
||||
margin-right: 10px;
|
||||
|
||||
&:after{
|
||||
position: absolute;
|
||||
content: '';
|
||||
top: 3px; bottom: 3px; right: 0;
|
||||
width: 1px;
|
||||
background: #F1F1F2;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-of-type{
|
||||
margin-left: 10px;
|
||||
|
||||
&:before{
|
||||
position: absolute;
|
||||
content: '';
|
||||
top: 3px; bottom: 3px; left: 0;
|
||||
width: 1px;
|
||||
background: #F1F1F2;
|
||||
}
|
||||
}
|
||||
|
||||
i{
|
||||
color: #97999E;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
i{
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav {
|
||||
min-width: 48px;
|
||||
|
||||
ul{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
margin: 0 -5px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li{
|
||||
position: relative;
|
||||
min-width: 15px;
|
||||
margin: 0 5px !important;
|
||||
text-align: center;
|
||||
line-height: 32px;
|
||||
color: #333740;
|
||||
|
||||
a {
|
||||
color: #333740;
|
||||
font-size: 1.3rem;
|
||||
|
||||
&:hover{
|
||||
&:after{
|
||||
position: absolute;
|
||||
content: '';
|
||||
bottom: 0; left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: #1C5DE7;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:visited,
|
||||
&:focus,
|
||||
&:active {
|
||||
text-decoration: none;
|
||||
color: #333740;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.navUl {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.navLiActive {
|
||||
font-weight: 800;
|
||||
|
||||
&:after{
|
||||
position: absolute;
|
||||
content: '';
|
||||
bottom: 0; left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: #1C5DE7;
|
||||
}
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* TableFooter
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import LimitSelect from '../LimitSelect';
|
||||
import Pagination from '../Pagination';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
class TableFooter extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className={`row ${styles.tableFooter}`}>
|
||||
<div className="col-lg-6">
|
||||
<LimitSelect
|
||||
onChangeLimit={this.props.onChangeLimit}
|
||||
limit={this.props.limit}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-lg-6">
|
||||
<Pagination
|
||||
limit={this.props.limit}
|
||||
currentPage={this.props.currentPage}
|
||||
onChangePage={this.props.onChangePage}
|
||||
count={this.props.count}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TableFooter.propTypes = {
|
||||
count: PropTypes.oneOfType([
|
||||
PropTypes.number,
|
||||
PropTypes.bool,
|
||||
]).isRequired,
|
||||
currentPage: PropTypes.number.isRequired,
|
||||
limit: PropTypes.number.isRequired,
|
||||
onChangeLimit: PropTypes.func.isRequired,
|
||||
onChangePage: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default TableFooter;
|
||||
@ -1,3 +0,0 @@
|
||||
.tableFooter {
|
||||
margin-top: 2.8rem;
|
||||
}
|
||||
@ -17,15 +17,12 @@ import injectSaga from 'utils/injectSaga';
|
||||
import getQueryParameters from 'utils/getQueryParameters';
|
||||
|
||||
import Home from 'containers/Home';
|
||||
// import Edit from 'containers/Edit';
|
||||
import EditPage from 'containers/EditPage';
|
||||
// import List from 'containers/List';
|
||||
import ListPage from 'containers/ListPage';
|
||||
import EmptyAttributesView from 'components/EmptyAttributesView';
|
||||
|
||||
import {
|
||||
emptyStore,
|
||||
// getModelEntries,
|
||||
loadModels,
|
||||
} from './actions';
|
||||
import { makeSelectLoading, makeSelectModels, makeSelectModelEntries } from './selectors';
|
||||
@ -35,24 +32,8 @@ import saga from './sagas';
|
||||
class App extends React.Component {
|
||||
componentDidMount() {
|
||||
this.props.loadModels();
|
||||
|
||||
// NOTE: I'm commenting this part of code since I'm not sure why it is needed
|
||||
// const modelName = this.props.location.pathname.split('/')[3];
|
||||
|
||||
// if (modelName) {
|
||||
// this.props.getModelEntries(modelName, getQueryParameters(this.props.location.search, 'source'));
|
||||
// }
|
||||
}
|
||||
|
||||
// NOTE: I'm commenting this part of code since I'm not sure why it is needed
|
||||
// componentDidUpdate(prevProps) {
|
||||
// const currentModelName = this.props.location.pathname.split('/')[3];
|
||||
//
|
||||
// if (prevProps.location.pathname !== this.props.location.pathname && currentModelName) {
|
||||
// this.props.getModelEntries(currentModelName, getQueryParameters(this.props.location.search, 'source'));
|
||||
// }
|
||||
// }
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.emptyStore();
|
||||
}
|
||||
@ -76,10 +57,6 @@ class App extends React.Component {
|
||||
<Switch>
|
||||
<Route path="/plugins/content-manager/:slug/:id" component={EditPage} />
|
||||
<Route path="/plugins/content-manager/:slug" component={ListPage} />
|
||||
{/* Note: I'm commenting this lines in case we need to rollback to the previous containers
|
||||
<Route path="/plugins/content-manager/:slug/:id" component={Edit} />
|
||||
<Route path="/plugins/content-manager/:slug" component={List} />
|
||||
*/}
|
||||
<Route path="/plugins/content-manager" component={Home} />
|
||||
</Switch>
|
||||
</div>
|
||||
@ -93,7 +70,6 @@ App.contextTypes = {
|
||||
|
||||
App.propTypes = {
|
||||
emptyStore: PropTypes.func.isRequired,
|
||||
// getModelEntries: PropTypes.func.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
loading: PropTypes.bool.isRequired,
|
||||
loadModels: PropTypes.func.isRequired,
|
||||
|
||||
@ -1,163 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* Edit actions
|
||||
*
|
||||
*/
|
||||
import { get } from 'lodash';
|
||||
import { getValidationsFromForm } from '../../utils/formValidations';
|
||||
|
||||
import {
|
||||
CANCEL_CHANGES,
|
||||
DELETE_RECORD,
|
||||
DELETE_RECORD_ERROR,
|
||||
DELETE_RECORD_SUCCESS,
|
||||
EDIT_RECORD,
|
||||
EDIT_RECORD_ERROR,
|
||||
EDIT_RECORD_SUCCESS,
|
||||
SET_CURRENT_MODEL_NAME,
|
||||
SET_IS_CREATING,
|
||||
SET_INITIAL_STATE,
|
||||
LOAD_RECORD,
|
||||
LOAD_RECORD_SUCCESS,
|
||||
SET_RECORD_ATTRIBUTE,
|
||||
TOGGLE_NULL,
|
||||
RESET_EDIT_SUCCESS,
|
||||
SET_FORM_VALIDATIONS,
|
||||
SET_FORM,
|
||||
SET_FORM_ERRORS,
|
||||
} from './constants';
|
||||
|
||||
export function cancelChanges() {
|
||||
return {
|
||||
type: CANCEL_CHANGES,
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteRecord(id, modelName, source) {
|
||||
return {
|
||||
type: DELETE_RECORD,
|
||||
id,
|
||||
modelName,
|
||||
source,
|
||||
};
|
||||
}
|
||||
|
||||
export function editRecord(source) {
|
||||
return {
|
||||
type: EDIT_RECORD,
|
||||
source,
|
||||
};
|
||||
}
|
||||
|
||||
export function loadRecord(id, source) {
|
||||
return {
|
||||
type: LOAD_RECORD,
|
||||
id,
|
||||
source,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function recordDeleted(id) {
|
||||
return {
|
||||
type: DELETE_RECORD_SUCCESS,
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export function recordDeleteError() {
|
||||
return {
|
||||
type: DELETE_RECORD_ERROR,
|
||||
};
|
||||
}
|
||||
|
||||
export function recordEdited() {
|
||||
return {
|
||||
type: EDIT_RECORD_SUCCESS,
|
||||
};
|
||||
}
|
||||
|
||||
export function recordEditError() {
|
||||
return {
|
||||
type: EDIT_RECORD_ERROR,
|
||||
};
|
||||
}
|
||||
|
||||
export function recordLoaded(record) {
|
||||
return {
|
||||
type: LOAD_RECORD_SUCCESS,
|
||||
record,
|
||||
};
|
||||
}
|
||||
|
||||
export function resetEditSuccess() {
|
||||
return {
|
||||
type: RESET_EDIT_SUCCESS,
|
||||
};
|
||||
}
|
||||
|
||||
export function setCurrentModelName(currentModelName) {
|
||||
return {
|
||||
type: SET_CURRENT_MODEL_NAME,
|
||||
currentModelName,
|
||||
};
|
||||
}
|
||||
|
||||
export function setForm(data) {
|
||||
const form = [];
|
||||
Object.keys(data).map(attr => {
|
||||
form.push([attr, '']);
|
||||
});
|
||||
|
||||
return {
|
||||
type: SET_FORM,
|
||||
form,
|
||||
};
|
||||
}
|
||||
|
||||
export function setFormErrors(formErrors) {
|
||||
return {
|
||||
type: SET_FORM_ERRORS,
|
||||
formErrors,
|
||||
};
|
||||
}
|
||||
|
||||
export function setFormValidations(data) {
|
||||
const form = Object.keys(data).map(attr => {
|
||||
return { name: attr, validations: get(data, attr) || {} };
|
||||
});
|
||||
|
||||
const formValidations = getValidationsFromForm(form, []);
|
||||
|
||||
return {
|
||||
type: SET_FORM_VALIDATIONS,
|
||||
formValidations,
|
||||
};
|
||||
}
|
||||
|
||||
export function setInitialState() {
|
||||
return {
|
||||
type: SET_INITIAL_STATE,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function setIsCreating() {
|
||||
return {
|
||||
type: SET_IS_CREATING,
|
||||
};
|
||||
}
|
||||
|
||||
export function setRecordAttribute(key, value) {
|
||||
return {
|
||||
type: SET_RECORD_ATTRIBUTE,
|
||||
key,
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
export function toggleNull() {
|
||||
return {
|
||||
type: TOGGLE_NULL,
|
||||
};
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* Edit constants
|
||||
*
|
||||
*/
|
||||
|
||||
export const SET_INITIAL_STATE = 'app/Edit/SET_INITIAL_STATE';
|
||||
export const SET_CURRENT_MODEL_NAME = 'app/Edit/SET_CURRENT_MODEL_NAME';
|
||||
export const SET_IS_CREATING = 'app/Edit/SET_IS_CREATING';
|
||||
export const SET_FORM_VALIDATIONS = 'app/Edit/SET_FORM_VALIDATIONS';
|
||||
export const SET_FORM = 'app/Edit/SET_FORM';
|
||||
export const SET_FORM_ERRORS = 'app/Edit/SET_FORM_ERRORS';
|
||||
|
||||
export const LOAD_RECORD = 'app/Edit/LOAD_RECORD';
|
||||
export const LOAD_RECORD_SUCCESS = 'app/Edit/LOAD_RECORD_SUCCESS';
|
||||
|
||||
export const SET_RECORD_ATTRIBUTE = 'app/Edit/SET_RECORD_ATTRIBUTE';
|
||||
|
||||
export const EDIT_RECORD = 'app/Edit/EDIT_RECORD';
|
||||
export const EDIT_RECORD_SUCCESS = 'app/Edit/EDIT_RECORD_SUCCESS';
|
||||
export const EDIT_RECORD_ERROR = 'app/Edit/EDIT_RECORD_ERROR';
|
||||
|
||||
export const DELETE_RECORD = 'app/Edit/DELETE_RECORD';
|
||||
export const DELETE_RECORD_SUCCESS = 'app/Edit/DELETE_RECORD_SUCCESS';
|
||||
export const DELETE_RECORD_ERROR = 'app/Edit/DELETE_RECORD_ERROR';
|
||||
export const CANCEL_CHANGES = 'app/Edit/CANCEL_CHANGES';
|
||||
|
||||
export const TOGGLE_NULL = 'app/Edit/TOGGLE_NULL';
|
||||
export const RESET_EDIT_SUCCESS = 'app/Edit/RESET_EDIT_SUCCESS';
|
||||
@ -1,356 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* Edit
|
||||
*
|
||||
*/
|
||||
|
||||
// Dependencies.
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators, compose } from 'redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
get,
|
||||
includes,
|
||||
isObject,
|
||||
isEmpty,
|
||||
map,
|
||||
replace,
|
||||
toNumber,
|
||||
toString,
|
||||
} from 'lodash';
|
||||
import { router } from 'app';
|
||||
|
||||
// Components.
|
||||
import BackHeader from 'components/BackHeader';
|
||||
import EditForm from 'components/EditForm';
|
||||
import EditFormRelations from 'components/EditFormRelations';
|
||||
import PluginHeader from 'components/PluginHeader';
|
||||
|
||||
// Selectors.
|
||||
import { makeSelectModels, makeSelectSchema } from 'containers/App/selectors';
|
||||
|
||||
// Utils.
|
||||
import getQueryParameters from 'utils/getQueryParameters';
|
||||
import injectReducer from 'utils/injectReducer';
|
||||
import injectSaga from 'utils/injectSaga';
|
||||
import templateObject from 'utils/templateObject';
|
||||
import { checkFormValidity } from '../../utils/formValidations';
|
||||
import { bindLayout } from '../../utils/bindLayout';
|
||||
|
||||
// Layout
|
||||
import layout from '../../../../config/layout';
|
||||
|
||||
// Styles.
|
||||
import styles from './styles.scss';
|
||||
|
||||
// Actions.
|
||||
import {
|
||||
setInitialState,
|
||||
setCurrentModelName,
|
||||
setIsCreating,
|
||||
loadRecord,
|
||||
setRecordAttribute,
|
||||
editRecord,
|
||||
toggleNull,
|
||||
cancelChanges,
|
||||
setFormValidations,
|
||||
setForm,
|
||||
setFormErrors,
|
||||
recordEdited,
|
||||
resetEditSuccess,
|
||||
} from './actions';
|
||||
|
||||
// Selectors.
|
||||
|
||||
import {
|
||||
makeSelectRecord,
|
||||
makeSelectLoading,
|
||||
makeSelectCurrentModelName,
|
||||
makeSelectEditing,
|
||||
makeSelectDeleting,
|
||||
makeSelectIsCreating,
|
||||
makeSelectIsRelationComponentNull,
|
||||
makeSelectForm,
|
||||
makeSelectFormValidations,
|
||||
makeSelectFormErrors,
|
||||
makeSelectDidCheckErrors,
|
||||
makeSelectEditSuccess,
|
||||
} from './selectors';
|
||||
|
||||
import reducer from './reducer';
|
||||
import saga from './sagas';
|
||||
|
||||
export class Edit extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.pluginHeaderActions = [
|
||||
{
|
||||
label: 'content-manager.containers.Edit.reset',
|
||||
kind: 'secondary',
|
||||
onClick: this.props.cancelChanges,
|
||||
type: 'button',
|
||||
},
|
||||
{
|
||||
kind: 'primary',
|
||||
label: this.props.editing ? 'content-manager.containers.Edit.editing' : 'content-manager.containers.Edit.submit',
|
||||
onClick: this.handleSubmit,
|
||||
disabled: this.props.editing,
|
||||
type: 'submit',
|
||||
},
|
||||
];
|
||||
|
||||
this.source = getQueryParameters(this.props.location.search, 'source');
|
||||
this.layout = bindLayout.call(this, layout);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const attributes =
|
||||
get(this.props.models, ['models', this.props.match.params.slug.toLowerCase(), 'attributes']) ||
|
||||
get(this.props.models, ['plugins', this.source, 'models', this.props.match.params.slug.toLowerCase(), 'attributes']);
|
||||
|
||||
if (this.source) {
|
||||
this.layout = bindLayout.call(this, get(this.context.plugins.toJS(), `${this.source}.layout`, layout));
|
||||
}
|
||||
|
||||
this.props.setInitialState();
|
||||
this.props.setCurrentModelName(this.props.match.params.slug.toLowerCase());
|
||||
this.props.setFormValidations(attributes);
|
||||
this.props.setForm(attributes);
|
||||
// Detect that the current route is the `create` route or not
|
||||
if (this.props.match.params.id === 'create') {
|
||||
this.props.setIsCreating();
|
||||
} else {
|
||||
this.props.loadRecord(this.props.match.params.id, this.source);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.editSuccess !== nextProps.editSuccess) {
|
||||
if (!isEmpty(this.props.location.search) && includes(this.props.location.search, '?redirectUrl')) {
|
||||
const redirectUrl = this.props.location.search.split('?redirectUrl=')[1];
|
||||
|
||||
router.push({
|
||||
pathname: redirectUrl.split('?')[0],
|
||||
search: redirectUrl.split('?')[1],
|
||||
});
|
||||
} else {
|
||||
router.push({
|
||||
pathname: replace(this.props.location.pathname, '/create', ''),
|
||||
search: `?source=${this.source}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.recordEdited();
|
||||
this.props.resetEditSuccess();
|
||||
this.props.setInitialState();
|
||||
}
|
||||
|
||||
handleChange = (e) => {
|
||||
const currentSchema = this.source !== 'content-manager' ? get(this.props.schema, ['plugins', this.source, this.props.currentModelName]) : get(this.props.schema, [this.props.currentModelName]);
|
||||
|
||||
let formattedValue = e.target.value;
|
||||
|
||||
if (isObject(e.target.value) && e.target.value._isAMomentObject === true) {
|
||||
formattedValue = moment(e.target.value, 'YYYY-MM-DD HH:mm:ss').format();
|
||||
} else if (['float', 'integer', 'biginteger', 'decimal'].indexOf(currentSchema.fields[e.target.name].type) !== -1) {
|
||||
formattedValue = toNumber(e.target.value);
|
||||
}
|
||||
|
||||
this.props.setRecordAttribute(e.target.name, formattedValue);
|
||||
}
|
||||
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const form = this.props.form.toJS();
|
||||
map(this.props.record.toJS(), (value, key) => form[key] = value);
|
||||
const formErrors = checkFormValidity(form, this.props.formValidations.toJS());
|
||||
|
||||
if (isEmpty(formErrors)) {
|
||||
this.props.editRecord(this.source);
|
||||
} else {
|
||||
this.props.setFormErrors(formErrors);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.loading || !this.props.schema || !this.props.currentModelName) {
|
||||
return <p>Loading...</p>;
|
||||
}
|
||||
|
||||
const currentModel = get(this.props.models, ['models', this.props.currentModelName]) || get(this.props.models, ['plugins', this.source, 'models', this.props.currentModelName]);
|
||||
// Plugin header config
|
||||
const primaryKey = currentModel.primaryKey;
|
||||
const mainField = get(currentModel, 'info.mainField') || primaryKey;
|
||||
const pluginHeaderTitle = this.props.isCreating ? 'New entry' : templateObject({ mainField }, this.props.record.toJS()).mainField;
|
||||
const pluginHeaderDescription = this.props.isCreating ? 'New entry' : `#${this.props.record && this.props.record.get(primaryKey)}`;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<BackHeader onClick={() => router.goBack()} />
|
||||
<div className={`container-fluid ${styles.containerFluid}`}>
|
||||
<PluginHeader
|
||||
title={{
|
||||
id: toString(pluginHeaderTitle),
|
||||
}}
|
||||
description={{
|
||||
id: 'plugin-content-manager-description',
|
||||
defaultMessage: `${pluginHeaderDescription}`,
|
||||
}}
|
||||
actions={this.pluginHeaderActions}
|
||||
fullWidth={this.props.isRelationComponentNull}
|
||||
/>
|
||||
<div className='row'>
|
||||
<div className={this.props.isRelationComponentNull ? `col-lg-12` : `col-lg-9`}>
|
||||
<div className={`${styles.main_wrapper}`}>
|
||||
<EditForm
|
||||
record={this.props.record}
|
||||
currentModelName={this.props.currentModelName}
|
||||
schema={this.props.schema}
|
||||
setRecordAttribute={this.props.setRecordAttribute}
|
||||
onChange={this.handleChange}
|
||||
onSubmit={this.handleSubmit}
|
||||
editing={this.props.editing}
|
||||
formErrors={this.props.formErrors.toJS()}
|
||||
didCheckErrors={this.props.didCheckErrors}
|
||||
formValidations={this.props.formValidations.toJS()}
|
||||
layout={this.layout}
|
||||
location={this.props.location}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`col-lg-3 ${this.props.isRelationComponentNull ? 'hidden-xl-down' : ''}`}>
|
||||
<div className={styles.sub_wrapper}>
|
||||
<EditFormRelations
|
||||
currentModelName={this.props.currentModelName}
|
||||
record={this.props.record}
|
||||
schema={this.props.schema}
|
||||
setRecordAttribute={this.props.setRecordAttribute}
|
||||
isNull={this.props.isRelationComponentNull}
|
||||
toggleNull={this.props.toggleNull}
|
||||
location={this.props.location}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Edit.contextTypes = {
|
||||
plugins: PropTypes.object,
|
||||
updatePlugin: PropTypes.func,
|
||||
};
|
||||
|
||||
/* eslint-disable react/require-default-props */
|
||||
Edit.propTypes = {
|
||||
cancelChanges: PropTypes.func.isRequired,
|
||||
currentModelName: PropTypes.oneOfType([
|
||||
PropTypes.bool,
|
||||
PropTypes.string,
|
||||
]).isRequired,
|
||||
didCheckErrors: PropTypes.bool.isRequired,
|
||||
editing: PropTypes.bool.isRequired,
|
||||
editRecord: PropTypes.func.isRequired,
|
||||
editSuccess: PropTypes.bool.isRequired,
|
||||
form: PropTypes.object.isRequired,
|
||||
formErrors: PropTypes.oneOfType([
|
||||
PropTypes.array,
|
||||
PropTypes.object,
|
||||
]),
|
||||
formValidations: PropTypes.oneOfType([
|
||||
PropTypes.array,
|
||||
PropTypes.object,
|
||||
]),
|
||||
isCreating: PropTypes.bool.isRequired,
|
||||
isRelationComponentNull: PropTypes.bool.isRequired,
|
||||
loading: PropTypes.bool.isRequired,
|
||||
loadRecord: PropTypes.func.isRequired,
|
||||
location: PropTypes.object.isRequired,
|
||||
match: PropTypes.shape({
|
||||
params: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
slug: PropTypes.string,
|
||||
}),
|
||||
}).isRequired,
|
||||
models: PropTypes.oneOfType([
|
||||
PropTypes.object,
|
||||
PropTypes.bool,
|
||||
]).isRequired,
|
||||
record: PropTypes.oneOfType([
|
||||
PropTypes.object,
|
||||
PropTypes.bool,
|
||||
]).isRequired,
|
||||
recordEdited: PropTypes.func,
|
||||
resetEditSuccess: PropTypes.func,
|
||||
schema: PropTypes.oneOfType([
|
||||
PropTypes.object,
|
||||
PropTypes.bool,
|
||||
]).isRequired,
|
||||
setCurrentModelName: PropTypes.func.isRequired,
|
||||
setForm: PropTypes.func.isRequired,
|
||||
setFormErrors: PropTypes.func.isRequired,
|
||||
setFormValidations: PropTypes.func.isRequired,
|
||||
setInitialState: PropTypes.func.isRequired,
|
||||
setIsCreating: PropTypes.func.isRequired,
|
||||
setRecordAttribute: PropTypes.func.isRequired,
|
||||
toggleNull: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
record: makeSelectRecord(),
|
||||
loading: makeSelectLoading(),
|
||||
currentModelName: makeSelectCurrentModelName(),
|
||||
editing: makeSelectEditing(),
|
||||
deleting: makeSelectDeleting(),
|
||||
isCreating: makeSelectIsCreating(),
|
||||
schema: makeSelectSchema(),
|
||||
models: makeSelectModels(),
|
||||
isRelationComponentNull: makeSelectIsRelationComponentNull(),
|
||||
form: makeSelectForm(),
|
||||
formValidations: makeSelectFormValidations(),
|
||||
formErrors: makeSelectFormErrors(),
|
||||
didCheckErrors: makeSelectDidCheckErrors(),
|
||||
editSuccess: makeSelectEditSuccess(),
|
||||
});
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators(
|
||||
{
|
||||
setInitialState,
|
||||
setCurrentModelName,
|
||||
setIsCreating,
|
||||
loadRecord,
|
||||
setRecordAttribute,
|
||||
editRecord,
|
||||
toggleNull,
|
||||
cancelChanges,
|
||||
setFormValidations,
|
||||
setForm,
|
||||
setFormErrors,
|
||||
recordEdited,
|
||||
resetEditSuccess,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
}
|
||||
|
||||
const withConnect = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
const withReducer = injectReducer({ key: 'edit', reducer });
|
||||
const withSaga = injectSaga({ key: 'edit', saga });
|
||||
|
||||
export default compose(
|
||||
withReducer,
|
||||
withSaga,
|
||||
withConnect,
|
||||
)(Edit);
|
||||
@ -1,18 +0,0 @@
|
||||
{
|
||||
"submit": {
|
||||
"id": "contentManager.containers.Edit.submit",
|
||||
"defaultMessage": "Submit"
|
||||
},
|
||||
"editing": {
|
||||
"id": "contentManager.containers.Edit.editing",
|
||||
"defaultMessage": "Editing..."
|
||||
},
|
||||
"delete": {
|
||||
"id": "contentManager.containers.Edit.delete",
|
||||
"defaultMessage": "Delete"
|
||||
},
|
||||
"cancel": {
|
||||
"id": "contentManager.containers.Edit.reset",
|
||||
"defaultMessage": "Cancel"
|
||||
}
|
||||
}
|
||||
@ -1,106 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* Edit reducer
|
||||
*
|
||||
*/
|
||||
|
||||
import { fromJS, Map, List } from 'immutable';
|
||||
|
||||
import {
|
||||
SET_INITIAL_STATE,
|
||||
SET_CURRENT_MODEL_NAME,
|
||||
SET_IS_CREATING,
|
||||
LOAD_RECORD,
|
||||
LOAD_RECORD_SUCCESS,
|
||||
SET_RECORD_ATTRIBUTE,
|
||||
EDIT_RECORD,
|
||||
EDIT_RECORD_SUCCESS,
|
||||
EDIT_RECORD_ERROR,
|
||||
DELETE_RECORD,
|
||||
DELETE_RECORD_SUCCESS,
|
||||
DELETE_RECORD_ERROR,
|
||||
TOGGLE_NULL,
|
||||
CANCEL_CHANGES,
|
||||
RESET_EDIT_SUCCESS,
|
||||
SET_FORM_VALIDATIONS,
|
||||
SET_FORM,
|
||||
SET_FORM_ERRORS,
|
||||
} from './constants';
|
||||
|
||||
const initialState = fromJS({
|
||||
currentModelName: '',
|
||||
loading: false,
|
||||
record: false,
|
||||
initialRecord: {},
|
||||
editing: false,
|
||||
deleting: false,
|
||||
isCreating: false,
|
||||
isRelationComponentNull: false,
|
||||
formValidations: List([]),
|
||||
formErrors: List([]),
|
||||
form: Map({}),
|
||||
didCheckErrors: false,
|
||||
editSuccess: false,
|
||||
});
|
||||
|
||||
function editReducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case SET_INITIAL_STATE:
|
||||
return initialState;
|
||||
case SET_CURRENT_MODEL_NAME:
|
||||
return state.set('currentModelName', action.currentModelName);
|
||||
case SET_IS_CREATING:
|
||||
return state
|
||||
.set('isCreating', true)
|
||||
.set('loading', false)
|
||||
.set('record', fromJS({}));
|
||||
case LOAD_RECORD:
|
||||
return state
|
||||
.set('loading', true)
|
||||
.set('model', action.model)
|
||||
.set('id', action.id);
|
||||
case LOAD_RECORD_SUCCESS:
|
||||
return state
|
||||
.set('loading', false)
|
||||
.set('record', fromJS(action.record))
|
||||
.set('initialRecord', fromJS(action.record));
|
||||
case SET_RECORD_ATTRIBUTE:
|
||||
return state
|
||||
.setIn(['record', action.key], fromJS(action.value));
|
||||
case EDIT_RECORD:
|
||||
return state.set('editing', true);
|
||||
case EDIT_RECORD_SUCCESS:
|
||||
return state
|
||||
.set('editSuccess', true)
|
||||
.set('editing', false);
|
||||
case EDIT_RECORD_ERROR:
|
||||
return state.set('editing', false);
|
||||
case DELETE_RECORD:
|
||||
return state.set('deleting', true);
|
||||
case DELETE_RECORD_SUCCESS:
|
||||
return state.set('deleting', false);
|
||||
case DELETE_RECORD_ERROR:
|
||||
return state.set('deleting', false);
|
||||
case TOGGLE_NULL:
|
||||
return state.set('isRelationComponentNull', true);
|
||||
case CANCEL_CHANGES:
|
||||
return state
|
||||
.set('formErrors', List([]))
|
||||
.set('record', state.get('initialRecord'));
|
||||
case SET_FORM_VALIDATIONS:
|
||||
return state
|
||||
.set('formValidations', List(action.formValidations));
|
||||
case SET_FORM:
|
||||
return state.set('form', Map(action.form));
|
||||
case SET_FORM_ERRORS:
|
||||
return state
|
||||
.set('formErrors', List(action.formErrors))
|
||||
.set('didCheckErrors', !state.get('didCheckErrors'));
|
||||
case RESET_EDIT_SUCCESS:
|
||||
return state.set('editSuccess', false);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default editReducer;
|
||||
@ -1,157 +0,0 @@
|
||||
import { LOCATION_CHANGE } from 'react-router-redux';
|
||||
import { get, isArray } from 'lodash';
|
||||
import { call, cancel, fork, put, take, select, takeLatest } from 'redux-saga/effects';
|
||||
import request from 'utils/request';
|
||||
import cleanData from 'utils/cleanData';
|
||||
import { router } from 'app';
|
||||
|
||||
import { decreaseCount } from 'containers/List/actions';
|
||||
|
||||
import {
|
||||
recordLoaded,
|
||||
recordEdited,
|
||||
recordEditError,
|
||||
recordDeleted,
|
||||
recordDeleteError,
|
||||
setFormErrors,
|
||||
} from './actions';
|
||||
import { LOAD_RECORD, EDIT_RECORD, DELETE_RECORD } from './constants';
|
||||
import {
|
||||
makeSelectCurrentModelName,
|
||||
makeSelectRecord,
|
||||
makeSelectIsCreating,
|
||||
} from './selectors';
|
||||
|
||||
export function* getRecord(action) {
|
||||
const currentModelName = yield select(makeSelectCurrentModelName());
|
||||
const params = {};
|
||||
|
||||
if (action.source !== undefined) {
|
||||
params.source = action.source;
|
||||
}
|
||||
|
||||
try {
|
||||
const requestUrl = `/content-manager/explorer/${currentModelName}/${action.id}`;
|
||||
|
||||
// Call our request helper (see 'utils/request')
|
||||
const response = yield request(requestUrl, {
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
|
||||
yield put(recordLoaded(response));
|
||||
} catch (err) {
|
||||
strapi.notification.error('content-manager.error.record.fetch');
|
||||
}
|
||||
}
|
||||
|
||||
export function* editRecord(action) {
|
||||
const currentModelName = yield select(makeSelectCurrentModelName());
|
||||
const record = yield select(makeSelectRecord());
|
||||
const recordJSON = record.toJSON();
|
||||
|
||||
const recordCleaned = Object.keys(recordJSON).reduce((acc, current) => {
|
||||
acc[current] = cleanData(recordJSON[current], 'value', 'id');
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const isCreating = yield select(makeSelectIsCreating());
|
||||
const id = isCreating ? '' : recordCleaned.id;
|
||||
const params = {};
|
||||
|
||||
if (action.source !== undefined) {
|
||||
params.source = action.source;
|
||||
}
|
||||
|
||||
try {
|
||||
const requestUrl = `/content-manager/explorer/${currentModelName}/${id}`;
|
||||
|
||||
// Call our request helper (see 'utils/request')
|
||||
yield call(request, requestUrl, {
|
||||
method: isCreating ? 'POST' : 'PUT',
|
||||
body: recordCleaned,
|
||||
params,
|
||||
});
|
||||
|
||||
strapi.notification.success('content-manager.success.record.save');
|
||||
yield put(recordEdited());
|
||||
} catch (err) {
|
||||
if (isArray(err.response.payload.message)) {
|
||||
const errors = err.response.payload.message.reduce((acc, current) => {
|
||||
const error = current.messages.reduce((acc, current) => {
|
||||
acc.errorMessage = current.id;
|
||||
|
||||
return acc;
|
||||
}, { id: 'components.Input.error.custom-error', errorMessage: '' });
|
||||
acc.push(error);
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const name = get(err.response.payload.message, ['0', 'messages', '0', 'field']);
|
||||
|
||||
yield put(setFormErrors([{ name, errors }]));
|
||||
}
|
||||
|
||||
yield put(recordEditError());
|
||||
strapi.notification.error(isCreating ? 'content-manager.error.record.create' : 'content-manager.error.record.update');
|
||||
}
|
||||
}
|
||||
|
||||
export function* deleteRecord({ id, modelName, source }) {
|
||||
function* httpCall(id, modelName) {
|
||||
try {
|
||||
const requestUrl = `/content-manager/explorer/${modelName}/${id}`;
|
||||
const params = {};
|
||||
|
||||
if (source !== undefined) {
|
||||
params.source = source;
|
||||
}
|
||||
// Call our request helper (see 'utils/request')
|
||||
yield call(request, requestUrl, {
|
||||
method: 'DELETE',
|
||||
params,
|
||||
});
|
||||
|
||||
yield put(recordDeleted(id));
|
||||
yield put(decreaseCount());
|
||||
strapi.notification.success('content-manager.success.record.delete');
|
||||
|
||||
// Redirect to the list page.
|
||||
router.push({
|
||||
pathname: `/plugins/content-manager/${modelName}`,
|
||||
search: `?source=${source}`,
|
||||
});
|
||||
} catch (err) {
|
||||
yield put(recordDeleteError());
|
||||
strapi.notification.error('content-manager.error.record.delete');
|
||||
}
|
||||
}
|
||||
|
||||
if (id && modelName) {
|
||||
yield httpCall(id, modelName);
|
||||
} else {
|
||||
const currentModelName = yield select(makeSelectCurrentModelName());
|
||||
const record = yield select(makeSelectRecord());
|
||||
const recordJSON = record.toJSON();
|
||||
|
||||
yield httpCall(recordJSON.id, currentModelName);
|
||||
}
|
||||
}
|
||||
|
||||
export function* defaultSaga() {
|
||||
const loadRecordWatcher = yield fork(takeLatest, LOAD_RECORD, getRecord);
|
||||
const editRecordWatcher = yield fork(takeLatest, EDIT_RECORD, editRecord);
|
||||
const deleteRecordWatcher = yield fork(takeLatest, DELETE_RECORD, deleteRecord);
|
||||
|
||||
// Suspend execution until location changes
|
||||
yield take(LOCATION_CHANGE);
|
||||
|
||||
yield cancel(loadRecordWatcher);
|
||||
yield cancel(editRecordWatcher);
|
||||
yield cancel(deleteRecordWatcher);
|
||||
}
|
||||
|
||||
// All sagas to be loaded
|
||||
export default defaultSaga;
|
||||
@ -1,68 +0,0 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
/**
|
||||
* Direct selector to the edit state domain
|
||||
*/
|
||||
const selectEditDomain = () => state => state.get('edit');
|
||||
|
||||
/**
|
||||
* Other specific selectors
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default selector used by Edit
|
||||
*/
|
||||
|
||||
const makeSelectRecord = () =>
|
||||
createSelector(selectEditDomain(), substate => substate.get('record'));
|
||||
|
||||
const makeSelectLoading = () =>
|
||||
createSelector(selectEditDomain(), substate => substate.get('loading'));
|
||||
|
||||
const makeSelectCurrentModelName = () =>
|
||||
createSelector(selectEditDomain(), substate =>
|
||||
substate.get('currentModelName')
|
||||
);
|
||||
|
||||
const makeSelectEditing = () =>
|
||||
createSelector(selectEditDomain(), substate => substate.get('editing'));
|
||||
|
||||
const makeSelectDeleting = () =>
|
||||
createSelector(selectEditDomain(), substate => substate.get('deleting'));
|
||||
|
||||
const makeSelectIsCreating = () =>
|
||||
createSelector(selectEditDomain(), substate => substate.get('isCreating'));
|
||||
|
||||
const makeSelectIsRelationComponentNull = () =>
|
||||
createSelector(selectEditDomain(), substate => substate.get('isRelationComponentNull'));
|
||||
|
||||
const makeSelectForm = () =>
|
||||
createSelector(selectEditDomain(), substate => substate.get('form'));
|
||||
|
||||
const makeSelectFormValidations = () =>
|
||||
createSelector(selectEditDomain(), substate => substate.get('formValidations'));
|
||||
|
||||
const makeSelectFormErrors = () =>
|
||||
createSelector(selectEditDomain(), substate => substate.get('formErrors'));
|
||||
|
||||
const makeSelectDidCheckErrors = () =>
|
||||
createSelector(selectEditDomain(), substate => substate.get('didCheckErrors'));
|
||||
|
||||
const makeSelectEditSuccess = () =>
|
||||
createSelector(selectEditDomain(), substate => substate.get('editSuccess'));
|
||||
|
||||
export default selectEditDomain;
|
||||
export {
|
||||
makeSelectRecord,
|
||||
makeSelectLoading,
|
||||
makeSelectCurrentModelName,
|
||||
makeSelectEditing,
|
||||
makeSelectDeleting,
|
||||
makeSelectIsCreating,
|
||||
makeSelectIsRelationComponentNull,
|
||||
makeSelectForm,
|
||||
makeSelectFormValidations,
|
||||
makeSelectFormErrors,
|
||||
makeSelectDidCheckErrors,
|
||||
makeSelectEditSuccess,
|
||||
};
|
||||
@ -1,17 +0,0 @@
|
||||
.containerFluid { /* stylelint-disable */
|
||||
padding: 18px 30px;
|
||||
}
|
||||
|
||||
.main_wrapper{
|
||||
background: #ffffff;
|
||||
padding: 22px 10px;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 2px 4px #E3E9F3;
|
||||
}
|
||||
|
||||
.sub_wrapper{
|
||||
background: #ffffff;
|
||||
padding: 0 25px 1px;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 2px 4px #E3E9F3;
|
||||
}
|
||||
@ -1,95 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* List actions
|
||||
*
|
||||
*/
|
||||
|
||||
import pluralize from 'pluralize';
|
||||
|
||||
import {
|
||||
DELETE_RECORD_SUCCESS,
|
||||
} from '../Edit/constants';
|
||||
|
||||
import {
|
||||
CHANGE_LIMIT,
|
||||
CHANGE_PAGE,
|
||||
CHANGE_SORT,
|
||||
DECREASE_COUNT,
|
||||
LOAD_COUNT,
|
||||
LOAD_RECORDS,
|
||||
LOADED_COUNT,
|
||||
LOADED_RECORDS,
|
||||
SET_CURRENT_MODEL_NAME,
|
||||
} from './constants';
|
||||
|
||||
export function changeLimit(limit, source) {
|
||||
return {
|
||||
type: CHANGE_LIMIT,
|
||||
limit: limit <= 0 ? 20 : limit,
|
||||
source,
|
||||
};
|
||||
}
|
||||
|
||||
export function changePage(page, source) {
|
||||
return {
|
||||
type: CHANGE_PAGE,
|
||||
page: page <= 0 ? 1 : page,
|
||||
source,
|
||||
};
|
||||
}
|
||||
|
||||
export function changeSort(sort, source) {
|
||||
return {
|
||||
type: CHANGE_SORT,
|
||||
sort,
|
||||
source,
|
||||
};
|
||||
}
|
||||
|
||||
export function decreaseCount() {
|
||||
return {
|
||||
type: DECREASE_COUNT,
|
||||
};
|
||||
}
|
||||
|
||||
export function loadCount(source) {
|
||||
return {
|
||||
type: LOAD_COUNT,
|
||||
source,
|
||||
};
|
||||
}
|
||||
|
||||
export function loadRecords(source) {
|
||||
return {
|
||||
type: LOAD_RECORDS,
|
||||
source,
|
||||
};
|
||||
}
|
||||
|
||||
export function loadedCount(count) {
|
||||
return {
|
||||
type: LOADED_COUNT,
|
||||
count,
|
||||
};
|
||||
}
|
||||
|
||||
export function loadedRecord(records) {
|
||||
return {
|
||||
type: LOADED_RECORDS,
|
||||
records,
|
||||
};
|
||||
}
|
||||
|
||||
export function setCurrentModelName(modelName) {
|
||||
return {
|
||||
type: SET_CURRENT_MODEL_NAME,
|
||||
modelName,
|
||||
modelNamePluralized: pluralize(modelName),
|
||||
};
|
||||
}
|
||||
|
||||
export function recordDeleted() {
|
||||
return {
|
||||
type: DELETE_RECORD_SUCCESS,
|
||||
};
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* List constants
|
||||
*
|
||||
*/
|
||||
|
||||
export const SET_CURRENT_MODEL_NAME = 'app/List/SET_CURRENT_MODEL_NAME';
|
||||
|
||||
export const LOAD_RECORDS = 'app/List/LOAD_RECORDS';
|
||||
export const LOADED_RECORDS = 'app/List/LOADED_RECORDS';
|
||||
|
||||
export const LOAD_COUNT = 'app/List/LOAD_COUNT';
|
||||
export const LOADED_COUNT = 'app/List/LOADED_COUNT';
|
||||
|
||||
export const CHANGE_PAGE = 'app/List/CHANGE_PAGE';
|
||||
export const CHANGE_SORT = 'app/List/CHANGE_SORT';
|
||||
export const CHANGE_LIMIT = 'app/List/CHANGE_LIMIT';
|
||||
|
||||
export const DELETE_RECORD = 'app/List/DELETE_RECORD';
|
||||
export const DECREASE_COUNT = 'app/List/DECREASE_COUNT';
|
||||
@ -1,343 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* List
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators, compose } from 'redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import PropTypes from 'prop-types';
|
||||
import { isEmpty, isUndefined, map, get, toInteger } from 'lodash';
|
||||
import { router } from 'app';
|
||||
|
||||
// Selectors.
|
||||
import { makeSelectModels, makeSelectSchema } from 'containers/App/selectors';
|
||||
|
||||
// Components.
|
||||
import Table from 'components/Table';
|
||||
import TableFooter from 'components/TableFooter';
|
||||
import PluginHeader from 'components/PluginHeader';
|
||||
import PopUpWarning from 'components/PopUpWarning';
|
||||
|
||||
import injectReducer from 'utils/injectReducer';
|
||||
import injectSaga from 'utils/injectSaga';
|
||||
|
||||
// Utils
|
||||
import getQueryParameters from 'utils/getQueryParameters';
|
||||
|
||||
// Actions.
|
||||
import {
|
||||
deleteRecord,
|
||||
} from '../Edit/actions';
|
||||
|
||||
// Styles.
|
||||
import styles from './styles.scss';
|
||||
|
||||
// Actions.
|
||||
import {
|
||||
setCurrentModelName,
|
||||
loadRecords,
|
||||
loadCount,
|
||||
changePage,
|
||||
changeSort,
|
||||
changeLimit,
|
||||
} from './actions';
|
||||
|
||||
// Selectors.
|
||||
import {
|
||||
makeSelectRecords,
|
||||
makeSelectCurrentModelName,
|
||||
makeSelectCurrentModelNamePluralized,
|
||||
makeSelectCount,
|
||||
makeSelectCurrentPage,
|
||||
makeSelectLimit,
|
||||
makeSelectSort,
|
||||
makeSelectLoadingCount,
|
||||
} from './selectors';
|
||||
|
||||
import reducer from './reducer';
|
||||
import saga from './sagas';
|
||||
|
||||
export class List extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
showWarning: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Init the view
|
||||
this.init(this.props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.source = getQueryParameters(nextProps.location.search, 'source');
|
||||
|
||||
const locationChanged = nextProps.location.pathname !== this.props.location.pathname;
|
||||
|
||||
if (locationChanged) {
|
||||
this.init(nextProps);
|
||||
}
|
||||
|
||||
if (!isEmpty(nextProps.location.search) && this.props.location.search !== nextProps.location.search) {
|
||||
this.props.loadRecords(getQueryParameters(nextProps.location.search, 'source'));
|
||||
}
|
||||
}
|
||||
|
||||
init(props) {
|
||||
const source = getQueryParameters(props.location.search, 'source');
|
||||
const slug = props.match.params.slug;
|
||||
|
||||
// Set current model name
|
||||
this.props.setCurrentModelName(slug.toLowerCase());
|
||||
|
||||
const sort = (isEmpty(props.location.search) ?
|
||||
get(this.props.models, ['models', slug.toLowerCase(), 'primaryKey']) || get(this.props.models.plugins, [source, 'models', slug.toLowerCase(), 'primaryKey']) :
|
||||
getQueryParameters('sort')) || 'id';
|
||||
|
||||
if (!isEmpty(props.location.search)) {
|
||||
this.props.changePage(toInteger(getQueryParameters(props.location.search, 'page')), source);
|
||||
this.props.changeLimit(toInteger(getQueryParameters(props.location.search, 'limit')), source);
|
||||
}
|
||||
|
||||
this.props.changeSort(sort, source);
|
||||
|
||||
// Load records
|
||||
this.props.loadRecords(source);
|
||||
|
||||
// Get the records count
|
||||
this.props.loadCount(source);
|
||||
|
||||
// Define the `create` route url
|
||||
this.addRoute = `${this.props.match.path.replace(':slug', slug)}/create`;
|
||||
}
|
||||
|
||||
handleChangeLimit = ({ target }) => {
|
||||
const source = getQueryParameters(this.props.location.search, 'source');
|
||||
this.props.changeLimit(toInteger(target.value), source);
|
||||
router.push({
|
||||
pathname: this.props.location.pathname,
|
||||
search: `?page=${this.props.currentPage}&limit=${target.value}&sort=${this.props.sort}&source=${source}`,
|
||||
});
|
||||
}
|
||||
|
||||
handleChangePage = (page) => {
|
||||
const source = getQueryParameters(this.props.location.search, 'source');
|
||||
router.push({
|
||||
pathname: this.props.location.pathname,
|
||||
search: `?page=${page}&limit=${this.props.limit}&sort=${this.props.sort}&source=${source}`,
|
||||
});
|
||||
this.props.changePage(page, source);
|
||||
}
|
||||
|
||||
handleChangeSort = (sort) => {
|
||||
const source = getQueryParameters(this.props.location.search, 'source');
|
||||
router.push({
|
||||
pathname: this.props.location.pathname,
|
||||
search: `?page=${this.props.currentPage}&limit=${this.props.limit}&sort=${sort}&source=${source}`,
|
||||
});
|
||||
this.props.changeSort(sort, source);
|
||||
}
|
||||
|
||||
handleDelete = (e) => {
|
||||
const source = getQueryParameters(this.props.location.search, 'source');
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.props.deleteRecord(this.state.target, this.props.currentModelName, source);
|
||||
this.setState({ showWarning: false });
|
||||
}
|
||||
|
||||
toggleModalWarning = (e) => {
|
||||
if (!isUndefined(e)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.setState({
|
||||
target: e.target.id,
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({ showWarning: !this.state.showWarning });
|
||||
}
|
||||
|
||||
render() {
|
||||
const source = getQueryParameters(this.props.location.search, 'source');
|
||||
// Detect current model structure from models list
|
||||
const currentModel = get(this.props.models, ['models', this.props.currentModelName]) || get(this.props.models, ['plugins', source, 'models', this.props.currentModelName]);
|
||||
const currentSchema = get(this.props.schema, [this.props.currentModelName]) || get(this.props.schema, ['plugins', source, this.props.currentModelName]);
|
||||
|
||||
if (!this.props.currentModelName || !currentSchema) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
// Define table headers
|
||||
const tableHeaders = map(currentSchema.list, (value) => ({
|
||||
name: value,
|
||||
label: currentSchema.fields[value].label,
|
||||
type: currentSchema.fields[value].type,
|
||||
}));
|
||||
|
||||
tableHeaders.splice(0, 0, { name: currentModel.primaryKey || 'id', label: 'Id', type: 'string' });
|
||||
|
||||
const content = (
|
||||
<Table
|
||||
records={this.props.records}
|
||||
route={this.props.match}
|
||||
routeParams={this.props.match.params}
|
||||
headers={tableHeaders}
|
||||
onChangeSort={this.handleChangeSort}
|
||||
sort={this.props.sort}
|
||||
history={this.props.history}
|
||||
primaryKey={currentModel.primaryKey || 'id'}
|
||||
handleDelete={this.toggleModalWarning}
|
||||
redirectUrl={`?redirectUrl=/plugins/content-manager/${this.props.currentModelName.toLowerCase()}?page=${this.props.currentPage}&limit=${this.props.limit}&sort=${this.props.sort}&source=${source}`}
|
||||
/>
|
||||
);
|
||||
|
||||
// Plugin header config
|
||||
const pluginHeaderTitle = currentSchema.label || 'Content Manager';
|
||||
|
||||
// Define plugin header actions
|
||||
const pluginHeaderActions = [
|
||||
{
|
||||
label: 'content-manager.containers.List.addAnEntry',
|
||||
labelValues: {
|
||||
entity: pluginHeaderTitle,
|
||||
},
|
||||
kind: 'primaryAddShape',
|
||||
onClick: () => this.context.router.history.push({
|
||||
pathname: this.addRoute,
|
||||
search: `?source=${source}`,
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={`container-fluid ${styles.containerFluid}`}>
|
||||
<PluginHeader
|
||||
title={{
|
||||
id: pluginHeaderTitle,
|
||||
}}
|
||||
description={{
|
||||
id: this.props.count > 1 ? 'content-manager.containers.List.pluginHeaderDescription' : 'content-manager.containers.List.pluginHeaderDescription.singular',
|
||||
values: {
|
||||
label: this.props.count,
|
||||
},
|
||||
}}
|
||||
actions={pluginHeaderActions}
|
||||
/>
|
||||
<div className={`row ${styles.row}`}>
|
||||
<div className='col-lg-12'>
|
||||
{content}
|
||||
<PopUpWarning
|
||||
isOpen={this.state.showWarning}
|
||||
toggleModal={this.toggleModalWarning}
|
||||
content={{
|
||||
title: 'content-manager.popUpWarning.title',
|
||||
message: 'content-manager.popUpWarning.bodyMessage.contentType.delete',
|
||||
cancel: 'content-manager.popUpWarning.button.cancel',
|
||||
confirm: 'content-manager.popUpWarning.button.confirm',
|
||||
}}
|
||||
popUpWarningType={'danger'}
|
||||
onConfirm={this.handleDelete}
|
||||
/>
|
||||
<TableFooter
|
||||
limit={this.props.limit}
|
||||
currentPage={this.props.currentPage}
|
||||
onChangePage={this.handleChangePage}
|
||||
count={this.props.count}
|
||||
className="push-lg-right"
|
||||
onChangeLimit={this.handleChangeLimit}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List.contextTypes = {
|
||||
router: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
List.propTypes = {
|
||||
changeLimit: PropTypes.func.isRequired,
|
||||
changePage: PropTypes.func.isRequired,
|
||||
changeSort: PropTypes.func.isRequired,
|
||||
count: PropTypes.oneOfType([
|
||||
PropTypes.number,
|
||||
PropTypes.bool,
|
||||
]).isRequired,
|
||||
currentModelName: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.bool,
|
||||
]).isRequired,
|
||||
currentPage: PropTypes.number.isRequired,
|
||||
deleteRecord: PropTypes.func.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
limit: PropTypes.number.isRequired,
|
||||
loadCount: PropTypes.func.isRequired,
|
||||
loadRecords: PropTypes.func.isRequired,
|
||||
location: PropTypes.object.isRequired,
|
||||
match: PropTypes.object.isRequired,
|
||||
models: PropTypes.oneOfType([
|
||||
PropTypes.object,
|
||||
PropTypes.bool,
|
||||
]).isRequired,
|
||||
records: PropTypes.oneOfType([
|
||||
PropTypes.array,
|
||||
PropTypes.object,
|
||||
]).isRequired,
|
||||
// route: PropTypes.object.isRequired,
|
||||
schema: PropTypes.oneOfType([
|
||||
PropTypes.bool,
|
||||
PropTypes.object,
|
||||
]).isRequired,
|
||||
setCurrentModelName: PropTypes.func.isRequired,
|
||||
sort: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators(
|
||||
{
|
||||
deleteRecord,
|
||||
setCurrentModelName,
|
||||
loadRecords,
|
||||
loadCount,
|
||||
changePage,
|
||||
changeSort,
|
||||
changeLimit,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
records: makeSelectRecords(),
|
||||
count: makeSelectCount(),
|
||||
loadingCount: makeSelectLoadingCount(),
|
||||
models: makeSelectModels(),
|
||||
currentPage: makeSelectCurrentPage(),
|
||||
limit: makeSelectLimit(),
|
||||
sort: makeSelectSort(),
|
||||
currentModelName: makeSelectCurrentModelName(),
|
||||
currentModelNamePluralized: makeSelectCurrentModelNamePluralized(),
|
||||
schema: makeSelectSchema(),
|
||||
});
|
||||
|
||||
const withConnect = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
const withReducer = injectReducer({ key: 'list', reducer });
|
||||
const withSaga = injectSaga({ key: 'list', saga });
|
||||
|
||||
export default compose(
|
||||
withReducer,
|
||||
withSaga,
|
||||
withConnect,
|
||||
)(List);
|
||||
@ -1,10 +0,0 @@
|
||||
{
|
||||
"addAnEntry": {
|
||||
"id": "contentManager.containers.List.addAnEntry",
|
||||
"defaultMessage": "Add an entry"
|
||||
},
|
||||
"pluginHeaderDescription": {
|
||||
"id": "contentManager.containers.List.pluginHeaderDescription",
|
||||
"defaultMessage": "Manage your {label}"
|
||||
}
|
||||
}
|
||||
@ -1,78 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* List reducer
|
||||
*
|
||||
*/
|
||||
|
||||
// Dependencies.
|
||||
import { fromJS } from 'immutable';
|
||||
|
||||
// Constants.
|
||||
|
||||
import {
|
||||
DELETE_RECORD_SUCCESS,
|
||||
} from '../Edit/constants';
|
||||
|
||||
import {
|
||||
SET_CURRENT_MODEL_NAME,
|
||||
LOAD_RECORDS,
|
||||
LOADED_RECORDS,
|
||||
LOAD_COUNT,
|
||||
LOADED_COUNT,
|
||||
CHANGE_PAGE,
|
||||
CHANGE_SORT,
|
||||
CHANGE_LIMIT,
|
||||
DECREASE_COUNT,
|
||||
} from './constants';
|
||||
|
||||
const initialState = fromJS({
|
||||
currentModel: false,
|
||||
currentModelName: false,
|
||||
currentModelNamePluralized: false,
|
||||
loadingRecords: true,
|
||||
records: [],
|
||||
loadingCount: true,
|
||||
count: false,
|
||||
currentPage: 1,
|
||||
limit: 10,
|
||||
sort: 'id',
|
||||
initLimit: 10,
|
||||
initSort: 'id',
|
||||
});
|
||||
|
||||
function listReducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case DECREASE_COUNT:
|
||||
return state.update('count', (value) => value - 1);
|
||||
case SET_CURRENT_MODEL_NAME:
|
||||
return state
|
||||
.set('currentModelName', action.modelName)
|
||||
.set('currentModelNamePluralized', action.modelNamePluralized);
|
||||
case LOAD_RECORDS:
|
||||
return state.set('loadingRecords', true);
|
||||
case LOADED_RECORDS:
|
||||
return state.set('loadingRecords', false).set('records', action.records);
|
||||
case LOAD_COUNT:
|
||||
return state.set('loadingCount', true);
|
||||
case LOADED_COUNT:
|
||||
return state.set('loadingCount', false).set('count', action.count);
|
||||
case CHANGE_PAGE:
|
||||
return state.set('currentPage', action.page);
|
||||
case CHANGE_SORT:
|
||||
return state.set('sort', action.sort);
|
||||
case CHANGE_LIMIT:
|
||||
return state.set('limit', action.limit);
|
||||
case DELETE_RECORD_SUCCESS:
|
||||
return state.set('records', state.get('records').filter(o => {
|
||||
if (o._id) {
|
||||
return o._id !== action.id;
|
||||
}
|
||||
|
||||
return o.id !== action.id;
|
||||
}));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default listReducer;
|
||||
@ -1,96 +0,0 @@
|
||||
// Dependencies.
|
||||
import { LOCATION_CHANGE } from 'react-router-redux';
|
||||
import { put, select, fork, call, take, cancel, takeLatest } from 'redux-saga/effects';
|
||||
|
||||
// Utils.
|
||||
import request from 'utils/request';
|
||||
|
||||
// Constants.
|
||||
import { DELETE_RECORD } from '../Edit/constants';
|
||||
|
||||
// Sagas.
|
||||
import { deleteRecord } from '../Edit/sagas';
|
||||
|
||||
// Actions.
|
||||
import { loadedRecord, loadedCount } from './actions';
|
||||
|
||||
// Constants.
|
||||
import { LOAD_RECORDS, LOAD_COUNT } from './constants';
|
||||
|
||||
// Selectors.
|
||||
import {
|
||||
makeSelectCurrentModelName,
|
||||
makeSelectLimit,
|
||||
makeSelectCurrentPage,
|
||||
makeSelectSort,
|
||||
} from './selectors';
|
||||
|
||||
export function* getRecords(action) {
|
||||
const currentModel = yield select(makeSelectCurrentModelName());
|
||||
const limit = yield select(makeSelectLimit());
|
||||
const currentPage = yield select(makeSelectCurrentPage());
|
||||
const sort = yield select(makeSelectSort());
|
||||
// Calculate the number of values to be skip
|
||||
const skip = (currentPage - 1) * limit;
|
||||
|
||||
// Init `params` object
|
||||
const params = {
|
||||
skip,
|
||||
limit,
|
||||
sort,
|
||||
};
|
||||
|
||||
if (action.source !== undefined) {
|
||||
params.source = action.source;
|
||||
}
|
||||
|
||||
try {
|
||||
const requestUrl = `/content-manager/explorer/${currentModel}`;
|
||||
// Call our request helper (see 'utils/request')
|
||||
const response = yield call(request, requestUrl, {
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
|
||||
yield put(loadedRecord(response));
|
||||
} catch (err) {
|
||||
strapi.notification.error('content-manager.error.records.fetch');
|
||||
}
|
||||
}
|
||||
|
||||
export function* getCount(action) {
|
||||
const currentModel = yield select(makeSelectCurrentModelName());
|
||||
const params = {};
|
||||
|
||||
if (action.source !== undefined) {
|
||||
params.source = action.source;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = yield call(request,`/content-manager/explorer/${currentModel}/count`, {
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
|
||||
yield put(loadedCount(response.count));
|
||||
} catch (err) {
|
||||
strapi.notification.error('content-manager.error.records.count');
|
||||
}
|
||||
}
|
||||
|
||||
// Individual exports for testing
|
||||
export function* defaultSaga() {
|
||||
const loadRecordsWatcher = yield fork(takeLatest, LOAD_RECORDS, getRecords);
|
||||
const loudCountWatcher = yield fork(takeLatest, LOAD_COUNT, getCount);
|
||||
const deleteRecordWatcher = yield fork(takeLatest, DELETE_RECORD, deleteRecord);
|
||||
|
||||
// Suspend execution until location changes
|
||||
yield take(LOCATION_CHANGE);
|
||||
|
||||
yield cancel(loadRecordsWatcher);
|
||||
yield cancel(loudCountWatcher);
|
||||
yield cancel(deleteRecordWatcher);
|
||||
}
|
||||
|
||||
// All sagas to be loaded
|
||||
export default defaultSaga;
|
||||
@ -1,60 +0,0 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
/**
|
||||
* Direct selector to the list state domain
|
||||
*/
|
||||
const selectListDomain = () => state => state.get('list');
|
||||
|
||||
/**
|
||||
* Other specific selectors
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default selector used by List
|
||||
*/
|
||||
|
||||
const makeSelectRecords = () =>
|
||||
createSelector(selectListDomain(), substate => substate.get('records'));
|
||||
|
||||
const makeSelectLoadingRecords = () =>
|
||||
createSelector(selectListDomain(), substate =>
|
||||
substate.get('loadingRecords')
|
||||
);
|
||||
|
||||
const makeSelectCount = () =>
|
||||
createSelector(selectListDomain(), substate => substate.get('count'));
|
||||
|
||||
const makeSelectLoadingCount = () =>
|
||||
createSelector(selectListDomain(), substate => substate.get('loadingCount'));
|
||||
|
||||
const makeSelectCurrentPage = () =>
|
||||
createSelector(selectListDomain(), substate => substate.get('currentPage'));
|
||||
|
||||
const makeSelectLimit = () =>
|
||||
createSelector(selectListDomain(), substate => substate.get('limit'));
|
||||
|
||||
const makeSelectSort = () =>
|
||||
createSelector(selectListDomain(), substate => substate.get('sort'));
|
||||
|
||||
const makeSelectCurrentModelName = () =>
|
||||
createSelector(selectListDomain(), substate =>
|
||||
substate.get('currentModelName')
|
||||
);
|
||||
|
||||
const makeSelectCurrentModelNamePluralized = () =>
|
||||
createSelector(selectListDomain(), substate =>
|
||||
substate.get('currentModelNamePluralized')
|
||||
);
|
||||
|
||||
export {
|
||||
selectListDomain,
|
||||
makeSelectRecords,
|
||||
makeSelectLoadingRecords,
|
||||
makeSelectCount,
|
||||
makeSelectLoadingCount,
|
||||
makeSelectCurrentPage,
|
||||
makeSelectLimit,
|
||||
makeSelectSort,
|
||||
makeSelectCurrentModelName,
|
||||
makeSelectCurrentModelNamePluralized,
|
||||
};
|
||||
@ -1,11 +0,0 @@
|
||||
.containerFluid { /* stylelint-disable */
|
||||
padding: 18px 30px;
|
||||
|
||||
.modal-content{
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.row{
|
||||
padding-bottom: 36px;
|
||||
}
|
||||
@ -84,7 +84,7 @@ class Li extends React.Component {
|
||||
return <FileIcon key={key} fileType={item[value]} />;
|
||||
}
|
||||
|
||||
if (value !== 'url' && value !== '') {
|
||||
if (value !== '') {
|
||||
return (
|
||||
<div key={key} className={styles.truncate}>{item[value]}</div>
|
||||
);
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* App actions
|
||||
*
|
||||
*/
|
||||
@ -1,5 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* App constants
|
||||
*
|
||||
*/
|
||||
@ -6,11 +6,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
import { bindActionCreators, compose } from 'redux';
|
||||
|
||||
// Utils
|
||||
import { pluginId } from 'app';
|
||||
@ -19,39 +15,15 @@ import { pluginId } from 'app';
|
||||
import HomePage from 'containers/HomePage';
|
||||
import NotFoundPage from 'containers/NotFoundPage';
|
||||
|
||||
class App extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className={pluginId}>
|
||||
<Switch>
|
||||
<Route path={`/plugins/${pluginId}`} component={HomePage} exact />
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
App.contextTypes = {
|
||||
plugins: PropTypes.object,
|
||||
router: PropTypes.object.isRequired,
|
||||
updatePlugin: PropTypes.func,
|
||||
};
|
||||
|
||||
App.propTypes = {};
|
||||
|
||||
export function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators(
|
||||
{},
|
||||
dispatch,
|
||||
function App() {
|
||||
return (
|
||||
<div className={pluginId}>
|
||||
<Switch>
|
||||
<Route path={`/plugins/${pluginId}`} component={HomePage} exact />
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = createStructuredSelector({});
|
||||
|
||||
// Wrap the component to inject dispatch and state into it
|
||||
const withConnect = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export default compose(
|
||||
withConnect,
|
||||
)(App);
|
||||
export default App;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user