cspace-ui
Version:
CollectionSpace user interface for browsers
669 lines (661 loc) • 23.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.searchName = exports.default = exports.BaseSearchToSelectModal = void 0;
var _react = _interopRequireWildcard(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _reactIntl = require("react-intl");
var _immutable = _interopRequireDefault(require("immutable"));
var _get = _interopRequireDefault(require("lodash/get"));
var _isEqual = _interopRequireDefault(require("lodash/isEqual"));
var _cspaceLayout = require("cspace-layout");
var _CheckboxInput = _interopRequireDefault(require("cspace-input/lib/components/CheckboxInput"));
var _SearchForm = _interopRequireDefault(require("./SearchForm"));
var _Pager = _interopRequireDefault(require("./Pager"));
var _AcceptSelectionButton = _interopRequireDefault(require("./AcceptSelectionButton"));
var _BackButton = _interopRequireDefault(require("../navigation/BackButton"));
var _CancelButton = _interopRequireDefault(require("../navigation/CancelButton"));
var _SearchButton = _interopRequireDefault(require("./SearchButton"));
var _SearchClearButton = _interopRequireDefault(require("./SearchClearButton"));
var _SearchResultSummary = _interopRequireDefault(require("./SearchResultSummary"));
var _SearchToSelectTitleBar = _interopRequireDefault(require("./SearchToSelectTitleBar"));
var _SelectBar = _interopRequireDefault(require("./SelectBar"));
var _SearchResultTableContainer = _interopRequireDefault(require("../../containers/search/SearchResultTableContainer"));
var _searchHelpers = require("../../helpers/searchHelpers");
var _SearchToSelectModal = _interopRequireDefault(require("../../../styles/cspace-ui/SearchToSelectModal.css"));
var _searchOperators = require("../../constants/searchOperators");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
const searchName = exports.searchName = 'searchToSelect';
const messages = (0, _reactIntl.defineMessages)({
editSearch: {
"id": "searchToSelectModal.editSearch",
"defaultMessage": "Revise search"
},
label: {
"id": "searchToSelectModal.label",
"defaultMessage": "Select records"
}
});
// FIXME: Make default page size configurable
const defaultPageSize = 20;
const stopPropagation = event => {
event.stopPropagation();
};
const propTypes = {
acceptButtonClassName: _propTypes.default.string,
acceptButtonLabel: _propTypes.default.node,
allowedRecordTypes: _propTypes.default.arrayOf(_propTypes.default.string),
allowedServiceTypes: _propTypes.default.arrayOf(_propTypes.default.string),
config: _propTypes.default.shape({
listTypes: _propTypes.default.object,
recordTypes: _propTypes.default.object
}),
intl: _reactIntl.intlShape,
isOpen: _propTypes.default.bool,
keywordValue: _propTypes.default.string,
defaultRecordTypeValue: _propTypes.default.string,
defaultVocabularyValue: _propTypes.default.string,
recordTypeValue: _propTypes.default.string,
vocabularyValue: _propTypes.default.string,
advancedSearchCondition: _propTypes.default.instanceOf(_immutable.default.Map),
advancedSearchConditionLimitBy: _propTypes.default.instanceOf(_immutable.default.Map),
advancedSearchConditionSearchTerms: _propTypes.default.instanceOf(_immutable.default.Map),
preferredAdvancedSearchBooleanOp: _propTypes.default.string,
preferredPageSize: _propTypes.default.number,
perms: _propTypes.default.instanceOf(_immutable.default.Map),
selectedItems: _propTypes.default.instanceOf(_immutable.default.Map),
singleSelect: _propTypes.default.bool,
titleMessage: _propTypes.default.objectOf(_propTypes.default.string),
getAuthorityVocabCsid: _propTypes.default.func,
buildRecordFieldOptionLists: _propTypes.default.func,
deleteOptionList: _propTypes.default.func,
onAdvancedSearchConditionCommit: _propTypes.default.func,
onAdvancedSearchConditionLimitByCommit: _propTypes.default.func,
onAdvancedSearchConditionSearchTermsCommit: _propTypes.default.func,
onKeywordCommit: _propTypes.default.func,
onRecordTypeCommit: _propTypes.default.func,
onVocabularyCommit: _propTypes.default.func,
onAccept: _propTypes.default.func,
onCloseButtonClick: _propTypes.default.func,
onCancelButtonClick: _propTypes.default.func,
onClearButtonClick: _propTypes.default.func,
onItemSelectChange: _propTypes.default.func,
customizeSearchDescriptor: _propTypes.default.func,
clearSearchResults: _propTypes.default.func,
parentSelector: _propTypes.default.func,
renderAcceptPending: _propTypes.default.func,
search: _propTypes.default.func,
setAllItemsSelected: _propTypes.default.func,
setPreferredPageSize: _propTypes.default.func,
shouldShowCheckbox: _propTypes.default.func,
useNewSearch: _propTypes.default.bool
};
const defaultProps = {
defaultVocabularyValue: 'all',
selectedItems: _immutable.default.Map(),
renderAcceptPending: () => /*#__PURE__*/_react.default.createElement("p", null),
shouldShowCheckbox: () => true
};
class BaseSearchToSelectModal extends _react.Component {
constructor() {
super();
this.handleAcceptButtonClick = this.handleAcceptButtonClick.bind(this);
this.handleCancelButtonClick = this.handleCancelButtonClick.bind(this);
this.handleCheckboxCommit = this.handleCheckboxCommit.bind(this);
this.handleCloseButtonClick = this.handleCloseButtonClick.bind(this);
this.handleEditSearchLinkClick = this.handleEditSearchLinkClick.bind(this);
this.handleFormSearch = this.handleFormSearch.bind(this);
this.handleItemClick = this.handleItemClick.bind(this);
this.handlePageChange = this.handlePageChange.bind(this);
this.handlePageSizeChange = this.handlePageSizeChange.bind(this);
this.handleSortChange = this.handleSortChange.bind(this);
this.renderCheckbox = this.renderCheckbox.bind(this);
this.renderEditSearchLink = this.renderEditSearchLink.bind(this);
this.renderModalButtonBar = this.renderModalButtonBar.bind(this);
this.renderSearchResultTableHeader = this.renderSearchResultTableHeader.bind(this);
this.renderSearchResultTableFooter = this.renderSearchResultTableFooter.bind(this);
this.state = {
isSearchInitiated: false,
pageNum: 0,
sort: null
};
}
// eslint-disable-next-line camelcase
UNSAFE_componentWillReceiveProps(nextProps) {
const {
isOpen
} = this.props;
const {
isOpen: nextIsOpen
} = nextProps;
if (isOpen && !nextIsOpen) {
// Closing.
this.setState({
isAcceptHandlerPending: false,
isSearchInitiated: false,
pageNum: 0,
sort: null
});
}
}
componentDidUpdate(prevProps, prevState) {
const {
isOpen
} = this.props;
const {
isOpen: prevIsOpen
} = prevProps;
if (prevIsOpen && !isOpen) {
// Closed.
const {
clearSearchResults,
onRecordTypeCommit
} = this.props;
if (clearSearchResults) {
clearSearchResults(searchName);
}
if (onRecordTypeCommit) {
onRecordTypeCommit('');
}
} else if (!prevIsOpen && isOpen) {
// Opened.
const {
config,
defaultRecordTypeValue,
defaultVocabularyValue,
onRecordTypeCommit,
onVocabularyCommit
} = this.props;
if (onRecordTypeCommit) {
onRecordTypeCommit(defaultRecordTypeValue);
if (onVocabularyCommit) {
const serviceType = (0, _get.default)(config, ['recordTypes', defaultRecordTypeValue, 'serviceConfig', 'serviceType']);
if (serviceType === 'authority') {
onVocabularyCommit(defaultVocabularyValue);
}
}
}
}
const {
isSearchInitiated,
pageNum,
sort
} = this.state;
const {
config,
defaultVocabularyValue,
recordTypeValue,
vocabularyValue,
advancedSearchCondition,
preferredPageSize,
onVocabularyCommit
} = this.props;
const {
recordTypeValue: prevRecordTypeValue,
vocabularyValue: prevVocabularyValue,
advancedSearchCondition: prevAdvancedSearchCondition,
preferredPageSize: prevPreferredPageSize
} = prevProps;
if (isSearchInitiated) {
const {
pageNum: prevPageNum,
sort: prevSort
} = prevState;
if (recordTypeValue !== prevRecordTypeValue || vocabularyValue !== prevVocabularyValue || !(0, _isEqual.default)(advancedSearchCondition, prevAdvancedSearchCondition) || preferredPageSize !== prevPreferredPageSize || pageNum !== prevPageNum || sort !== prevSort) {
this.search();
}
} else if (recordTypeValue !== prevRecordTypeValue) {
const serviceType = (0, _get.default)(config, ['recordTypes', recordTypeValue, 'serviceConfig', 'serviceType']);
if (serviceType === 'authority') {
onVocabularyCommit(defaultVocabularyValue);
}
}
}
handleAcceptButtonClick() {
const {
isSearchInitiated
} = this.state;
if (isSearchInitiated) {
const {
onAccept,
selectedItems
} = this.props;
if (onAccept) {
this.setState({
isAcceptHandlerPending: true,
isSearchInitiated: false
});
Promise.resolve(onAccept(selectedItems, this.getSearchDescriptor()));
}
} else {
this.search();
}
}
handleCancelButtonClick(event) {
const {
onCancelButtonClick
} = this.props;
if (onCancelButtonClick) {
onCancelButtonClick(event);
}
}
handleCloseButtonClick(event) {
const {
onCloseButtonClick
} = this.props;
if (onCloseButtonClick) {
onCloseButtonClick(event);
}
}
handleEditSearchLinkClick() {
const {
clearSearchResults
} = this.props;
if (clearSearchResults) {
clearSearchResults(searchName);
}
this.setState({
isSearchInitiated: false
});
}
handleFormSearch() {
this.search();
}
handleCheckboxCommit(path, value) {
const index = parseInt(path[0], 10);
this.setItemSelected(index, value);
}
handleItemClick(item, index) {
const {
selectedItems,
shouldShowCheckbox
} = this.props;
if (shouldShowCheckbox(item)) {
const itemCsid = item.get('csid');
const selected = selectedItems ? selectedItems.has(itemCsid) : false;
this.setItemSelected(index, !selected);
}
}
handlePageChange(pageNum) {
this.setState({
pageNum
});
}
handlePageSizeChange(pageSize) {
const {
setPreferredPageSize
} = this.props;
let normalizedPageSize;
if (Number.isNaN(pageSize) || pageSize < 1) {
normalizedPageSize = 0;
} else if (pageSize > 2500) {
normalizedPageSize = 2500;
} else {
normalizedPageSize = pageSize;
}
if (setPreferredPageSize) {
this.setState({
pageNum: 0
});
setPreferredPageSize(normalizedPageSize);
}
}
handleSortChange(sort) {
this.setState({
sort
});
}
getSearchDescriptor() {
const {
config,
recordTypeValue: recordType,
vocabularyValue: vocabulary,
keywordValue: keyword,
advancedSearchCondition,
advancedSearchConditionLimitBy,
advancedSearchConditionSearchTerms,
preferredPageSize,
customizeSearchDescriptor,
useNewSearch
} = this.props;
const {
pageNum,
sort
} = this.state;
const pageSize = preferredPageSize || defaultPageSize;
const searchQuery = {
p: pageNum,
size: pageSize
};
const searchCondition = useNewSearch || typeof useNewSearch === 'undefined' ? _immutable.default.Map({
op: _searchOperators.OP_AND,
value: _immutable.default.List.of(advancedSearchConditionSearchTerms, advancedSearchConditionLimitBy)
}) : advancedSearchCondition;
if (sort) {
searchQuery.sort = sort;
}
const kw = keyword ? keyword.trim() : '';
if (kw) {
searchQuery.kw = kw;
}
const fields = (0, _get.default)(config, ['recordTypes', recordType, 'fields']);
const condition = (0, _searchHelpers.normalizeCondition)(fields, searchCondition);
if (condition) {
searchQuery.as = condition;
}
const searchDescriptor = _immutable.default.fromJS({
recordType,
vocabulary,
searchQuery
});
if (customizeSearchDescriptor) {
return customizeSearchDescriptor(searchDescriptor);
}
return searchDescriptor;
}
setItemSelected(index, selected) {
const {
config,
singleSelect,
setAllItemsSelected,
onItemSelectChange
} = this.props;
if (onItemSelectChange) {
const searchDescriptor = this.getSearchDescriptor();
const {
listType
} = (0, _searchHelpers.deriveSearchType)(config, searchName, searchDescriptor);
if (singleSelect && selected) {
setAllItemsSelected(config, searchName, searchDescriptor, listType, false);
}
onItemSelectChange(config, searchName, searchDescriptor, listType, index, selected);
}
}
search() {
const {
config,
search
} = this.props;
if (search) {
const searchDescriptor = this.getSearchDescriptor();
search(config, searchName, searchDescriptor);
this.setState({
isSearchInitiated: true
});
}
}
renderCheckbox({
rowData,
rowIndex
}) {
const {
shouldShowCheckbox
} = this.props;
if (shouldShowCheckbox(rowData)) {
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,
onCommit: this.handleCheckboxCommit
// Prevent click on the checkbox from propagating to the row, which would cause
// double-toggling of the selected state.
,
onClick: stopPropagation
});
}
return null;
}
renderSearchForm() {
const {
allowedRecordTypes,
allowedServiceTypes,
config,
intl,
keywordValue,
recordTypeValue,
vocabularyValue,
perms,
preferredAdvancedSearchBooleanOp,
advancedSearchCondition,
advancedSearchConditionLimitBy,
advancedSearchConditionSearchTerms,
getAuthorityVocabCsid,
buildRecordFieldOptionLists,
deleteOptionList,
onAdvancedSearchConditionCommit,
onAdvancedSearchConditionLimitByCommit,
onAdvancedSearchConditionSearchTermsCommit,
onKeywordCommit,
onRecordTypeCommit,
onVocabularyCommit,
useNewSearch
} = this.props;
let recordTypeInputReadOnly = true;
let recordTypeInputRootType;
if (allowedServiceTypes) {
// Allow the record type to be changed.
recordTypeInputReadOnly = false;
// Don't show the All Records option.
recordTypeInputRootType = '';
} else if (allowedRecordTypes) {
recordTypeInputReadOnly = allowedRecordTypes.length < 2;
}
return /*#__PURE__*/_react.default.createElement(_SearchForm.default, {
config: config,
intl: intl,
recordTypeValue: recordTypeValue,
vocabularyValue: vocabularyValue,
keywordValue: keywordValue,
advancedSearchCondition: advancedSearchCondition,
advancedSearchConditionLimitBy: advancedSearchConditionLimitBy,
advancedSearchConditionSearchTerms: advancedSearchConditionSearchTerms,
perms: perms,
preferredAdvancedSearchBooleanOp: preferredAdvancedSearchBooleanOp,
recordTypeInputReadOnly: recordTypeInputReadOnly,
recordTypeInputRootType: recordTypeInputRootType,
recordTypeInputRecordTypes: allowedRecordTypes,
recordTypeInputServiceTypes: allowedServiceTypes,
getAuthorityVocabCsid: getAuthorityVocabCsid,
buildRecordFieldOptionLists: buildRecordFieldOptionLists,
deleteOptionList: deleteOptionList,
onAdvancedSearchConditionCommit: onAdvancedSearchConditionCommit,
onAdvancedSearchConditionLimitByCommit: onAdvancedSearchConditionLimitByCommit,
onAdvancedSearchConditionSearchTermsCommit: onAdvancedSearchConditionSearchTermsCommit,
onKeywordCommit: onKeywordCommit,
onRecordTypeCommit: onRecordTypeCommit,
onVocabularyCommit: onVocabularyCommit,
onSearch: this.handleFormSearch,
showNewSearch: useNewSearch || typeof useNewSearch === 'undefined'
});
}
renderEditSearchLink() {
return /*#__PURE__*/_react.default.createElement("button", {
type: "button",
onClick: this.handleEditSearchLinkClick
}, /*#__PURE__*/_react.default.createElement(_reactIntl.FormattedMessage, messages.editSearch));
}
renderSearchResultTableHeader({
searchError,
searchResult
}) {
const {
config,
selectedItems,
setAllItemsSelected,
shouldShowCheckbox,
singleSelect
} = this.props;
if (searchError) {
return null;
}
const listType = (0, _searchHelpers.getListTypeFromResult)(config, searchResult);
const searchDescriptor = this.getSearchDescriptor();
let selectBar;
if (!singleSelect) {
// If only a single selection is allowed, there's no need to show a count of selected records
// or a select all checkbox.
selectBar = /*#__PURE__*/_react.default.createElement(_SelectBar.default, {
config: config,
listType: listType,
searchDescriptor: searchDescriptor,
searchName: searchName,
searchResult: searchResult,
selectedItems: selectedItems,
setAllItemsSelected: setAllItemsSelected,
showCheckboxFilter: shouldShowCheckbox
});
}
return /*#__PURE__*/_react.default.createElement("header", null, /*#__PURE__*/_react.default.createElement(_SearchResultSummary.default, {
config: config,
listType: listType,
searchDescriptor: searchDescriptor,
renderEditLink: this.renderEditSearchLink,
onPageSizeChange: this.handlePageSizeChange
}), selectBar);
}
renderSearchResultTableFooter({
searchResult
}) {
const {
config
} = this.props;
if (searchResult) {
const listType = (0, _searchHelpers.getListTypeFromResult)(config, searchResult);
const listTypeConfig = config.listTypes[listType];
const list = searchResult.get(listTypeConfig.listNodeName);
const totalItems = parseInt(list.get('totalItems'), 10);
const pageSize = parseInt(list.get('pageSize'), 10);
const pageNum = parseInt(list.get('pageNum'), 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;
}
renderSearchResultTable() {
const {
config,
recordTypeValue
} = this.props;
const searchDescriptor = this.getSearchDescriptor();
return /*#__PURE__*/_react.default.createElement(_SearchResultTableContainer.default, {
config: config,
linkItems: false,
recordType: recordTypeValue,
searchName: searchName,
searchDescriptor: searchDescriptor,
showCheckboxColumn: true,
renderCheckbox: this.renderCheckbox,
renderHeader: this.renderSearchResultTableHeader,
renderFooter: this.renderSearchResultTableFooter,
onItemClick: this.handleItemClick,
onSortChange: this.handleSortChange
});
}
renderModalButtonBar() {
const {
acceptButtonClassName,
acceptButtonLabel,
selectedItems,
onClearButtonClick
} = this.props;
const {
isAcceptHandlerPending,
isSearchInitiated
} = this.state;
const cancelButton = /*#__PURE__*/_react.default.createElement(_CancelButton.default, {
disabled: isAcceptHandlerPending,
onClick: this.handleCancelButtonClick
});
let acceptButton;
let backButton;
let clearButton;
if (isSearchInitiated) {
acceptButton = /*#__PURE__*/_react.default.createElement(_AcceptSelectionButton.default, {
className: acceptButtonClassName,
disabled: isAcceptHandlerPending || !selectedItems || selectedItems.size < 1,
label: acceptButtonLabel,
onClick: this.handleAcceptButtonClick
});
backButton = /*#__PURE__*/_react.default.createElement(_BackButton.default, {
disabled: isAcceptHandlerPending,
label: /*#__PURE__*/_react.default.createElement(_reactIntl.FormattedMessage, messages.editSearch),
onClick: this.handleEditSearchLinkClick
});
} else {
acceptButton = /*#__PURE__*/_react.default.createElement(_SearchButton.default, {
disabled: isAcceptHandlerPending,
type: "button",
onClick: this.handleAcceptButtonClick
});
clearButton = /*#__PURE__*/_react.default.createElement(_SearchClearButton.default, {
onClick: onClearButtonClick
});
}
return /*#__PURE__*/_react.default.createElement("div", null, clearButton, backButton, cancelButton, acceptButton);
}
render() {
const {
config,
intl,
isOpen,
recordTypeValue,
parentSelector,
singleSelect,
titleMessage,
renderAcceptPending
} = this.props;
const {
isAcceptHandlerPending,
isSearchInitiated
} = this.state;
let content;
let title;
if (isOpen) {
if (isAcceptHandlerPending) {
content = renderAcceptPending();
} else if (isSearchInitiated) {
content = this.renderSearchResultTable();
} else {
content = this.renderSearchForm();
}
const searchDescriptor = this.getSearchDescriptor();
title = /*#__PURE__*/_react.default.createElement(_SearchToSelectTitleBar.default, {
config: config,
isSearchInitiated: isSearchInitiated,
recordType: recordTypeValue,
searchDescriptor: searchDescriptor,
singleSelect: singleSelect,
titleMessage: titleMessage
});
}
return /*#__PURE__*/_react.default.createElement(_cspaceLayout.Modal, {
className: _SearchToSelectModal.default.common,
contentLabel: intl.formatMessage(messages.label),
title: title,
isOpen: isOpen,
showCloseButton: !isAcceptHandlerPending,
closeButtonClassName: "material-icons",
closeButtonLabel: "close",
parentSelector: parentSelector,
renderButtonBar: this.renderModalButtonBar,
onCloseButtonClick: this.handleCloseButtonClick
}, content);
}
}
exports.BaseSearchToSelectModal = BaseSearchToSelectModal;
BaseSearchToSelectModal.propTypes = propTypes;
BaseSearchToSelectModal.defaultProps = defaultProps;
var _default = exports.default = (0, _reactIntl.injectIntl)(BaseSearchToSelectModal);