import React from "react";
import { FormattedMessage, defineMessages, injectIntl, intlShape } from "react-intl";
import PropTypes from "prop-types";
import { Tabs, Tab } from "patternfly-react";
import { Tooltip, TooltipPosition } from "@patternfly/react-core";
import { connect } from "react-redux";
import ComponentTypeIcons from "./ComponentTypeIcons";
import DependencyListView from "./DependencyListView";
import LabelWithBadge from "./LabelWithBadge";
import Loading from "../Loading/Loading";
import EmptyState from "../EmptyState/EmptyState";
import { fetchingInputDetails, fetchingInputDeps } from "../../core/actions/inputs";
const messages = defineMessages({
details: {
defaultMessage: "Details",
},
dependencies: {
defaultMessage: "Dependencies",
},
hideDetails: {
defaultMessage: "Hide Details",
},
removeFromBlueprint: {
defaultMessage: "Remove from Blueprint",
},
noDepsTitle: {
defaultMessage: "No Dependencies",
},
noDepsMessage: {
defaultMessage: "This component has no dependencies.",
},
});
class ComponentDetailsView extends React.Component {
constructor() {
super();
this.state = {
selectedBuildIndex: undefined,
savedVersion: undefined,
};
this.setBuildIndex = this.setBuildIndex.bind(this);
this.handleVersionSelect = this.handleVersionSelect.bind(this);
this.handleChildComponent = this.handleChildComponent.bind(this);
this.handleParentComponent = this.handleParentComponent.bind(this);
this.handleCloseDetails = this.handleCloseDetails.bind(this);
this.handleSelectedBuildDeps = this.handleSelectedBuildDeps.bind(this);
}
componentDidMount() {
this.props.fetchingInputDetails(this.props.component);
if (this.props.handleUpdateComponent) {
this.setBuildIndex();
} else if (this.props.dependencies === undefined) {
this.props.fetchingInputDeps(this.props.component);
}
}
componentDidUpdate(prevProps) {
if (this.props.component.name !== prevProps.component.name) {
this.props.fetchingInputDetails(this.props.component);
this.setState({ selectedBuildIndex: undefined }); // eslint-disable-line react/no-did-update-set-state
}
if (this.props.handleUpdateComponent) {
this.setBuildIndex();
} else if (this.props.dependencies === undefined) {
this.props.fetchingInputDeps(this.props.component);
}
}
handleVersionSelect(event) {
this.setState({ selectedBuildIndex: event.target.value });
this.handleSelectedBuildDeps(event.target.value);
}
handleSelectedBuildDeps(index) {
const { component } = this.props;
// update dependencies for selected component build
const selectedComponentBuild = {
...component,
release: component.builds[index].release,
version: component.builds[index].depsolveVersion,
};
this.props.fetchingInputDeps(selectedComponentBuild);
}
handleParentComponent(event, component, index) {
// user clicks a node in the breadcrumb
this.props.setSelectedInput(component);
const updatedParents = this.props.componentParent.slice(0, index);
this.props.setSelectedInputParent(updatedParents);
event.preventDefault();
event.stopPropagation();
}
handleChildComponent(event, component) {
// user clicks a list item in the dependencies tab
this.props.setSelectedInput(component);
const updatedParents = this.props.componentParent.concat(this.props.component);
this.props.setSelectedInputParent(updatedParents);
event.preventDefault();
event.stopPropagation();
}
handleCloseDetails(event) {
this.props.clearSelectedInput();
event.preventDefault();
event.stopPropagation();
}
setBuildIndex() {
// if component is not in the blueprint, then default to the first option ("*")
// if component is in the blueprint, then set selectedBuildIndex to the object with the current selected version
const { component, selectedComponents } = this.props;
let index = this.state.selectedBuildIndex;
if (component.builds !== undefined && index === undefined) {
if (component.userSelected === true) {
const selectedVersion = selectedComponents.find((selected) => selected.name === component.name).version;
const selectedBuild = component.builds.filter((obj) => obj.version === selectedVersion)[0];
index = component.builds.indexOf(selectedBuild);
if (index === -1) {
index = 0;
}
this.setState({ selectedBuildIndex: index });
this.setState({ savedVersion: selectedVersion });
this.handleSelectedBuildDeps(index);
} else {
this.setState({ selectedBuildIndex: 0 });
this.handleSelectedBuildDeps(0);
}
}
}
render() {
const {
component,
dependencies,
componentParent,
blueprint,
handleAddComponent,
handleUpdateComponent,
handleRemoveComponent,
handleDepListItem,
} = this.props;
const { selectedBuildIndex } = this.state;
const { formatMessage } = this.props.intl;
return (
<div className="cmpsr-panel__body cmpsr-panel__body--main">
<div className="cmpsr-header">
{(componentParent.length > 0 && (
<ol className="breadcrumb">
<li>
<a href="#" onClick={(e) => this.handleCloseDetails(e)}>
<FormattedMessage
defaultMessage="Back to {blueprint}"
values={{
blueprint,
}}
/>
</a>
</li>
{componentParent.map((parent, i) => (
<li key={parent.name}>
<a href="#" onClick={(e) => this.handleParentComponent(e, parent, i)}>
{parent.name}
</a>
</li>
))}
<li />
</ol>
)) || (
<ol className="breadcrumb">
<li>
<a href="#" onClick={(e) => this.handleCloseDetails(e)}>
<FormattedMessage
defaultMessage="Back to {blueprint}"
values={{
blueprint,
}}
/>
</a>
</li>
</ol>
)}
<div className="cmpsr-header__actions">
<ul className="list-inline">
{handleAddComponent !== undefined &&
((component.inBlueprint && !component.userSelected) || !component.inBlueprint) && (
<li>
<button
className="btn btn-primary add"
type="button"
onClick={(e) => handleAddComponent(e, component, component.builds[selectedBuildIndex].version)}
>
<FormattedMessage defaultMessage="Add" />
</button>
</li>
)}
{handleUpdateComponent !== undefined &&
component.inBlueprint &&
component.userSelected &&
component.builds !== undefined &&
selectedBuildIndex !== undefined &&
component.builds[selectedBuildIndex].version !== this.state.savedVersion && (
<li>
<button
className="btn btn-primary"
type="button"
onClick={(e) => handleUpdateComponent(e, component, component.builds[selectedBuildIndex].version)}
>
<FormattedMessage defaultMessage="Apply Change" />
</button>
</li>
)}
{handleRemoveComponent !== undefined && component.inBlueprint && component.userSelected && (
<li>
<Tooltip position={TooltipPosition.bottom} content={formatMessage(messages.removeFromBlueprint)}>
<button
className="btn btn-default"
type="button"
onClick={(e) => handleRemoveComponent(e, component.name)}
>
<FormattedMessage defaultMessage="Remove" />
</button>
</Tooltip>
</li>
)}
<li>
<Tooltip position={TooltipPosition.bottom} content={formatMessage(messages.hideDetails)}>
<button type="button" className="close" onClick={(e) => this.handleCloseDetails(e)}>
<span className="pficon pficon-close" />
</button>
</Tooltip>
</li>
</ul>
</div>
<h3 className="cmpsr-title">
<span>
<ComponentTypeIcons
componentType={component.ui_type}
compDetails
componentInBlueprint={component.inBlueprint}
isSelected={component.userSelected}
/>{" "}
{component.name}
</span>
</h3>
</div>
{handleUpdateComponent !== undefined && component.builds !== undefined && component.builds.length > 1 && (
<div className="cmpsr-component-details__form">
<h4>
<FormattedMessage defaultMessage="Component Options" />
</h4>
<form className="form-horizontal">
<div className="form-group">
<label className="col-sm-3 col-md-2 control-label" htmlFor="cmpsr-compon__version-select">
<FormattedMessage defaultMessage="Version" />
</label>
<div className="col-sm-8 col-md-9">
<select
id="cmpsr-compon__version-select"
className="form-control"
value={selectedBuildIndex}
onChange={this.handleVersionSelect}
>
{component.builds.map((build, i) => (
<option key={build.version} value={i}>
{build.version} {build.version === "*" && "(latest version)"}
</option>
))}
</select>
</div>
</div>
</form>
</div>
)}
<div>
<Tabs id="blueprint-tabs">
<Tab eventKey="details" title={formatMessage(messages.details)}>
<h4 className="cmpsr-title">{component.summary}</h4>
<p>{component.description}</p>
<dl className="dl-horizontal">
<dt>
<FormattedMessage defaultMessage="Type" />
</dt>
<dd>{component.ui_type}</dd>
<dt>
<FormattedMessage defaultMessage="Version" />
</dt>
{((component.builds === undefined || selectedBuildIndex === undefined) && (
<dd>{component.version}</dd>
)) || <dd>{component.builds[selectedBuildIndex].depsolveVersion}</dd>}
<dt>
<FormattedMessage defaultMessage="Release" />
</dt>
{((component.builds === undefined || selectedBuildIndex === undefined) && (
<dd>{component.release}</dd>
)) || <dd>{component.builds[selectedBuildIndex].release}</dd>}
<dt>
<FormattedMessage defaultMessage="URL" />
</dt>
{(component.homepage !== null && (
<dd>
<a target="_blank" rel="noopener noreferrer" href={component.homepage}>
{component.homepage}
</a>
</dd>
)) || <dd> </dd>}
</dl>
</Tab>
{(dependencies === undefined && (
<Tab eventKey="dependencies" title={formatMessage(messages.dependencies)}>
<Loading />
</Tab>
)) || (
<Tab
eventKey="dependencies"
title={<LabelWithBadge title={formatMessage(messages.dependencies)} badge={dependencies.length} />}
>
{(dependencies.length === 0 && (
<EmptyState
title={formatMessage(messages.noDepsTitle)}
message={formatMessage(messages.noDepsMessage)}
/>
)) || (
<DependencyListView
id="cmpsr-component-dependencies"
listItems={dependencies}
noEditComponent
handleComponentDetails={this.handleChildComponent}
componentDetailsParent={component}
fetchDetails={handleDepListItem}
/>
)}
</Tab>
)}
</Tabs>
</div>
</div>
);
}
}
ComponentDetailsView.propTypes = {
component: PropTypes.shape({
arch: PropTypes.string,
epoch: PropTypes.number,
homepage: PropTypes.string,
inBlueprint: PropTypes.bool,
name: PropTypes.string,
release: PropTypes.string,
summary: PropTypes.string,
description: PropTypes.string,
ui_type: PropTypes.string,
userSelected: PropTypes.bool,
version: PropTypes.string,
builds: PropTypes.arrayOf(PropTypes.object),
}),
selectedComponents: PropTypes.arrayOf(PropTypes.object),
blueprint: PropTypes.string,
componentParent: PropTypes.arrayOf(PropTypes.object),
handleRemoveComponent: PropTypes.func,
handleAddComponent: PropTypes.func,
handleUpdateComponent: PropTypes.func,
handleDepListItem: PropTypes.func,
fetchingInputDeps: PropTypes.func,
fetchingInputDetails: PropTypes.func,
dependencies: PropTypes.arrayOf(PropTypes.object),
setSelectedInput: PropTypes.func,
setSelectedInputParent: PropTypes.func,
clearSelectedInput: PropTypes.func,
intl: intlShape.isRequired,
};
ComponentDetailsView.defaultProps = {
component: {},
selectedComponents: [],
blueprint: "",
componentParent: [],
handleRemoveComponent: undefined,
handleAddComponent: undefined,
handleUpdateComponent: undefined,
handleDepListItem() {},
fetchingInputDeps() {},
fetchingInputDetails() {},
dependencies: undefined,
setSelectedInput() {},
setSelectedInputParent() {},
clearSelectedInput() {},
};
const mapDispatchToProps = (dispatch) => ({
fetchingInputDetails: (component) => {
dispatch(fetchingInputDetails(component));
},
fetchingInputDeps: (component) => {
dispatch(fetchingInputDeps(component));
},
});
export default connect(null, mapDispatchToProps)(injectIntl(ComponentDetailsView));