cspace-ui
Version:
CollectionSpace user interface for browsers
375 lines (311 loc) • 8.74 kB
JSX
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import Immutable from 'immutable';
import get from 'lodash/get';
import { getDisplayName } from 'cspace-refname';
import { helpers as inputHelpers } from 'cspace-input';
import { Row } from 'cspace-layout';
import HierarchySiblingListContainer from '../../containers/record/HierarchySiblingListContainer';
import HierarchyReparentNotifierContainer from '../../containers/record/HierarchyReparentNotifierContainer';
import UntypedHierarchyEditor from './UntypedHierarchyEditor';
import TypedHierarchyEditor from './TypedHierarchyEditor';
import {
placeholderCsid,
normalizeRelationList,
findBroaderRelation,
findNarrowerRelations,
} from '../../helpers/relationListHelpers';
const {
getPath,
pathPropType,
} = inputHelpers.pathHelpers;
const findParent = (csid, relations) => {
const broaderRelation = findBroaderRelation(csid, relations);
if (broaderRelation) {
return Immutable.Map({
relCsid: broaderRelation.get('csid'),
csid: broaderRelation.getIn(['object', 'csid']),
refName: broaderRelation.getIn(['object', 'refName']),
type: broaderRelation.get('relationshipMetaType'),
});
}
return null;
};
const findChildren = (csid, relations) => findNarrowerRelations(csid, relations)
.sort((relationA, relationB) => {
const displayNameA = getDisplayName(relationA.getIn(['subject', 'refName'])) || '';
const displayNameB = getDisplayName(relationB.getIn(['subject', 'refName'])) || '';
if (displayNameA && displayNameB) {
return displayNameA.localeCompare(displayNameB);
}
if (!displayNameA && !displayNameB) {
return 0;
}
if (displayNameA) {
return -1;
}
return 1;
})
.map((relation) => Immutable.Map({
relCsid: relation.get('csid'),
refName: relation.getIn(['subject', 'refName']),
type: relation.get('relationshipMetaType'),
}));
function createHierarchy(csid, value) {
const relations = normalizeRelationList(value);
return Immutable.fromJS({
parent: findParent(csid, relations) || Immutable.Map(),
children: findChildren(csid, relations),
});
}
const propTypes = {
csid: PropTypes.string,
messages: PropTypes.shape({
siblings: PropTypes.object,
}),
/* eslint-disable react/no-unused-prop-types */
name: PropTypes.string,
parentPath: pathPropType,
subpath: pathPropType,
readOnly: PropTypes.bool,
/* eslint-enable react/no-unused-prop-types */
isRecordModified: PropTypes.bool,
showParent: PropTypes.bool,
showChildren: PropTypes.bool,
showSiblings: PropTypes.bool,
value: PropTypes.oneOfType([
PropTypes.instanceOf(Immutable.List),
PropTypes.instanceOf(Immutable.Map),
PropTypes.array,
PropTypes.object,
]),
parentTypeOptionListName: PropTypes.string,
childTypeOptionListName: PropTypes.string,
onCommit: PropTypes.func,
};
const defaultProps = {
showParent: true,
showChildren: true,
showSiblings: true,
};
const contextTypes = {
config: PropTypes.shape({
recordTypes: PropTypes.object,
}),
recordType: PropTypes.string,
vocabulary: PropTypes.string,
};
export default class HierarchyInput extends Component {
constructor(props) {
super(props);
const {
csid,
value,
} = props;
this.state = {
csid,
value,
hierarchy: createHierarchy(csid, value),
};
this.handleAddChild = this.handleAddChild.bind(this);
this.handleRemoveChild = this.handleRemoveChild.bind(this);
this.handleCommit = this.handleCommit.bind(this);
}
static getDerivedStateFromProps(props, state) {
const {
csid,
value,
} = props;
const {
csid: oldCsid,
value: oldValue,
} = state;
if (oldCsid !== csid || oldValue !== value) {
return {
csid,
value,
hierarchy: createHierarchy(csid, value),
};
}
return null;
}
handleAddChild() {
const {
onCommit,
} = this.props;
if (onCommit) {
const {
hierarchy,
} = this.state;
let children = hierarchy.get('children');
if (children.size === 0) {
// The UI renders a single blank input event if there are no children, so adding a child
// should result in two children, not one.
children = children.push(Immutable.Map());
}
const updatedHierarchy = hierarchy.set('children', children.push(Immutable.Map()));
onCommit(getPath(this.props), this.getRelationItems(updatedHierarchy));
}
}
handleRemoveChild(position) {
const {
onCommit,
} = this.props;
if (onCommit) {
const {
hierarchy,
} = this.state;
const children = hierarchy.get('children');
const updatedHierarchy = hierarchy.set('children', children.delete(position));
onCommit(getPath(this.props), this.getRelationItems(updatedHierarchy));
}
}
handleCommit(path, value) {
const {
onCommit,
} = this.props;
if (onCommit) {
const {
hierarchy,
} = this.state;
const updatedHierarchy = hierarchy.setIn(path, value);
onCommit(getPath(this.props), this.getRelationItems(updatedHierarchy));
}
}
getRelationItems(hierarchy) {
const {
csid,
} = this.props;
const children = hierarchy.get('children');
const childRelationItems = children.map((child) => Immutable.fromJS({
csid: child.get('relCsid'),
predicate: 'hasBroader',
relationshipMetaType: child.get('type'),
subject: {
refName: child.get('refName'),
},
object: {
csid: csid || placeholderCsid,
},
}));
const parent = hierarchy.get('parent');
const parentRelationItem = Immutable.fromJS({
csid: parent.get('relCsid'),
predicate: 'hasBroader',
relationshipMetaType: parent.get('type'),
subject: {
csid: csid || placeholderCsid,
},
object: {
csid: parent.get('csid'),
refName: parent.get('refName'),
},
});
return childRelationItems.push(parentRelationItem);
}
renderHierarchy() {
const {
csid,
messages,
parentTypeOptionListName,
childTypeOptionListName,
isRecordModified,
readOnly,
showParent,
showChildren,
} = this.props;
if (!showParent && !showChildren) {
return undefined;
}
const {
config,
recordType,
vocabulary,
} = this.context;
const {
hierarchy,
} = this.state;
const serviceType = get(config, ['recordTypes', recordType, 'serviceConfig', 'serviceType']);
const HierarchyEditor = (serviceType === 'object')
? TypedHierarchyEditor
: UntypedHierarchyEditor;
return (
<HierarchyEditor
csid={csid}
messages={messages}
parentTypeOptionListName={parentTypeOptionListName}
childTypeOptionListName={childTypeOptionListName}
recordType={recordType}
vocabulary={vocabulary}
value={hierarchy}
readOnly={readOnly}
isRecordModified={isRecordModified}
showParent={showParent}
showChildren={showChildren}
onCommit={this.handleCommit}
onAddChild={this.handleAddChild}
onRemoveChild={this.handleRemoveChild}
/>
);
}
renderReparentNotifier() {
const {
csid,
} = this.props;
const {
hierarchy,
} = this.state;
const {
config,
} = this.context;
const newChildRefNames = hierarchy.get('children')
.filter((child) => !child.get('relCsid'))
.map((child) => child.get('refName'));
return (
<HierarchyReparentNotifierContainer
config={config}
csid={csid}
childRefNames={newChildRefNames}
/>
);
}
renderSiblings() {
const {
csid,
messages,
showSiblings,
} = this.props;
if (!showSiblings) {
return undefined;
}
const {
config,
recordType,
} = this.context;
const {
hierarchy,
} = this.state;
return (
<HierarchySiblingListContainer
config={config}
csid={csid}
parentCsid={hierarchy.getIn(['parent', 'csid'])}
recordType={recordType}
title={<FormattedMessage {...messages.siblings} />}
/>
);
}
render() {
return (
<Row>
{this.renderHierarchy()}
{this.renderSiblings()}
{this.renderReparentNotifier()}
</Row>
);
}
}
HierarchyInput.propTypes = propTypes;
HierarchyInput.defaultProps = defaultProps;
HierarchyInput.contextTypes = contextTypes;