Merge "Convert SelectRolesDialog to ModalPanel"

This commit is contained in:
Zuul 2018-11-19 13:04:17 +00:00 committed by Gerrit Code Review
commit 97afdda125
8 changed files with 241 additions and 53 deletions

View File

@ -0,0 +1,10 @@
features:
- |
Available Roles in Roles selection dialog are now displayed in scrollable
modal panel view, which allows to submit the form more easily.
- |
Role cards are now equal size, to make the cards more organized. Description
is truncated when needed.
- |
Clicking Role name opens Role details dialog which contains title, complete
description, tags, role networks and services

View File

@ -0,0 +1,149 @@
/**
* Copyright 2018 Red Hat Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, { Component, Fragment } from 'react';
import { MessageDialog, Label } from 'patternfly-react';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
const messages = defineMessages({
dialogTitle: {
id: 'AvailableRoleDetailDialog.dialogTitle',
defaultMessage: 'Detailed Role Information'
},
descriptionLabel: {
id: 'AvailableRoleDetailDialog.descriptionLabel',
defaultMessage: 'Description:'
},
tagsLabel: {
id: 'AvailableRoleDetailDialog.tagsLabel',
defaultMessage: 'Tags:'
},
networksLabel: {
id: 'AvailableRoleDetailDialog.networksLabel',
defaultMessage: 'Networks:'
},
servicesLabel: {
id: 'AvailableRoleDetailDialog.servicesLabel',
defaultMessage: 'Services:'
},
close: {
id: 'AvailableRoleDetailDialog.close',
defaultMessage: 'Close'
},
disable: {
id: 'AvailableRoleDetailDialog.disable',
defaultMessage: 'Disable'
},
enable: {
id: 'AvailableRoleDetailDialog.enable',
defaultMessage: 'Enable'
}
});
class AvailableRoleDetailDialog extends Component {
state = {
show: false
};
primaryAction = () => {
this.props.toggle();
this.secondaryAction();
};
secondaryAction = () => {
this.setState(() => ({ show: false }));
};
showModal = () => {
this.setState(() => ({ show: true }));
};
render() {
const {
enabled,
intl: { formatMessage },
role: { name, description, networks, tags, ServicesDefault }
} = this.props;
const primaryContent = <p className="lead">{name}</p>;
const secondaryContent = (
<Fragment>
<p>
<strong>
<FormattedMessage {...messages.descriptionLabel} />
</strong>{' '}
<br />
{description}
</p>
{!tags.isEmpty() && (
<p>
<strong>
<FormattedMessage {...messages.tagsLabel} />
</strong>{' '}
{tags.map(t => (
<span key={t}>
<Label key={t}>{t}</Label>{' '}
</span>
))}
</p>
)}
<strong>
<FormattedMessage {...messages.networksLabel} />
</strong>
<ul>{networks.map(network => <li key={network}>{network}</li>)}</ul>
<strong>
<FormattedMessage {...messages.servicesLabel} />
</strong>
<ul>
{ServicesDefault.map(service => <li key={service}>{service}</li>)}
</ul>
</Fragment>
);
return (
<Fragment>
<a className="link" onClick={this.showModal}>
{name}
</a>
<MessageDialog
show={this.state.show}
onHide={this.secondaryAction}
primaryAction={this.primaryAction}
secondaryAction={this.secondaryAction}
primaryActionButtonContent={
enabled
? formatMessage(messages.disable)
: formatMessage(messages.enable)
}
secondaryActionButtonContent={formatMessage(messages.close)}
title={formatMessage(messages.dialogTitle)}
primaryContent={primaryContent}
secondaryContent={secondaryContent}
/>
</Fragment>
);
}
}
AvailableRoleDetailDialog.propTypes = {
ServicesDefault: ImmutablePropTypes.list.isRequired,
enabled: PropTypes.bool.isRequired,
intl: PropTypes.object.isRequired,
role: ImmutablePropTypes.record.isRequired,
toggle: PropTypes.func.isRequired
};
export default injectIntl(AvailableRoleDetailDialog);

View File

@ -14,11 +14,14 @@
* under the License.
*/
import { Col } from 'react-bootstrap';
import { Col, Card, CardTitle, CardBody } from 'patternfly-react';
import cx from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes';
import React from 'react';
import PropTypes from 'prop-types';
import { truncate } from 'lodash';
import AvailableRoleDetailDialog from './AvailableRoleDetailDialog';
const AvailableRoleInput = ({
className,
@ -27,17 +30,24 @@ const AvailableRoleInput = ({
style
}) => (
<Col xs={12} sm={4} lg={3} style={style}>
<div
<Card
matchHeight
accented
className={cx(
'card-pf card-pf-view card-pf-view-select card-pf-view-multi-select',
'role-card card-pf-accented',
'card-pf card-pf-view card-pf-view-select card-pf-view-multi-select role-card',
{ active: value },
role.identifier,
className
)}
>
<h2 className="card-pf-title">{name}</h2>
<div className="card-pf-body">
<CardTitle>
<AvailableRoleDetailDialog
role={role}
enabled={value}
toggle={() => onChange(!value)}
/>
</CardTitle>
<CardBody>
{!role.tags.isEmpty() && (
<h6>
{role.tags.map(t => (
@ -47,8 +57,10 @@ const AvailableRoleInput = ({
))}
</h6>
)}
<p className="card-pf-info">{role.description}</p>
</div>
<p className="card-pf-info">
{truncate(role.description, { length: 80 })}
</p>
</CardBody>
<div
className="card-pf-view-checkbox"
style={{ right: 15, left: 'auto' }}
@ -59,7 +71,7 @@ const AvailableRoleInput = ({
checked={value}
/>
</div>
</div>
</Card>
</Col>
);
AvailableRoleInput.propTypes = {

View File

@ -60,7 +60,7 @@ class RoleServices extends React.Component {
isActive={service.id === this.state.selectedService}
>
<a className="link" onClick={this.selectService.bind(this, service.id)}>
{service.type.split('::').pop()}
{service.type.split('OS::TripleO::Services::').pop()}
</a>
</Tab>
));

View File

@ -24,7 +24,7 @@ import { pickBy } from 'lodash';
import PropTypes from 'prop-types';
import AvailableRoleInput from './AvailableRoleInput';
import { CloseModalXButton, RoutedModal } from '../ui/Modals';
import { CloseModalXButton, RoutedModalPanel } from '../ui/Modals';
import { getMergedRoles, getRoles } from '../../selectors/roles';
import { getCurrentPlanName } from '../../selectors/plans';
import { Loader } from '../ui/Loader';
@ -62,7 +62,7 @@ class SelectRolesDialog extends React.Component {
roles
} = this.props;
return (
<RoutedModal bsSize="xl" redirectPath={`/plans/${currentPlanName}`}>
<RoutedModalPanel redirectPath={`/plans/${currentPlanName}`}>
<ModalHeader>
<CloseModalXButton />
<ModalTitle>
@ -73,6 +73,7 @@ class SelectRolesDialog extends React.Component {
height={100}
loaded={availableRolesLoaded && !fetchingAvailableRoles}
content={formatMessage(messages.loadingAvailableRoles)}
componentProps={{ className: 'flex-container' }}
>
<SelectRolesForm
onSubmit={this.handleFormSubmit}
@ -94,7 +95,7 @@ class SelectRolesDialog extends React.Component {
))}
</SelectRolesForm>
</Loader>
</RoutedModal>
</RoutedModalPanel>
);
}
}

View File

@ -14,16 +14,16 @@
* under the License.
*/
import { Button } from 'react-bootstrap';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import { ModalFooter } from 'react-bootstrap';
import { pickBy } from 'lodash';
import { OverlayLoader } from '../ui/Loader';
import PropTypes from 'prop-types';
import React from 'react';
import { reduxForm } from 'redux-form';
import { Modal, Button } from 'patternfly-react';
import { CloseModalButton } from '../ui/Modals';
import { CardGridFluid } from '../ui/cards';
import ModalFormErrorList from '../ui/forms/ModalFormErrorList';
const messages = defineMessages({
@ -46,44 +46,40 @@ const messages = defineMessages({
}
});
class SelectRolesForm extends React.Component {
render() {
const {
children,
error,
handleSubmit,
invalid,
intl: { formatMessage },
pristine,
submitting
} = this.props;
return (
<form onSubmit={handleSubmit}>
<OverlayLoader
loaded={!submitting}
content={formatMessage(messages.updatingRoles)}
>
<ModalFormErrorList errors={error ? [error] : []} />
<div className="cards-pf">
<div className="row row-cards-pf">{children}</div>
</div>
</OverlayLoader>
<ModalFooter>
<CloseModalButton>
<FormattedMessage {...messages.cancel} />
</CloseModalButton>
<Button
disabled={invalid || pristine || submitting}
bsStyle="primary"
type="submit"
>
<FormattedMessage {...messages.saveChanges} />
</Button>
</ModalFooter>
</form>
);
}
}
const SelectRolesForm = ({
children,
error,
handleSubmit,
invalid,
intl: { formatMessage },
pristine,
submitting
}) => (
<form className="flex-container" onSubmit={handleSubmit}>
<OverlayLoader
loaded={!submitting}
content={formatMessage(messages.updatingRoles)}
containerClassName="flex-container"
>
<ModalFormErrorList errors={error ? [error] : []} />
<CardGridFluid className="flex-column" matchHeight>
{children}
</CardGridFluid>
</OverlayLoader>
<Modal.Footer>
<CloseModalButton>
<FormattedMessage {...messages.cancel} />
</CloseModalButton>
<Button
disabled={invalid || pristine || submitting}
bsStyle="primary"
type="submit"
>
<FormattedMessage {...messages.saveChanges} />
</Button>
</Modal.Footer>
</form>
);
SelectRolesForm.propTypes = {
children: PropTypes.node,
currentPlanName: PropTypes.string.isRequired,

View File

@ -17,6 +17,7 @@
import cx from 'classnames';
import React from 'react';
import PropTypes from 'prop-types';
import { Row, CardGrid } from 'patternfly-react';
export const ActionCard = ({ children, className, onClick, ...rest }) => (
<div
@ -38,3 +39,18 @@ ActionCard.propTypes = {
className: PropTypes.string,
onClick: PropTypes.func.isRequired
};
export const CardGridFluid = ({ children, className, matchHeight }) => (
<div className={cx('cards-pf', className)}>
<CardGrid matchHeight={matchHeight} fluid>
<Row style={{ marginRight: '-10px', marginLeft: '-10px' }}>
{children}
</Row>
</CardGrid>
</div>
);
CardGridFluid.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
matchHeight: PropTypes.bool.isRequired
};

View File

@ -175,3 +175,7 @@ tbody > tr > td.blank-slate-pf {
.toast-notifications-list-pf {
z-index: 1060;
}
p.lead {
margin-bottom: 10px;
}