Merge "Convert SelectRolesDialog to ModalPanel"
This commit is contained in:
commit
97afdda125
|
@ -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
|
|
@ -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);
|
|
@ -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 = {
|
||||
|
|
|
@ -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>
|
||||
));
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -175,3 +175,7 @@ tbody > tr > td.blank-slate-pf {
|
|||
.toast-notifications-list-pf {
|
||||
z-index: 1060;
|
||||
}
|
||||
|
||||
p.lead {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue