cspace-ui
Version:
CollectionSpace user interface for browsers
693 lines (685 loc) • 25.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = _interopRequireWildcard(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _reactIntl = require("react-intl");
var _get = _interopRequireDefault(require("lodash/get"));
var _immutable = _interopRequireDefault(require("immutable"));
var _qs = _interopRequireDefault(require("qs"));
var _CheckboxInput = _interopRequireDefault(require("cspace-input/lib/components/CheckboxInput"));
var _ErrorPage = _interopRequireDefault(require("./ErrorPage"));
var _ExportButton = _interopRequireDefault(require("../search/ExportButton"));
var _RelateButton = _interopRequireDefault(require("../record/RelateButton"));
var _Pager = _interopRequireDefault(require("../search/Pager"));
var _SearchResultSidebar = _interopRequireDefault(require("../search/SearchResultSidebar"));
var _SearchResultSummary = _interopRequireDefault(require("../search/SearchResultSummary"));
var _SearchResultTitleBar = _interopRequireDefault(require("../search/SearchResultTitleBar"));
var _SelectBar = _interopRequireDefault(require("../search/SelectBar"));
var _ExportModalContainer = _interopRequireDefault(require("../../containers/search/ExportModalContainer"));
var _WatchedSearchResultTableContainer = _interopRequireDefault(require("../../containers/search/WatchedSearchResultTableContainer"));
var _SearchToRelateModalContainer = _interopRequireDefault(require("../../containers/search/SearchToRelateModalContainer"));
var _permissionHelpers = require("../../helpers/permissionHelpers");
var _searchHelpers = require("../../helpers/searchHelpers");
var _searchNames = require("../../constants/searchNames");
var _configHelpers = require("../../helpers/configHelpers");
var _SearchResultPage = _interopRequireDefault(require("../../../styles/cspace-ui/SearchResultPage.css"));
var _PageBody = _interopRequireDefault(require("../../../styles/cspace-ui/PageBody.css"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/* global window */
// const stopPropagation = (event) => {
// event.stopPropagation();
// };
const messages = (0, _reactIntl.defineMessages)({
relate: {
"id": "searchResultPage.relate",
"defaultMessage": "Relate\u2026"
}
});
const propTypes = {
isSidebarOpen: _propTypes.default.bool,
history: _propTypes.default.shape({
push: _propTypes.default.func,
replace: _propTypes.default.func
}),
location: _propTypes.default.shape({
pathname: _propTypes.default.string,
search: _propTypes.default.string,
state: _propTypes.default.object
}),
match: _propTypes.default.shape({
params: _propTypes.default.object
}),
openExport: _propTypes.default.func,
perms: _propTypes.default.instanceOf(_immutable.default.Map),
preferredPageSize: _propTypes.default.number,
search: _propTypes.default.func,
selectedItems: _propTypes.default.instanceOf(_immutable.default.Map),
setPreferredPageSize: _propTypes.default.func,
setSearchPageAdvanced: _propTypes.default.func,
setSearchPageKeyword: _propTypes.default.func,
setSearchPageRecordType: _propTypes.default.func,
setSearchPageVocabulary: _propTypes.default.func,
setAllItemsSelected: _propTypes.default.func,
onItemSelectChange: _propTypes.default.func
};
const defaultProps = {
isSidebarOpen: true
};
const contextTypes = {
config: _propTypes.default.shape({
recordTypes: _propTypes.default.object
}).isRequired
};
class SearchResultPage extends _react.Component {
constructor() {
super();
this.getSearchToRelateSubjects = this.getSearchToRelateSubjects.bind(this);
this.handleCheckboxClick = this.handleCheckboxClick.bind(this);
this.handleCheckboxCommit = this.handleCheckboxCommit.bind(this);
this.handleEditSearchLinkClick = this.handleEditSearchLinkClick.bind(this);
this.handleExportButtonClick = this.handleExportButtonClick.bind(this);
this.handleExportOpened = this.handleExportOpened.bind(this);
this.handleModalCancelButtonClick = this.handleModalCancelButtonClick.bind(this);
this.handleModalCloseButtonClick = this.handleModalCloseButtonClick.bind(this);
this.handlePageChange = this.handlePageChange.bind(this);
this.handlePageSizeChange = this.handlePageSizeChange.bind(this);
this.handleRelateButtonClick = this.handleRelateButtonClick.bind(this);
this.handleRelationsCreated = this.handleRelationsCreated.bind(this);
this.handleSortChange = this.handleSortChange.bind(this);
this.renderCheckbox = this.renderCheckbox.bind(this);
this.renderFooter = this.renderFooter.bind(this);
this.renderHeader = this.renderHeader.bind(this);
this.search = this.search.bind(this);
this.state = {
isExportModalOpen: false,
isSearchToRelateModalOpen: false
};
}
componentDidMount() {
if (!this.normalizeQuery()) {
const {
location,
setPreferredPageSize
} = this.props;
if (setPreferredPageSize) {
const {
search
} = location;
const query = _qs.default.parse(search.substring(1));
setPreferredPageSize(parseInt(query.size, 10));
}
this.search();
}
}
componentDidUpdate(prevProps) {
const {
location,
match,
perms
} = this.props;
const {
location: prevLocation,
match: prevMatch,
perms: prevPerms
} = prevProps;
const {
params
} = match;
const {
params: prevParams
} = prevMatch;
if (perms !== prevPerms || params.recordType !== prevParams.recordType || params.vocabulary !== prevParams.vocabulary || params.csid !== prevParams.csid || params.subresource !== prevParams.subresource || location.search !== prevLocation.search) {
if (!this.normalizeQuery()) {
const {
setPreferredPageSize
} = this.props;
if (setPreferredPageSize) {
const {
search
} = location;
const query = _qs.default.parse(search.substring(1));
setPreferredPageSize(parseInt(query.size, 10));
}
this.search();
}
}
}
handleCheckboxClick(event) {
// DRYD-252: Elaborate workaround for Firefox. When a checkbox is a child of an a, clicking on
// the checkbox navigates to the link. So we have to handle the checkbox click, and prevent the
// default. This prevents the navigation, but also prevents the checkbox state from changing.
// So we also have to manually commit the change. The Firefox bug has been open for 17 years
// now. https://bugzilla.mozilla.org/show_bug.cgi?id=62151
event.preventDefault();
event.stopPropagation();
const checkbox = event.currentTarget.querySelector('input[type="checkbox"]');
const index = checkbox.dataset.name;
window.setTimeout(() => {
this.handleCheckboxCommit([index], !checkbox.checked);
}, 0);
}
handleCheckboxCommit(path, value) {
const index = parseInt(path[0], 10);
const selected = value;
const {
onItemSelectChange
} = this.props;
const {
config
} = this.context;
if (onItemSelectChange) {
const searchDescriptor = this.getSearchDescriptor();
const listType = this.getListType(searchDescriptor);
onItemSelectChange(config, _searchNames.SEARCH_RESULT_PAGE_SEARCH_NAME, searchDescriptor, listType, index, selected);
}
}
handleEditSearchLinkClick() {
// Transfer the search descriptor from this search to the search page. If this search
// originated from the search page, the original descriptor will be in the location state.
// Otherwise, build it from the URL params. If present, the search descriptor from the
// originating search page will be more complete than one constructed from the URL; for
// example, it will contain fields that are blank, which will have been removed from the
// URL, to reduce the size.
const {
location,
setSearchPageAdvanced,
setSearchPageKeyword,
setSearchPageRecordType,
setSearchPageVocabulary
} = this.props;
const origin = (0, _get.default)(location.state, 'originSearchPage');
const searchDescriptor = origin ? _immutable.default.fromJS(origin.searchDescriptor) : this.getSearchDescriptor();
const searchQuery = searchDescriptor.get('searchQuery');
if (setSearchPageRecordType) {
setSearchPageRecordType(searchDescriptor.get('recordType'));
}
if (setSearchPageVocabulary) {
setSearchPageVocabulary(searchDescriptor.get('vocabulary'));
}
if (setSearchPageKeyword) {
setSearchPageKeyword(searchQuery.get('kw'));
}
if (setSearchPageAdvanced) {
setSearchPageAdvanced(searchQuery.get('as'));
}
}
handleExportButtonClick() {
this.setState({
isExportModalOpen: true
});
}
handleExportOpened() {
this.closeModal();
}
handleModalCancelButtonClick() {
this.closeModal();
}
handleModalCloseButtonClick() {
this.closeModal();
}
handlePageChange(pageNum) {
const {
history,
location
} = this.props;
if (history) {
const {
search
} = location;
const query = _qs.default.parse(search.substring(1));
query.p = (pageNum + 1).toString();
const queryString = _qs.default.stringify(query);
history.push({
pathname: location.pathname,
search: `?${queryString}`,
state: location.state
});
}
}
handlePageSizeChange(pageSize) {
const {
history,
location,
setPreferredPageSize
} = this.props;
if (setPreferredPageSize) {
setPreferredPageSize(pageSize);
}
if (history) {
const {
search
} = location;
const query = _qs.default.parse(search.substring(1));
query.p = '1';
query.size = pageSize.toString();
const queryString = _qs.default.stringify(query);
history.push({
pathname: location.pathname,
search: `?${queryString}`,
state: location.state
});
}
}
handleRelateButtonClick() {
this.setState({
isSearchToRelateModalOpen: true,
selectionValidationError: this.validateSelectedItemsRelatable()
});
}
handleRelationsCreated() {
this.closeModal();
}
handleSortChange(sort) {
const {
history,
location
} = this.props;
if (history) {
const {
search
} = location;
const query = _qs.default.parse(search.substring(1));
query.sort = sort;
const queryString = _qs.default.stringify(query);
history.push({
pathname: location.pathname,
search: `?${queryString}`,
state: location.state
});
}
}
getListType(searchDescriptor) {
const {
config
} = this.context;
return (0, _searchHelpers.getListType)(config, searchDescriptor);
}
getSearchDescriptor() {
// FIXME: Refactor this into a wrapper component that calculates the search descriptor from
// location and params, and passes it into a child. This will eliminate the multiple calls to
// this method from the various render methods in this class.
const {
location,
match
} = this.props;
const {
params
} = match;
const {
search
} = location;
const query = _qs.default.parse(search.substring(1));
const searchQuery = {
...query,
p: parseInt(query.p, 10) - 1,
size: parseInt(query.size, 10)
};
const advancedSearchCondition = query.as;
if (advancedSearchCondition) {
searchQuery.as = JSON.parse(advancedSearchCondition);
}
const searchDescriptor = {
searchQuery
};
['recordType', 'vocabulary', 'csid', 'subresource'].forEach(param => {
const value = params[param];
if (typeof value !== 'undefined') {
searchDescriptor[param] = value;
}
});
return _immutable.default.fromJS(searchDescriptor);
}
getSearchToRelateSubjects() {
const {
selectedItems
} = this.props;
const {
config
} = this.context;
if (!selectedItems) {
return null;
}
const searchDescriptor = this.getSearchDescriptor();
const recordType = searchDescriptor.get('recordType');
const serviceType = (0, _get.default)(config, ['recordTypes', recordType, 'serviceConfig', 'serviceType']);
const itemRecordType = serviceType === 'utility' ? undefined : recordType;
const titleColumnName = (0, _configHelpers.getFirstColumnName)(config, recordType);
return selectedItems.valueSeq().map(item => ({
csid: item.get('csid'),
recordType: itemRecordType || (0, _configHelpers.getRecordTypeNameByServiceObjectName)(config, item.get('docType')),
title: item.get(titleColumnName)
})).toJS();
}
isResultExportable(searchDescriptor) {
const {
config
} = this.context;
const recordType = searchDescriptor.get('recordType');
const subresource = searchDescriptor.get('subresource');
const serviceType = (0, _get.default)(config, ['recordTypes', recordType, 'serviceConfig', 'serviceType']);
return subresource !== 'terms' && subresource !== 'refs' && (serviceType === 'procedure' || serviceType === 'object' || serviceType === 'authority');
}
isResultRelatable(searchDescriptor) {
const {
config
} = this.context;
const recordType = searchDescriptor.get('recordType');
const subresource = searchDescriptor.get('subresource');
const serviceType = (0, _get.default)(config, ['recordTypes', recordType, 'serviceConfig', 'serviceType']);
return subresource !== 'terms' && (serviceType === 'procedure' || serviceType === 'object' || recordType === 'procedure' || recordType === 'object');
}
closeModal() {
this.setState({
isExportModalOpen: false,
isSearchToRelateModalOpen: false,
selectionValidationError: undefined
});
}
normalizeQuery() {
const {
config
} = this.context;
const {
history,
location,
preferredPageSize
} = this.props;
const {
search
} = location;
const query = _qs.default.parse(search.substring(1));
if (history) {
const normalizedQueryParams = {};
const pageSize = parseInt(query.size, 10);
if (Number.isNaN(pageSize) || pageSize < 1) {
const normalizedPageSize = preferredPageSize || config.defaultSearchPageSize || 20;
normalizedQueryParams.size = normalizedPageSize.toString();
} else if (pageSize > 2500) {
// Services layer max is 2500
normalizedQueryParams.size = '2500';
} else if (pageSize.toString() !== query.size) {
normalizedQueryParams.size = pageSize.toString();
}
const pageNum = parseInt(query.p, 10);
if (Number.isNaN(pageNum) || pageNum < 1) {
normalizedQueryParams.p = '1';
} else if (pageNum.toString() !== query.p) {
normalizedQueryParams.p = pageNum.toString();
}
if (Object.keys(normalizedQueryParams).length > 0) {
const newQuery = {
...query,
...normalizedQueryParams
};
const queryString = _qs.default.stringify(newQuery);
history.replace({
pathname: location.pathname,
search: `?${queryString}`,
state: location.state
});
return true;
}
}
return false;
}
search() {
const {
search
} = this.props;
const {
config
} = this.context;
const searchDescriptor = this.getSearchDescriptor();
const searchQuery = searchDescriptor.get('searchQuery');
// Don't send the query if the provided page size and/or number are invalid.
// The search will be repeated with a valid descriptor once normalizeQuery
// has set them to the defaults.
if (!Number.isNaN(searchQuery.get('p')) && !Number.isNaN(searchQuery.get('size')) && search) {
const listType = this.getListType(searchDescriptor);
search(config, _searchNames.SEARCH_RESULT_PAGE_SEARCH_NAME, searchDescriptor, listType);
}
}
validateSelectedItemsRelatable() {
const {
perms,
selectedItems
} = this.props;
const {
config
} = this.context;
if (selectedItems) {
let err;
selectedItems.valueSeq().find(item => {
if (item.get('workflowState') === 'locked') {
err = {
code: 'locked'
};
return true;
}
const recordType = (0, _configHelpers.getRecordTypeNameByUri)(config, item.get('uri'));
if (!(0, _permissionHelpers.canRelate)(recordType, perms, config)) {
const recordMessages = (0, _get.default)(config, ['recordTypes', recordType, 'messages', 'record']);
err = {
code: 'notPermitted',
values: {
name: /*#__PURE__*/_react.default.createElement(_reactIntl.FormattedMessage, recordMessages.name),
collectionName: /*#__PURE__*/_react.default.createElement(_reactIntl.FormattedMessage, recordMessages.collectionName)
}
};
return true;
}
return false;
});
if (err) {
return err;
}
}
return undefined;
}
renderCheckbox({
rowData,
rowIndex
}) {
const {
selectedItems
} = this.props;
const itemCsid = rowData.get('csid');
const selected = selectedItems ? selectedItems.has(itemCsid) : false;
return /*#__PURE__*/_react.default.createElement(_CheckboxInput.default, {
embedded: true,
name: `${rowIndex}`,
value: selected
// DRYD-252: Elaborate workaround for Firefox, part II. Use this onClick instead of the
// onCommit and onClick below.
,
onClick: this.handleCheckboxClick
// onCommit={this.handleCheckboxCommit}
// Prevent clicking on the checkbox from selecting the record.
// onClick={stopPropagation}
});
}
renderHeader({
searchError,
searchResult
}) {
const {
selectedItems,
setAllItemsSelected
} = this.props;
const {
config
} = this.context;
const searchDescriptor = this.getSearchDescriptor();
const listType = this.getListType(searchDescriptor);
let selectBar;
if (!searchError) {
const selectedCount = selectedItems ? selectedItems.size : 0;
let relateButton;
if (this.isResultRelatable(searchDescriptor)) {
relateButton = /*#__PURE__*/_react.default.createElement(_RelateButton.default, {
disabled: selectedCount < 1,
key: "relate",
label: /*#__PURE__*/_react.default.createElement(_reactIntl.FormattedMessage, messages.relate),
name: "relate",
onClick: this.handleRelateButtonClick
});
}
let exportButton;
if (this.isResultExportable(searchDescriptor)) {
exportButton = /*#__PURE__*/_react.default.createElement(_ExportButton.default, {
disabled: selectedCount < 1,
key: "export",
onClick: this.handleExportButtonClick
});
}
selectBar = /*#__PURE__*/_react.default.createElement(_SelectBar.default, {
buttons: [relateButton, exportButton],
config: config,
listType: listType,
searchDescriptor: searchDescriptor,
searchName: _searchNames.SEARCH_RESULT_PAGE_SEARCH_NAME,
searchResult: searchResult,
selectedItems: selectedItems,
setAllItemsSelected: setAllItemsSelected
});
}
return /*#__PURE__*/_react.default.createElement("header", null, /*#__PURE__*/_react.default.createElement(_SearchResultSummary.default, {
config: config,
listType: listType,
searchDescriptor: searchDescriptor,
searchError: searchError,
searchResult: searchResult,
onEditSearchLinkClick: this.handleEditSearchLinkClick,
onPageSizeChange: this.handlePageSizeChange
}), selectBar);
}
renderFooter({
searchResult
}) {
if (searchResult) {
const {
config
} = this.context;
const searchDescriptor = this.getSearchDescriptor();
const listType = this.getListType(searchDescriptor);
const listTypeConfig = config.listTypes[listType];
const {
listNodeName
} = listTypeConfig;
const list = searchResult.get(listNodeName);
const totalItems = parseInt(list.get('totalItems'), 10);
const pageNum = parseInt(list.get('pageNum'), 10);
const pageSize = parseInt(list.get('pageSize'), 10);
const lastPage = Math.max(0, Number.isNaN(totalItems) ? 0 : Math.ceil(totalItems / pageSize) - 1);
return /*#__PURE__*/_react.default.createElement("footer", null, /*#__PURE__*/_react.default.createElement(_Pager.default, {
currentPage: pageNum,
lastPage: lastPage,
pageSize: pageSize,
onPageChange: this.handlePageChange,
onPageSizeChange: this.handlePageSizeChange
}));
}
return null;
}
render() {
const {
location,
history,
isSidebarOpen,
selectedItems
} = this.props;
const {
config
} = this.context;
const {
isExportModalOpen,
isSearchToRelateModalOpen,
selectionValidationError
} = this.state;
const searchDescriptor = this.getSearchDescriptor();
const listType = this.getListType(searchDescriptor);
const recordType = searchDescriptor.get('recordType');
const vocabulary = searchDescriptor.get('vocabulary');
const csid = searchDescriptor.get('csid');
const subresource = searchDescriptor.get('subresource');
const validation = (0, _configHelpers.validateLocation)(config, {
recordType,
vocabulary,
csid,
subresource
});
if (validation.error) {
return /*#__PURE__*/_react.default.createElement(_ErrorPage.default, {
error: validation.error
});
}
let searchToRelateModal;
if (this.isResultRelatable(searchDescriptor)) {
searchToRelateModal = /*#__PURE__*/_react.default.createElement(_SearchToRelateModalContainer.default, {
allowedServiceTypes: ['object', 'procedure'],
subjects: this.getSearchToRelateSubjects,
config: config,
isOpen: isSearchToRelateModalOpen,
defaultRecordTypeValue: "collectionobject",
error: selectionValidationError,
onCancelButtonClick: this.handleModalCancelButtonClick,
onCloseButtonClick: this.handleModalCloseButtonClick,
onRelationsCreated: this.handleRelationsCreated
});
}
let exportModal;
if (this.isResultExportable(searchDescriptor)) {
exportModal = /*#__PURE__*/_react.default.createElement(_ExportModalContainer.default, {
config: config,
isOpen: isExportModalOpen,
recordType: recordType,
vocabulary: vocabulary,
selectedItems: selectedItems,
onCancelButtonClick: this.handleModalCancelButtonClick,
onCloseButtonClick: this.handleModalCloseButtonClick,
onExportOpened: this.handleExportOpened
});
}
return /*#__PURE__*/_react.default.createElement("div", {
className: _SearchResultPage.default.common
}, /*#__PURE__*/_react.default.createElement(_SearchResultTitleBar.default, {
config: config,
searchDescriptor: searchDescriptor,
searchName: _searchNames.SEARCH_RESULT_PAGE_SEARCH_NAME,
updateDocumentTitle: true
}), /*#__PURE__*/_react.default.createElement("div", {
className: isSidebarOpen ? _PageBody.default.common : _PageBody.default.full
}, /*#__PURE__*/_react.default.createElement(_WatchedSearchResultTableContainer.default, {
config: config,
history: history,
linkState: {
originSearchPage: (0, _get.default)(location, ['state', 'originSearchPage'])
},
listType: listType,
searchName: _searchNames.SEARCH_RESULT_PAGE_SEARCH_NAME,
searchDescriptor: searchDescriptor,
recordType: recordType,
showCheckboxColumn: true,
renderCheckbox: this.renderCheckbox,
renderHeader: this.renderHeader,
renderFooter: this.renderFooter,
onSortChange: this.handleSortChange,
search: this.search
}), /*#__PURE__*/_react.default.createElement(_SearchResultSidebar.default, {
config: config,
history: history,
isOpen: isSidebarOpen,
recordType: recordType,
selectedItems: selectedItems
})), searchToRelateModal, exportModal);
}
}
exports.default = SearchResultPage;
SearchResultPage.propTypes = propTypes;
SearchResultPage.defaultProps = defaultProps;
SearchResultPage.contextTypes = contextTypes;