cspace-ui
Version:
CollectionSpace user interface for browsers
368 lines (303 loc) • 10.5 kB
JSX
import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, intlShape, FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import Immutable from 'immutable';
import TitleBar from '../sections/TitleBar';
import { canCreate } from '../../helpers/permissionHelpers';
import { isLocked } from '../../helpers/workflowStateHelpers';
import styles from '../../../styles/cspace-ui/CreatePage.css';
import panelStyles from '../../../styles/cspace-ui/CreatePagePanel.css';
const serviceTypes = ['object', 'procedure', 'authority'];
const messages = defineMessages({
title: {
id: 'createPage.title',
defaultMessage: 'Create New',
},
object: {
id: 'createPage.object',
defaultMessage: 'Objects',
},
procedure: {
id: 'createPage.procedure',
defaultMessage: 'Procedures',
},
authority: {
id: 'createPage.authority',
defaultMessage: 'Authorities',
},
});
const tagMessages = defineMessages({
nagpra: {
id: 'createPage.tag.nagpra',
defaultMessage: 'NAGPRA Procedures',
},
legacy: {
id: 'createPage.tag.legacy',
defaultMessage: 'Legacy Procedures',
},
});
const getRecordTypesByServiceType = (recordTypes, perms, intl) => {
const recordTypesByServiceType = {};
serviceTypes.forEach((serviceType) => {
const recordTypeNames = Object.keys(recordTypes)
.filter((recordTypeName) => {
const recordTypeConfig = recordTypes[recordTypeName];
return (
recordTypeConfig.serviceConfig.serviceType === serviceType
&& !recordTypeConfig.disabled
&& canCreate(recordTypeName, perms)
);
})
.sort((nameA, nameB) => {
const configA = recordTypes[nameA];
const configB = recordTypes[nameB];
// Primary sort by sortOrder
let sortOrderA = configA.sortOrder;
let sortOrderB = configB.sortOrder;
if (typeof sortOrderA !== 'number') {
sortOrderA = Number.MAX_VALUE;
}
if (typeof sortOrderB !== 'number') {
sortOrderB = Number.MAX_VALUE;
}
if (sortOrderA !== sortOrderB) {
return (sortOrderA > sortOrderB ? 1 : -1);
}
// Secondary sort by label
const labelA = intl.formatMessage(configA.messages.record.name);
const labelB = intl.formatMessage(configB.messages.record.name);
// FIXME: This should be locale aware
return labelA.localeCompare(labelB);
});
recordTypesByServiceType[serviceType] = recordTypeNames;
});
return recordTypesByServiceType;
};
const getVocabularies = (recordTypeConfig, intl, getAuthorityVocabWorkflowState) => {
const recordTypeName = recordTypeConfig.name;
const { vocabularies } = recordTypeConfig;
let vocabularyNames;
if (vocabularies) {
vocabularyNames = Object.keys(vocabularies)
.filter((vocabularyName) => {
// Filter out vocabularies that don't exist in the services layer, vocabularies that are
// locked, and vocabularies that are disabled. Always include the 'all' vocabulary.
const workflowState = getAuthorityVocabWorkflowState(recordTypeName, vocabularyName);
return (
vocabularyName !== 'all'
&& workflowState // Empty workflow state means vocab doesn't exist.
&& !isLocked(workflowState)
&& !vocabularies[vocabularyName].disabled
);
})
.sort((nameA, nameB) => {
const configA = vocabularies[nameA];
const configB = vocabularies[nameB];
// Primary sort by sortOrder
let sortOrderA = configA.sortOrder;
let sortOrderB = configB.sortOrder;
if (typeof sortOrderA !== 'number') {
sortOrderA = Number.MAX_VALUE;
}
if (typeof sortOrderB !== 'number') {
sortOrderB = Number.MAX_VALUE;
}
if (sortOrderA !== sortOrderB) {
return (sortOrderA > sortOrderB ? 1 : -1);
}
// Secondary sort by label
const labelA = intl.formatMessage(configA.messages.name);
const labelB = intl.formatMessage(configB.messages.name);
// FIXME: This should be locale aware
return labelA.localeCompare(labelB);
});
}
return vocabularyNames;
};
const renderListItem = (recordType, config) => {
const recordConfig = config[recordType];
const recordDisplayName = <FormattedMessage {...recordConfig.messages.record.name} />;
const recordLink = <Link id={recordType} to={`/record/${recordType}`}>{recordDisplayName}</Link>;
return (
<li key={recordType}>
{recordLink}
</li>
);
};
/**
* Render the panel for a group of Procedures
*
* @param {String} serviceType the name of the service type (object, procedure, authority)
* @param {Array} items the array of list items to display for the service
* @returns
*/
const renderPanel = (serviceType, items) => (
items && items.length > 0 ? (
<div className={panelStyles[serviceType]} key={serviceType}>
<h2><FormattedMessage {...messages[serviceType]} /></h2>
<ul>
{items}
</ul>
</div>
) : null
);
/**
* Render the div for Object records
*
* @param {Array} recordTypes the object records
* @param {Object} config the cspace config
* @returns the div
*/
const renderObjects = (recordTypes = [], config) => {
const serviceType = 'object';
const items = recordTypes.map((recordType) => renderListItem(recordType, config));
return renderPanel(serviceType, items);
};
/**
* Render the div for procedure records. The procedures are grouped together by their service tags
* in order to display procedures in a workflow together. Each tag has its own header in order to
* act as a delimiter within the div. Procedures without a tag do not have a header and are part
* of a default group.
*
* @param {Array} recordTypes the procedure record types
* @param {Object} config the cspace config
* @param {Function} getTagsForRecord function to query the service tag of a record
* @param {Object} tagConfig the configuration for the service tags containing their sortOrder
* @returns
*/
const renderProcedures = (recordTypes = [], config, getTagsForRecord, tagConfig) => {
const serviceType = 'procedure';
const grouped = {};
recordTypes.forEach((recordType) => {
const tag = getTagsForRecord(recordType) || 'defaultGroup';
if (grouped[tag] === undefined) {
grouped[tag] = [recordType];
} else {
grouped[tag].push(recordType);
}
});
const {
defaultGroup: defaultRecordTypes = [],
...taggedRecordTypes
} = grouped;
const defaultItems = defaultRecordTypes.map((recordType) => renderListItem(recordType, config));
const taggedItems = Object.keys(taggedRecordTypes).sort((lhs, rhs) => {
const lhsConfig = tagConfig[lhs] || {};
const rhsConfig = tagConfig[rhs] || {};
const {
sortOrder: lhsOrder = Number.MAX_SAFE_INTEGER,
} = lhsConfig;
const {
sortOrder: rhsOrder = Number.MAX_SAFE_INTEGER,
} = rhsConfig;
return lhsOrder > rhsOrder ? 1 : -1;
}).map((tag) => {
const tagRecordTypes = taggedRecordTypes[tag];
const items = tagRecordTypes.map((recordType) => renderListItem(recordType, config));
return (
<li className={panelStyles.tag}>
<h3 id={tag}><FormattedMessage {...tagMessages[tag]} /></h3>
<ul>
{items}
</ul>
</li>
);
});
return renderPanel(serviceType, defaultItems.concat(taggedItems));
};
/**
* Render the div for creating authority items. Each authority is a header and its vocabulary items
* are represented as a sub-list.
*
* @param {Array} recordTypes the authority records
* @param {Object} config the cspace config
* @param {intlShape} intl the intl object
* @param {Function} getAuthorityVocabWorkflowState function to get workflow states
*/
const renderAuthorities = (recordTypes = [], config, intl, getAuthorityVocabWorkflowState) => {
const authorityItems = recordTypes.map((recordType) => {
const recordConfig = config[recordType];
const vocabularies = getVocabularies(
recordConfig, intl, getAuthorityVocabWorkflowState,
);
if (!vocabularies || vocabularies.length === 0) {
return null;
}
const vocabularyItems = vocabularies.map((vocabulary) => (
<li key={vocabulary}>
<Link id={`${recordType}/${vocabulary}`} to={`/record/${recordType}/${vocabulary}`}>
<FormattedMessage {...recordConfig.vocabularies[vocabulary].messages.name} />
</Link>
</li>
));
const vocabularyList = <ul>{vocabularyItems}</ul>;
const recordDisplayName = <FormattedMessage {...recordConfig.messages.record.name} />;
const recordLink = <h3 id={recordType}>{recordDisplayName}</h3>;
return (
<li key={recordType}>
{recordLink}
{vocabularyList}
</li>
);
});
return renderPanel('authority', authorityItems);
};
const contextTypes = {
config: PropTypes.shape({
recordTypes: PropTypes.object,
}),
};
const propTypes = {
intl: intlShape,
perms: PropTypes.instanceOf(Immutable.Map),
getAuthorityVocabWorkflowState: PropTypes.func,
getTagsForRecord: PropTypes.func,
};
const defaultProps = {
getAuthorityVocabWorkflowState: () => null,
};
export default function CreatePage(props, context) {
const {
intl,
perms,
getAuthorityVocabWorkflowState,
getTagsForRecord,
} = props;
const {
config,
} = context;
const {
tags: tagConfig,
recordTypes,
} = config;
let objectPanel;
let procedurePanel;
let authorityPanel;
if (recordTypes) {
const recordTypesByServiceType = getRecordTypesByServiceType(recordTypes, perms, intl);
objectPanel = renderObjects(recordTypesByServiceType.object, recordTypes);
procedurePanel = renderProcedures(recordTypesByServiceType.procedure,
recordTypes,
getTagsForRecord,
tagConfig);
authorityPanel = renderAuthorities(recordTypesByServiceType.authority,
recordTypes,
intl,
getAuthorityVocabWorkflowState);
}
const title = <FormattedMessage {...messages.title} />;
return (
<div className={styles.common}>
<TitleBar title={title} updateDocumentTitle />
<div>
{objectPanel}
{procedurePanel}
{authorityPanel}
</div>
</div>
);
}
CreatePage.propTypes = propTypes;
CreatePage.defaultProps = defaultProps;
CreatePage.contextTypes = contextTypes;