cspace-ui
Version:
CollectionSpace user interface for browsers
426 lines (362 loc) • 10.8 kB
JSX
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { intlShape } from 'react-intl';
import Immutable from 'immutable';
import get from 'lodash/get';
import qs from 'qs';
import RelatedRecordButtonBar from './RelatedRecordButtonBar';
import RelatedRecordPanelContainer from '../../containers/record/RelatedRecordPanelContainer';
import RelationEditorContainer from '../../containers/record/RelationEditorContainer';
import SearchToRelateModalContainer from '../../containers/search/SearchToRelateModalContainer';
import { canCreate, canRelate } from '../../helpers/permissionHelpers';
import styles from '../../../styles/cspace-ui/RelatedRecordBrowser.css';
const propTypes = {
cloneCsid: PropTypes.string,
config: PropTypes.shape({
recordTypes: PropTypes.object,
}),
history: PropTypes.shape({
replace: PropTypes.func,
}),
location: PropTypes.shape({
state: PropTypes.object,
}),
isSidebarOpen: PropTypes.bool,
perms: PropTypes.instanceOf(Immutable.Map),
recordType: PropTypes.string,
vocabulary: PropTypes.string,
csid: PropTypes.string,
primaryRecordData: PropTypes.instanceOf(Immutable.Map),
preferredRelatedCsid: PropTypes.string,
relatedCsid: PropTypes.string,
relatedRecordType: PropTypes.string,
deselectItem: PropTypes.func,
workflowState: PropTypes.string,
setPreferredRelatedCsid: PropTypes.func,
onShowRelated: PropTypes.func,
};
const defaultProps = {
isSidebarOpen: true,
};
const contextTypes = {
intl: intlShape,
};
export default class RelatedRecordBrowser extends Component {
constructor() {
super();
this.cloneRelatedRecord = this.cloneRelatedRecord.bind(this);
this.handleCreateButtonClick = this.handleCreateButtonClick.bind(this);
this.handleRelateButtonClick = this.handleRelateButtonClick.bind(this);
this.handleRelatedRecordCreated = this.handleRelatedRecordCreated.bind(this);
this.handleRelatedRecordClick = this.handleRelatedRecordClick.bind(this);
this.handleRelatedRecordPanelUnrelated = this.handleRelatedRecordPanelUnrelated.bind(this);
this.handleModalCancelButtonClick = this.handleModalCancelButtonClick.bind(this);
this.handleModalCloseButtonClick = this.handleModalCloseButtonClick.bind(this);
this.handleRelationEditorClose = this.handleRelationEditorClose.bind(this);
this.handleRelationEditorUnrelated = this.handleRelationEditorUnrelated.bind(this);
this.handleRelationsCreated = this.handleRelationsCreated.bind(this);
this.state = {
isSearchToRelateModalOpen: false,
};
}
componentDidMount() {
this.normalizeRelatedCsid();
}
componentDidUpdate(prevProps) {
const {
relatedCsid,
relatedRecordType,
} = this.props;
const {
relatedRecordType: prevRelatedRecordType,
relatedCsid: prevRelatedCsid,
} = prevProps;
if (relatedCsid !== prevRelatedCsid || relatedRecordType !== prevRelatedRecordType) {
this.normalizeRelatedCsid();
}
}
handleCreateButtonClick() {
const {
history,
location,
recordType,
vocabulary,
csid,
relatedRecordType,
} = this.props;
const path = [recordType, vocabulary, csid, relatedRecordType, 'new']
.filter((part) => !!part)
.join('/');
history.replace({
pathname: `/record/${path}`,
state: location.state,
});
}
handleRelateButtonClick() {
this.setState({
isSearchToRelateModalOpen: true,
});
}
handleModalCancelButtonClick() {
this.closeModal();
}
handleModalCloseButtonClick() {
this.closeModal();
}
handleRelationEditorClose() {
const {
history,
location,
recordType,
vocabulary,
csid,
relatedRecordType,
setPreferredRelatedCsid,
} = this.props;
if (setPreferredRelatedCsid) {
setPreferredRelatedCsid(relatedRecordType, undefined);
}
const path = [recordType, vocabulary, csid, relatedRecordType]
.filter((part) => !!part)
.join('/');
history.replace({
pathname: `/record/${path}`,
state: location.state,
});
}
handleRelationEditorUnrelated(subject, object) {
const {
deselectItem,
} = this.props;
// If a record is unrelated in the editor, deselect it in the store, since it will no longer
// appear in the related records list.
if (deselectItem) {
deselectItem(this.getRelatedRecordPanelName(), object.csid);
}
}
handleRelationsCreated() {
this.closeModal();
}
handleRelatedRecordClick(item) {
const {
relatedRecordType,
onShowRelated,
} = this.props;
if (onShowRelated) {
const relatedCsid = item.get('csid');
onShowRelated(relatedRecordType, relatedCsid);
}
// Prevent the default action.
return false;
}
handleRelatedRecordCreated(newRecordCsid, isNavigating) {
if (!isNavigating) {
const {
history,
location,
recordType,
vocabulary,
csid,
relatedRecordType,
} = this.props;
const path = [recordType, vocabulary, csid, relatedRecordType, newRecordCsid]
.filter((part) => !!part)
.join('/');
history.replace({
pathname: `/record/${path}`,
state: location.state,
});
}
}
handleRelatedRecordPanelUnrelated(objects) {
const {
relatedCsid,
} = this.props;
let isRelatedCsidUnrelated = false;
for (let i = 0; i < objects.length; i += 1) {
if (objects[i].csid === relatedCsid) {
isRelatedCsidUnrelated = true;
break;
}
}
if (isRelatedCsidUnrelated) {
this.handleRelationEditorClose();
}
}
getRelatedRecordPanelName() {
const {
relatedRecordType,
} = this.props;
return `relatedRecordBrowser-${relatedRecordType}`;
}
normalizeRelatedCsid() {
const {
preferredRelatedCsid,
relatedCsid,
relatedRecordType,
setPreferredRelatedCsid,
} = this.props;
if (typeof relatedCsid !== 'undefined') {
if (setPreferredRelatedCsid && (preferredRelatedCsid !== relatedCsid)) {
setPreferredRelatedCsid(relatedRecordType, relatedCsid);
}
} else if (preferredRelatedCsid) {
const {
recordType,
vocabulary,
csid,
history,
location,
} = this.props;
const path = [recordType, vocabulary, csid, relatedRecordType, preferredRelatedCsid]
.filter((part) => !!part)
.join('/');
history.replace({
pathname: `/record/${path}`,
state: location.state,
});
}
}
cloneRelatedRecord(relatedRecordCsid) {
const {
recordType,
vocabulary,
csid,
relatedRecordType,
history,
location,
} = this.props;
const path = [recordType, vocabulary, csid, relatedRecordType]
.filter((part) => !!part)
.join('/');
const query = {
clone: relatedRecordCsid,
};
const queryString = qs.stringify(query);
history.replace({
pathname: `/record/${path}/new`,
search: `?${queryString}`,
state: location.state,
});
}
closeModal() {
this.setState({
isSearchToRelateModalOpen: false,
});
}
renderRelationEditor() {
const {
cloneCsid,
config,
recordType,
csid,
perms,
relatedCsid,
relatedRecordType,
workflowState,
} = this.props;
if (typeof relatedCsid === 'undefined' || relatedCsid === null) {
return null;
}
return (
<RelationEditorContainer
cloneCsid={cloneCsid}
config={config}
perms={perms}
subject={{
csid,
recordType,
}}
subjectWorkflowState={workflowState}
object={{
csid: relatedCsid,
recordType: relatedRecordType,
}}
predicate="affects"
cloneRecord={this.cloneRelatedRecord}
onClose={this.handleRelationEditorClose}
onRecordCreated={this.handleRelatedRecordCreated}
onUnrelated={this.handleRelationEditorUnrelated}
/>
);
}
renderSearchToRelateModal() {
const {
config,
recordType,
csid,
relatedRecordType,
primaryRecordData,
} = this.props;
const {
intl,
} = this.context;
const {
isSearchToRelateModalOpen,
} = this.state;
const titleGetter = get(config, ['recordTypes', recordType, 'title']);
const title = titleGetter && titleGetter(primaryRecordData, { config, intl });
return (
<SearchToRelateModalContainer
subjects={[{ csid, recordType, title }]}
config={config}
isOpen={isSearchToRelateModalOpen}
defaultRecordTypeValue={relatedRecordType}
onCancelButtonClick={this.handleModalCancelButtonClick}
onCloseButtonClick={this.handleModalCloseButtonClick}
onRelationsCreated={this.handleRelationsCreated}
/>
);
}
render() {
const {
config,
recordType,
csid,
isSidebarOpen,
perms,
relatedRecordType,
workflowState,
} = this.props;
const isCreatable = canCreate(relatedRecordType, perms);
const isRelatable = (
workflowState !== 'locked'
&& canRelate(recordType, perms, config)
&& canRelate(relatedRecordType, perms, config)
);
const className = isSidebarOpen ? styles.normal : styles.full;
// TODO: Vary the name of the RelatedRecordPanelContainer depending on the object record type?
// This would allow selected items to be remembered when switching back and forth between
// secondary tabs, instead of being cleared.
return (
<div className={className}>
<header>
<RelatedRecordButtonBar
isCreatable={isCreatable}
isRelatable={isRelatable}
onCreateButtonClick={this.handleCreateButtonClick}
onRelateButtonClick={this.handleRelateButtonClick}
/>
</header>
<RelatedRecordPanelContainer
collapsed={false}
csid={csid}
config={config}
linkItems={false}
name={this.getRelatedRecordPanelName()}
perms={perms}
recordType={recordType}
relatedRecordType={relatedRecordType}
showCheckboxColumn={isRelatable}
onItemClick={this.handleRelatedRecordClick}
onUnrelated={this.handleRelatedRecordPanelUnrelated}
/>
{this.renderRelationEditor()}
{this.renderSearchToRelateModal()}
</div>
);
}
}
RelatedRecordBrowser.propTypes = propTypes;
RelatedRecordBrowser.defaultProps = defaultProps;
RelatedRecordBrowser.contextTypes = contextTypes;