Display databases and PopUpForm for adding a new database, fix pluginleftmenu active links

This commit is contained in:
cyril lopez 2017-08-03 13:51:46 +02:00
parent e4ee6bf3be
commit bb064478f4
18 changed files with 291 additions and 114 deletions

View File

@ -8,11 +8,13 @@ import React from 'react';
import { Link } from 'react-router';
import { join, map, take } from 'lodash';
import EditForm from 'components/EditForm';
import List from 'components/List';
import styles from './styles.scss';
class HeaderNav extends React.Component { // eslint-disable-line react/prefer-stateless-function
render() {
const baseUrl = join(take(this.props.path.split('/'), 4), '/');
const component = this.props.renderListComponent ? <List {...this.props} /> : <EditForm {...this.props} />;
return (
<div className={styles.headerNav}>
<div className={`${styles.noPaddingLeft} container-fluid `}>
@ -35,7 +37,7 @@ class HeaderNav extends React.Component { // eslint-disable-line react/prefer-st
</div>
</div>
</div>
<EditForm {...this.props} />
{component}
</div>
);
}
@ -44,5 +46,6 @@ class HeaderNav extends React.Component { // eslint-disable-line react/prefer-st
HeaderNav.propTypes = {
links: React.PropTypes.array,
path: React.PropTypes.string,
renderListComponent: React.PropTypes.bool,
}
export default HeaderNav;

View File

@ -10,6 +10,14 @@ import { FormattedMessage } from 'react-intl';
import styles from './styles.scss';
class InputSelect extends React.Component { // eslint-disable-line react/prefer-stateless-function
componentDidMount() {
// init the select value
if (this.props.selectOptions[0].value !== '') {
const target = { name: this.props.target, value: this.props.selectOptions[0].value };
this.props.handleChange({ target });
}
}
render() {
const bootStrapClass = this.props.customBootstrapClass ? this.props.customBootstrapClass : 'col-md-6';
@ -44,7 +52,10 @@ InputSelect.propTypes = {
customBootstrapClass: React.PropTypes.string,
handleChange: React.PropTypes.func.isRequired,
name: React.PropTypes.string.isRequired,
selectOptions: React.PropTypes.array,
selectOptions: React.PropTypes.oneOfType([
React.PropTypes.array,
React.PropTypes.object, // TODO remove
]),
target: React.PropTypes.string,
value: React.PropTypes.string,
};

View File

@ -16,7 +16,7 @@
*/
import React from 'react';
import { map } from 'lodash';
import { forEach, has, map} from 'lodash';
import { FormattedMessage } from 'react-intl';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
@ -29,16 +29,55 @@ class List extends React.Component { // eslint-disable-line react/prefer-statele
super(props);
this.state = {
modal: false,
isPopUpFormValid: true,
requiredInputs: [],
};
}
componentDidMount() {
const requiredInputs = [];
forEach(this.props.sections, (section) => {
forEach(section.items, (item) => {
if (has(item.validations, 'required')) {
requiredInputs.push( item.target );
}
});
});
this.setState({ requiredInputs });
forEach(requiredInputs, (inputTarget) => {
if (!has(this.props.values, inputTarget)) {
this.setState({ isPopUpFormValid: false });
}
});
}
componentWillReceiveProps(nextProps) {
if (nextProps.values !== this.props.values) {
forEach(this.state.requiredInputs, (inputTarget) => { // eslint-disable-line consistent-return
if (has(nextProps.values, inputTarget) && nextProps.values[inputTarget] !== "") {
this.setState({ isPopUpFormValid: true });
} else {
this.setState({ isPopUpFormValid: false });
return false;
}
});
}
}
toggle = () => {
this.setState({ modal: !this.state.modal });
}
handleSubmit = () => {
this.setState({ modal: !this.state.modal });
this.props.handleListPopUpSubmit();
handleSubmit = (e) => {
e.preventDefault();
// this.setState({ modal: !this.state.modal });
if (this.state.isPopUpFormValid) {
this.setState({ modal: !this.state.modal });
this.props.handleListPopUpSubmit(e);
}
}
render() {
@ -87,14 +126,16 @@ class List extends React.Component { // eslint-disable-line react/prefer-statele
<FormattedMessage {...{id: this.props.listButtonLabel}} />
</ModalHeader>
<div className={styles.bordered} />
<ModalBody className={styles.modalBody}>
<PopUpForm {...this.props} />
</ModalBody>
<ModalFooter className={`${styles.noBorder} ${styles.flexStart} ${styles.modalFooter}`}>
{/* TODO change tthis.toggle => this.props.addLanguage */}
<Button onClick={this.handleSubmit} className={styles.primary}>Save</Button>{' '}
<Button onClick={this.toggle} className={styles.secondary}>Cancel</Button>
</ModalFooter>
<form onSubmit={this.handleSubmit}>
<ModalBody className={styles.modalBody}>
<PopUpForm {...this.props} />
</ModalBody>
<ModalFooter className={`${styles.noBorder} ${styles.modalFooter}`}>
<Button type="submit" onClick={this.handleSubmit} className={styles.primary} disabled={!this.state.isPopUpFormValid}>Save</Button>{' '}
<Button onClick={this.toggle} className={styles.secondary}>Cancel</Button>
</ModalFooter>
</form>
</Modal>
</div>
</div>
@ -116,6 +157,8 @@ List.propTypes = {
React.PropTypes.bool,
React.PropTypes.func,
]),
sections: React.PropTypes.array,
values: React.PropTypes.object,
}
export default List;

View File

@ -83,12 +83,14 @@
}
.primary {
font-family: Lato;
background: linear-gradient(315deg, #0097F6 0%, #005EEA 100%);
color: white;
width: 15rem;
}
.secondary {
font-family: Lato;
color: #F64D0A;
border: 0.1rem solid #F64D0A;
margin-left: 1.9rem!important;
@ -128,14 +130,17 @@
margin-left: 25%;
width: 50%!important;
max-width: none;
position: relative;
}
.modalBody {
padding: 0;
padding-top: .5rem;
}
.modalFooter {
margin-top: 4.5rem;
margin-bottom: 3.5rem;
padding-left: 3.1rem !important;

View File

@ -14,7 +14,7 @@ class PluginLeftMenu extends React.Component { // eslint-disable-line react/pref
return (
<div className={`${styles.pluginLeftMenu} col-md-3`}>
{map(this.props.sections, (section, index) => (
<PluginLeftMenuSection key={index} section={section} environments={this.props.environments} />
<PluginLeftMenuSection key={index} section={section} environments={this.props.environments} envParams={this.props.envParams} />
))}
</div>
);
@ -23,6 +23,7 @@ class PluginLeftMenu extends React.Component { // eslint-disable-line react/pref
PluginLeftMenu.propTypes = {
environments: React.PropTypes.array,
envParams: React.PropTypes.string,
sections: React.PropTypes.array.isRequired,
};

View File

@ -1,3 +0,0 @@
{
"environmentsRequired": [ "databases", "security", "server"]
}

View File

@ -7,19 +7,31 @@
import React from 'react';
import { Link } from 'react-router';
import { FormattedMessage } from 'react-intl';
import { includes, isEmpty } from 'lodash';
import { isEmpty, findIndex } from 'lodash';
import styles from './styles.scss';
import config from './config.json';
class PluginLeftMenuLink extends React.Component { // eslint-disable-line react/prefer-stateless-function
constructor(props) {
super(props);
this.state = {
environmentIndex: 0,
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.envParams && nextProps.envParams !== this.props.envParams) {
const environmentIndex = findIndex(nextProps.environments, ['name', nextProps.envParams]);
this.setState({ environmentIndex });
}
}
render() {
let url;
if (!isEmpty(this.props.environments)) {
url = includes(config.environmentsRequired, this.props.link.slug) ?
`${this.props.link.slug}/${this.props.environments[0].name}`
url = this.props.environmentsRequired ?
`${this.props.link.slug}/${this.props.environments[this.state.environmentIndex].name}`
: `${this.props.link.slug}`;
}
return (
<li className={styles.pluginLeftMenuLink}>
<Link className={styles.link} to={`/plugins/settings-manager/${url}`} activeClassName={styles.linkActive}>
@ -33,6 +45,8 @@ class PluginLeftMenuLink extends React.Component { // eslint-disable-line react/
PluginLeftMenuLink.propTypes = {
environments: React.PropTypes.array,
environmentsRequired: React.PropTypes.bool,
envParams: React.PropTypes.string,
link: React.PropTypes.object.isRequired,
};

View File

@ -13,13 +13,17 @@ import styles from './styles.scss';
class PluginLeftMenuSection extends React.Component { // eslint-disable-line react/prefer-stateless-function
render() {
const environmentsRequired = this.props.section.name === 'menu.section.environments';
const links = map(this.props.section.items, (item, index) => (
<PluginLeftMenuLink
key={index}
link={item}
environments={this.props.environments}
environmentsRequired={environmentsRequired}
envParams={this.props.envParams}
/>
));
return (
<div className={styles.pluginLeftMenuSection}>
<p>
@ -35,6 +39,7 @@ class PluginLeftMenuSection extends React.Component { // eslint-disable-line rea
PluginLeftMenuSection.propTypes = {
environments: React.PropTypes.array,
envParams: React.PropTypes.string,
section: React.PropTypes.object.isRequired,
};

View File

@ -16,18 +16,24 @@ class PopUpForm extends React.Component { // eslint-disable-line react/prefer-st
<div className="container-fluid">
<div className="row">
<div className="col-sm-12">
{map(this.props.sections, (section) => {
// custom rendering
if (this.props.renderPopUpForm) {
// Need to pass props to use this.props.renderInput from WithFormSection HOC
return this.props.renderPopUpForm(section, this.props);
}
return (
map(section.items, (item, key) => (
this.props.renderInput(item, key)
))
)
})}
<div className={styles.padded}>
<div className="row">
{map(this.props.sections, (section) => {
// custom rendering
if (this.props.renderPopUpForm) {
// Need to pass props to use this.props.renderInput from WithFormSection HOC
return this.props.renderPopUpForm(section, this.props, styles);
}
return (
map(section.items, (item, key) => (
this.props.renderInput(item, key)
))
);
})}
</div>
</div>
</div>
</div>
</div>
@ -38,11 +44,11 @@ class PopUpForm extends React.Component { // eslint-disable-line react/prefer-st
PopUpForm.propTypes = {
renderInput: React.PropTypes.func.isRequired,
renderPopUpForm: React.PropTypes.func,
sections: React.PropTypes.oneOfType([
React.PropTypes.array,
React.PropTypes.object,
renderPopUpForm: React.PropTypes.oneOfType([
React.PropTypes.func,
React.PropTypes.bool,
]),
sections: React.PropTypes.array,
};
export default WithFormSection(PopUpForm); // eslint-disable-line new-cap

View File

@ -1,3 +1,31 @@
.popUpForm { /* stylelint-disable */
}
.padded {
padding: 0 15px 0 15px;
}
.defaultConnection {
position: absolute;
bottom: -9rem;
left: 3rem;
display: flex;
}
.rounded {
margin-top: .5rem;
margin-left: 1rem;
width: 1.2rem;
height: 1.2rem;
border-radius: 50%;
padding-left: .1rem;
padding-bottom: .1rem;
border: 1px solid black;
font-size: .4rem;
vertical-align: middle;
display: flex;
align-items: center;
align-content: center;
}

View File

@ -1,4 +1,5 @@
{
"application.description": "col-md-6 offset-md-6 pull-md-6",
"application.version": "col-md-6 offset-md-6 pull-md-6"
"application.version": "col-md-6 offset-md-6 pull-md-6",
"databases.connections.${name}.settings.host": "col-md-12"
}

View File

@ -95,6 +95,7 @@ const WithFormSection = (InnerComponent) => class extends React.Component {
// custom handleChange props for nested input form
const handleChange = this.state.hasNestedInput ? this.handleChange : this.props.handleChange;
return (
<Input
customBootstrapClass={customBootstrapClass}

View File

@ -46,7 +46,7 @@ class App extends React.Component {
<div className={`container-fluid ${styles.noPadding}`}>
<div className={styles.baseline}></div>
<div className="row">
<PluginLeftMenu sections={this.props.sections} environments={this.props.environments} />
<PluginLeftMenu sections={this.props.sections} environments={this.props.environments} envParams={this.props.params.env} />
{React.Children.toArray(content)}
</div>
</div>
@ -66,6 +66,7 @@ App.propTypes = {
exposedComponents: React.PropTypes.object.isRequired,
loading: React.PropTypes.bool,
menuFetch: React.PropTypes.func,
params: React.PropTypes.object,
sections: React.PropTypes.array.isRequired,
};

View File

@ -155,15 +155,21 @@ export function databasesFetch(environment) {
};
}
export function databasesFetchSucceeded(data) {
export function databasesFetchSucceeded(listDatabases, appDatabases) {
const configsDisplay = {
name: 'form.databases.name',
description: 'form.databases.description',
sections: data.databases,
sections: listDatabases.databases,
};
const modifiedData = {
'databases.defaultConnection': false,
};
return {
type: DATABASES_FETCH_SUCCEEDED,
configsDisplay,
appDatabases,
modifiedData,
};
}

View File

@ -1,6 +1,6 @@
{
"customComponents": {
"editForm": ["general", "advanced"],
"list": ["languages", "databases"]
"list": ["languages"]
}
}

View File

@ -88,7 +88,7 @@ export class Home extends React.Component { // eslint-disable-line react/prefer-
}
} else if (this.props.params.env !== nextProps.params.env && nextProps.params.env && this.props.params.env) {
// get data if params env updated
this.props.configFetch(`${this.props.params.slug}/${nextProps.params.env}`);
this.handleFetch(nextProps);
}
}
@ -98,6 +98,41 @@ export class Home extends React.Component { // eslint-disable-line react/prefer-
}
}
/* eslint-disable react/sort-comp */
/* eslint-disable jsx-a11y/no-static-element-interactions */
addConnection = (e) => {
e.preventDefault();
// TODO add connection
console.log('added');
}
changeDefaultLanguage = ({ target }) => {
// create new object configsDisplay based on store property configsDisplay
const configsDisplay = {
name: this.props.home.configsDisplay.name,
description: this.props.home.configsDisplay.description,
sections: [],
};
// Find the index of the new setted language
const activeLanguageIndex = findIndex(this.props.home.configsDisplay.sections, ['name', target.id]);
forEach(this.props.home.configsDisplay.sections, (section, key) => {
// set all Language active state to false
if (key !== activeLanguageIndex) {
configsDisplay.sections.push({ name: section.name, active: false });
} else {
// set the new language active state to true
configsDisplay.sections.push({ name: section.name, active: true });
}
});
// reset all the configs to ensure component is updated
this.props.changeDefaultLanguage(configsDisplay, target.id);
// Edit the new config
this.props.editSettings({ 'i18n.i18n.defaultLocale': target.id }, 'i18n');
}
handleFetch(props) {
const apiUrl = props.params.env ? `${props.params.slug}/${props.params.env}` : props.params.slug;
@ -151,47 +186,6 @@ export class Home extends React.Component { // eslint-disable-line react/prefer-
console.log('will detele');
}
addLanguage = () => {
this.props.newLanguagePost();
}
showDatabaseModal = () => {
// allow state here just for modal purpose
this.setState({ modal: !this.state.modal });
}
changeDefaultLanguage = ({ target }) => {
// create new object configsDisplay based on store property configsDisplay
const configsDisplay = {
name: this.props.home.configsDisplay.name,
description: this.props.home.configsDisplay.description,
sections: [],
};
// Find the index of the new setted language
const activeLanguageIndex = findIndex(this.props.home.configsDisplay.sections, ['name', target.id]);
forEach(this.props.home.configsDisplay.sections, (section, key) => {
// set all Language active state to false
if (key !== activeLanguageIndex) {
configsDisplay.sections.push({ name: section.name, active: false });
} else {
// set the new language active state to true
configsDisplay.sections.push({ name: section.name, active: true });
}
});
// reset all the configs to ensure component is updated
this.props.changeDefaultLanguage(configsDisplay, target.id);
// Edit the new config
this.props.editSettings({ 'i18n.i18n.defaultLocale': target.id }, 'i18n');
}
// Hide database modal
toggle = () => {
this.setState({ modal: !this.state.modal });
}
// custom Row rendering for the component List with params slug === languages
renderRowLanguage = (props, key, liStyles) => {
// assign the target id the language name to prepare for delete
@ -238,12 +232,38 @@ export class Home extends React.Component { // eslint-disable-line react/prefer-
renderListButtonLabel = () => `list.${this.props.params.slug}.button.label`;
renderPopUpFormDatabase = (section, props, popUpStyles) => (
map(section.items, (item, key) => {
const isActive = props.values[item.target] ?
<div className={popUpStyles.rounded}><i className="fa fa-check" /></div> : '';
if (item.name === 'form.database.item.default') {
return (
<div
key={key}
className={popUpStyles.defaultConnection}
id={item.target}
onClick={this.setDefaultConnectionDb}
>
{item.name}{isActive}
</div>
);
}
return (
props.renderInput(item, key)
);
})
)
setDefaultConnectionDb = (e) => {
const target = { name: e.target.id, value: !this.props.home.modifiedData[e.target.id] }
this.handleChange({target});
}
renderPopUpFormLanguage = (section, props) => (
map(section.items, (item, key) => (
<div key={key}>
<form>
{props.renderInput(item, key)}
</form>
{props.renderInput(item, key)}
</div>
))
)
@ -259,7 +279,7 @@ export class Home extends React.Component { // eslint-disable-line react/prefer-
default:
provider = 'N/A';
}
/* eslint-disable jsx-a11y/no-static-element-interactions */
return (
<li key={key}>
<div className={listStyles.flexLi}>
@ -283,13 +303,15 @@ export class Home extends React.Component { // eslint-disable-line react/prefer-
Databases
</ModalHeader>
<div className={listStyles.bordered} />
<ModalBody className={listStyles.modalBody}>
</ModalBody>
<ModalFooter className={`${listStyles.noBorder} ${listStyles.flexStart} ${listStyles.modalFooter}`}>
{/* TODO change tthis.toggle => this.props.addLanguage */}
<Button onClick={this.handleSubmit} className={listStyles.primary}>Save</Button>{' '}
<Button onClick={this.toggle} className={listStyles.secondary}>Cancel</Button>
</ModalFooter>
<form>
<ModalBody className={listStyles.modalBody}>
</ModalBody>
<ModalFooter className={`${listStyles.noBorder} ${listStyles.flexStart} ${listStyles.modalFooter}`}>
<Button onClick={this.handleSubmit} className={listStyles.primary}>Save</Button>{' '}
<Button onClick={this.toggle} className={listStyles.secondary}>Cancel</Button>
</ModalFooter>
</form>
</Modal>
</div>
</li>
@ -305,24 +327,36 @@ export class Home extends React.Component { // eslint-disable-line react/prefer-
const listTitle = this.props.params.slug === 'languages' || 'databases' ? this.renderListTitle() : '';
const listButtonLabel = this.props.params.slug === 'languages' || 'databases' ? this.renderListButtonLabel() : '';
// sections is the props used by EditForm in case of list of table rendering we need to change its value
const sections = this.props.params.slug === 'languages' ? this.props.home.listLanguages.sections : this.props.home.configsDisplay.sections;
// check if HeaderNav component needs to render a form or a list
const renderListComponent = this.props.params.slug === 'databases';
let handleListPopUpSubmit;
// sections is the props used by EditForm in case of list of table rendering we need to change its value
let sections;
let renderPopUpForm = false;
let renderRow = false;
switch (this.props.params.slug) {
case 'languages':
sections = this.props.home.listLanguages.sections;
// custom rendering for PopUpForm
renderPopUpForm = this.renderPopUpFormLanguage;
renderRow = this.renderRowLanguage;
handleListPopUpSubmit = this.props.newLanguagePost;
break;
case 'databases':
sections = this.props.home.addDatabaseSection.sections;
renderPopUpForm = this.renderPopUpFormDatabase
handleListPopUpSubmit = this.addConnection;
renderRow = this.renderRowDatabase;
break;
default:
sections = this.props.home.configsDisplay.sections;
}
// custom selectOptions for languages
const selectOptions = this.props.params.slug === 'languages' ? this.props.home.listLanguages : [];
// custom rendering for PopUpForm
const renderPopUpForm = this.props.params.slug === 'languages' ? this.renderPopUpFormLanguage : false;
let renderRow = false;
if (this.props.params.slug === 'languages') {
renderRow = this.renderRowLanguage;
} else if (this.props.params.slug === 'databases') {
renderRow = this.renderRowDatabase;
}
return (
<Component
sections={sections}
@ -338,13 +372,27 @@ export class Home extends React.Component { // eslint-disable-line react/prefer-
listTitle={listTitle}
listButtonLabel={listButtonLabel}
handlei18n
handleListPopUpSubmit={this.addLanguage}
handleListPopUpSubmit={handleListPopUpSubmit}
selectOptions={selectOptions}
renderPopUpForm={renderPopUpForm}
renderListComponent={renderListComponent}
/>
);
}
showDatabaseModal = () => {
// allow state here just for modal purpose
this.setState({ modal: !this.state.modal });
}
// Hide database modal
toggle = () => {
this.setState({ modal: !this.state.modal });
}
render() {
if (this.props.home.loading) {
return <div />;

View File

@ -23,6 +23,7 @@ const initialState = fromJS({
initialData: Map(),
modifiedData: Map(),
listLanguages: Map(),
addDatabaseSection: Map(),
didCreatedNewLanguage: false,
});
@ -41,9 +42,10 @@ function homeReducer(state = initialState, action) {
case DATABASES_FETCH_SUCCEEDED:
return state
.set('configsDisplay', OrderedMap(action.configsDisplay))
.set('addDatabaseSection', OrderedMap(action.appDatabases))
.set('loading', false)
.set('initialData', Map())
.set('modifiedData', Map());
.set('modifiedData', Map(action.modifiedData));
case LANGUAGES_FETCH_SUCCEEDED:
return state
.set('loading', false)

View File

@ -71,11 +71,15 @@ export function* fetchDatabases(action) {
method: 'GET',
};
const requestUrl = `/settings-manager/configurations/databases/${action.environment}`;
const requestUrlListDatabases = `/settings-manager/configurations/databases/${action.environment}`;
const requestUrlAppDatabases = '/settings-manager/configurations/database/model';
const data = yield call(request, requestUrl, opts);
const [listDatabasesData, appDatabaseData] = yield [
call(request, requestUrlListDatabases, opts),
call(request, requestUrlAppDatabases, opts),
];
yield put(databasesFetchSucceeded(data));
yield put(databasesFetchSucceeded(listDatabasesData, appDatabaseData));
} catch(error) {
window.Strapi.notification.error('An error occurred');
@ -128,6 +132,7 @@ export function* postLanguage() {
yield put(languageActiontSucceded());
} catch(error) {
console.log(error);
// TODO handle error i18n
window.Strapi.notification.error(error);
}