UNPKG

cspace-ui

Version:
375 lines (362 loc) 16.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.searchKey = exports.isPending = exports.isDirty = exports.getState = exports.getSelectedItems = exports.getResult = exports.getMostRecentDescriptor = exports.getIndexesByCsid = exports.getError = exports.default = void 0; var _immutable = _interopRequireDefault(require("immutable")); var _objectHelpers = require("../helpers/objectHelpers"); var _actionCodes = require("../constants/actionCodes"); var _searchNames = require("../constants/searchNames"); var _recordDataHelpers = require("../helpers/recordDataHelpers"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * Generates a search key for a given search descriptor. All search descriptors that represent the * same search should have the same search key. */ const searchKey = searchDescriptor => { // First convert the search descriptor to an array of name/value pairs, sorted by name. This // ensures that the key is not sensitive to the order in which properties are iterated out of the // search descriptor object. const pairs = (0, _objectHelpers.asPairs)(_immutable.default.Map.isMap(searchDescriptor) ? searchDescriptor.toJS() : searchDescriptor); // Serialize the name/value pairs to JSON. return JSON.stringify(pairs); }; exports.searchKey = searchKey; const handleSetMostRecentSearch = (state, action) => { const { searchName, searchDescriptor } = action.meta; const key = searchKey(searchDescriptor); const namedSearch = state.get(searchName); if (namedSearch && namedSearch.getIn(['byKey', key])) { return state.set(searchName, namedSearch.set('mostRecentKey', key)); } return state; }; const handleSearchStarted = (state, action) => { const { listTypeConfig, searchName, searchDescriptor } = action.meta; const { listNodeName, itemNodeName } = listTypeConfig; const namedSearch = state.get(searchName) || _immutable.default.Map(); const key = searchKey(searchDescriptor); const mostRecentKey = namedSearch.get('mostRecentKey'); let updatedNamedSearch = namedSearch; let result = null; let itemsInPage = null; let totalItems = null; let items = null; if (mostRecentKey) { const mostRecentSearchState = namedSearch.getIn(['byKey', mostRecentKey]); const mostRecentSearchDescriptor = mostRecentSearchState.get('descriptor'); const changes = (0, _objectHelpers.diff)(searchDescriptor.toJS(), mostRecentSearchDescriptor.toJS()); const changeCount = Object.keys(changes).length; const pageChanged = ('searchQuery.p' in changes); const sortChanged = ('searchQuery.sort' in changes); const seqIDChanged = ('seqID' in changes); if (pageChanged && changeCount === 1) { // Only the page number changed from the last search. Seed these results with the total items // count from the previous results, since it probably will remain the same. This allows the // UI to render a smooth transition from this page to the next, without the count information // and pager blinking out. totalItems = mostRecentSearchState.getIn(['result', listNodeName, 'totalItems']); } else { // Something other than the page number changed from the last search. Clear out stored // search states. (If only the page number changed, keep the previous states around as a // cache so that flipping between pages will be fast.) updatedNamedSearch = updatedNamedSearch.set('byKey', _immutable.default.Map()); if (sortChanged && changeCount === 1) { // Only the sort changed from the last search. As above, seed these results with the total // items count. Also seed the itemsInPage count, since this is not expected to change. totalItems = mostRecentSearchState.getIn(['result', listNodeName, 'totalItems']); itemsInPage = mostRecentSearchState.getIn(['result', listNodeName, 'itemsInPage']); } else if (seqIDChanged && changeCount === 1) { // Only the seq id changed from the last search. This means that the search parameters // themselves haven't changed, but the search is being triggered because the data may have // changed. Seed the result with the previous total items, items in page, and items. This // makes for the smoothest possible transition to the new result set. totalItems = mostRecentSearchState.getIn(['result', listNodeName, 'totalItems']); itemsInPage = mostRecentSearchState.getIn(['result', listNodeName, 'itemsInPage']); items = mostRecentSearchState.getIn(['result', listNodeName, itemNodeName]); } delete changes['searchQuery.p']; delete changes['searchQuery.size']; delete changes['searchQuery.sort']; delete changes.seqID; if (Object.keys(changes).length > 0) { // Something other than page num, page size, sort, or seq id changed. Clear selected items. updatedNamedSearch = updatedNamedSearch.delete('selected'); } } } // Seed the search state with a blank result that has the page num and size of this search, and // the total items and items in page counts from the last search, if they're not expected to // change. This allows a blank result table to be rendered while the search is pending, // preventing a flash of empty content. const searchQuery = searchDescriptor.get('searchQuery'); const p = searchQuery.get('p'); const size = searchQuery.get('size'); const pageNum = typeof p === 'number' ? p.toString() : p; const pageSize = typeof size === 'number' ? size.toString() : size; result = _immutable.default.fromJS({ [listNodeName]: { itemsInPage, totalItems, pageNum, pageSize, [itemNodeName]: items } }); updatedNamedSearch = updatedNamedSearch.set('mostRecentKey', key).setIn(['byKey', key], _immutable.default.fromJS({ result, descriptor: searchDescriptor, isPending: true })); return state.set(searchName, updatedNamedSearch); }; const computeIndexesByCsid = (listTypeConfig, result) => { const { listNodeName, itemNodeName } = listTypeConfig; let indexesByCsid; let items = result.getIn([listNodeName, itemNodeName]); if (items) { if (!_immutable.default.List.isList(items)) { items = _immutable.default.List.of(items); } indexesByCsid = _immutable.default.Map(items.map((item, index) => [item.get('csid') || item.get('docId'), index])); } return indexesByCsid; }; const setSearchResult = (state, listTypeConfig, searchName, searchDescriptor, result) => { const namedSearch = state.get(searchName); const key = searchKey(searchDescriptor); if (namedSearch && namedSearch.hasIn(['byKey', key])) { const { itemNodeName, listNodeName, normalizeListData } = listTypeConfig; const normalizedResult = normalizeListData ? normalizeListData(result, listTypeConfig) : result; const searchState = namedSearch.getIn(['byKey', key]); const updatedSearchState = searchState.set('isPending', false).set('result', normalizedResult).set('indexesByCsid', computeIndexesByCsid(listTypeConfig, normalizedResult)).set('listNodeName', listNodeName).set('itemNodeName', itemNodeName).delete('error').delete('isDirty'); const updatedNamedSearch = namedSearch.setIn(['byKey', key], updatedSearchState); return state.set(searchName, updatedNamedSearch); } return state; }; const handleCreateEmptySearchResult = (state, action) => { const { listTypeConfig, searchName, searchDescriptor } = action.meta; const searchQuery = searchDescriptor.get('searchQuery'); const p = searchQuery.get('p'); const size = searchQuery.get('size'); const pageNum = typeof p === 'number' ? p.toString() : p; const pageSize = typeof size === 'number' ? size.toString() : size; const result = _immutable.default.fromJS({ [listTypeConfig.listNodeName]: { itemsInPage: '0', totalItems: '0', pageNum, pageSize } }); return setSearchResult(state, listTypeConfig, searchName, searchDescriptor, result); }; const handleSearchFulfilled = (state, action) => { const { listTypeConfig, searchName, searchDescriptor } = action.meta; return setSearchResult(state, listTypeConfig, searchName, searchDescriptor, _immutable.default.fromJS(action.payload.data)); }; const handleSearchRejected = (state, action) => { const { searchName, searchDescriptor } = action.meta; const namedSearch = state.get(searchName); const key = searchKey(searchDescriptor); if (namedSearch && namedSearch.hasIn(['byKey', key])) { const updatedNamedSearch = namedSearch.setIn(['byKey', key], namedSearch.getIn(['byKey', key]).set('isPending', false).set('error', _immutable.default.fromJS(action.payload)).delete('result').delete('isDirty')); return state.set(searchName, updatedNamedSearch); } return state; }; const handleSetAllResultItemsSelected = (state, action) => { const isSelected = action.payload; const { filter, listTypeConfig, searchName, searchDescriptor } = action.meta; const { listNodeName, itemNodeName } = listTypeConfig; const namedSearch = state.get(searchName); if (namedSearch) { const key = searchKey(searchDescriptor); const path = ['byKey', key, 'result', listNodeName, itemNodeName]; let items = namedSearch.getIn(path); if (!_immutable.default.List.isList(items)) { items = _immutable.default.List.of(items); } if (filter) { items = items.filter(filter); } const selectedItems = namedSearch.get('selected') || _immutable.default.Map(); let updatedSelectedItems; if (isSelected) { updatedSelectedItems = selectedItems.withMutations(map => items.reduce((updatedMap, item) => updatedMap.set(item.get('csid'), item), map)); } else { updatedSelectedItems = selectedItems.withMutations(map => items.reduce((updatedMap, item) => updatedMap.delete(item.get('csid')), map)); } return state.set(searchName, namedSearch.set('selected', updatedSelectedItems)); } return state; }; const handleSetResultItemSelected = (state, action) => { const isSelected = action.payload; const { listTypeConfig, searchName, searchDescriptor, index } = action.meta; const { listNodeName, itemNodeName } = listTypeConfig; const namedSearch = state.get(searchName); if (namedSearch) { const key = searchKey(searchDescriptor); const path = ['byKey', key, 'result', listNodeName, itemNodeName, index]; const item = (0, _recordDataHelpers.deepGet)(namedSearch, path); const csid = item.get('csid'); const updatedNamedSearch = isSelected ? namedSearch.setIn(['selected', csid], item) : namedSearch.deleteIn(['selected', csid]); return state.set(searchName, updatedNamedSearch); } return state; }; const clearFilteredResults = (state, filter) => { let nextState = state; state.filter(filter).forEach((searchState, searchName) => { nextState = nextState.delete(searchName); }); return nextState; }; const clearNamedResults = (state, action) => state.delete(action.meta.searchName); const clearAllResults = state => state.clear(); const handleRecordSaveFulfilled = state => { // We don't really know which search results will be affected by a record being created, so clear // them all, with the following exceptions: // - The search result page. We want to keep those results so that if the current record has a // search context, it won't be lost. Instead, the search result page results are only marked // as dirty, so that the next time the page is viewed, the results can be cleared and // reloaded at that point. // - Report and batch panels. These probably won't be affected. let nextState = state; nextState = clearFilteredResults(nextState, (searchState, searchName) => searchName !== _searchNames.SEARCH_RESULT_PAGE_SEARCH_NAME && searchName !== _searchNames.RECORD_BATCH_PANEL_SEARCH_NAME && searchName !== _searchNames.RECORD_REPORT_PANEL_SEARCH_NAME); const searchResultPageState = nextState.get(_searchNames.SEARCH_RESULT_PAGE_SEARCH_NAME); if (searchResultPageState) { nextState = nextState.set(_searchNames.SEARCH_RESULT_PAGE_SEARCH_NAME, searchResultPageState.set('isDirty', true)); } return nextState; }; const handleRecordDeleteFulfilled = state => clearAllResults(state); const handleRecordTransitionFulfilled = (state, action) => { const { transitionName } = action.meta; if (transitionName === 'delete') { return clearAllResults(state); } return state; }; const handleLoginFulfilled = (state, action) => { const { prevUsername, username } = action.meta; if (prevUsername !== username) { // The logged in user has changed. Remove all search state, because the new user may not be // permitted to list some records that the previous user could. return clearAllResults(state); } return state; }; var _default = (state = _immutable.default.Map(), action) => { switch (action.type) { case _actionCodes.BATCH_INVOKE_FULFILLED: if (action.meta.numAffected) { // We don't really know which search results were affected by the batch job invocation, so // clear them all. return clearAllResults(state); } return state; case _actionCodes.CLEAR_SELECTED: return state.deleteIn([action.meta.searchName, 'selected']); case _actionCodes.CLEAR_SEARCH_RESULTS: return clearNamedResults(state, action); case _actionCodes.SET_MOST_RECENT_SEARCH: return handleSetMostRecentSearch(state, action); case _actionCodes.CREATE_EMPTY_SEARCH_RESULT: return handleCreateEmptySearchResult(state, action); case _actionCodes.SEARCH_STARTED: return handleSearchStarted(state, action); case _actionCodes.SEARCH_FULFILLED: return handleSearchFulfilled(state, action); case _actionCodes.SEARCH_REJECTED: return handleSearchRejected(state, action); case _actionCodes.SET_ALL_RESULT_ITEMS_SELECTED: return handleSetAllResultItemsSelected(state, action); case _actionCodes.SET_RESULT_ITEM_SELECTED: return handleSetResultItemSelected(state, action); case _actionCodes.DESELECT_RESULT_ITEM: return state.deleteIn([action.meta.searchName, 'selected', action.meta.csid]); case _actionCodes.SUBRECORD_CREATED: return clearNamedResults(state, action); case _actionCodes.RECORD_SAVE_FULFILLED: return handleRecordSaveFulfilled(state, action); case _actionCodes.RECORD_DELETE_FULFILLED: return handleRecordDeleteFulfilled(state, action); case _actionCodes.RECORD_TRANSITION_FULFILLED: return handleRecordTransitionFulfilled(state, action); case _actionCodes.LOGIN_FULFILLED: return handleLoginFulfilled(state, action); case _actionCodes.LOGOUT_FULFILLED: return clearAllResults(state); default: return state; } }; exports.default = _default; const isDirty = (state, searchName) => state.getIn([searchName, 'isDirty']); exports.isDirty = isDirty; const isPending = (state, searchName, searchDescriptor) => state.getIn([searchName, 'byKey', searchKey(searchDescriptor), 'isPending']); exports.isPending = isPending; const getState = (state, searchName, searchDescriptor) => state.getIn([searchName, 'byKey', searchKey(searchDescriptor)]); exports.getState = getState; const getIndexesByCsid = (state, searchName, searchDescriptor) => state.getIn([searchName, 'byKey', searchKey(searchDescriptor), 'indexesByCsid']); exports.getIndexesByCsid = getIndexesByCsid; const getMostRecentDescriptor = (state, searchName) => state.getIn([searchName, 'byKey', state.getIn([searchName, 'mostRecentKey']), 'descriptor']); exports.getMostRecentDescriptor = getMostRecentDescriptor; const getResult = (state, searchName, searchDescriptor) => state.getIn([searchName, 'byKey', searchKey(searchDescriptor), 'result']); exports.getResult = getResult; const getError = (state, searchName, searchDescriptor) => state.getIn([searchName, 'byKey', searchKey(searchDescriptor), 'error']); exports.getError = getError; const getSelectedItems = (state, searchName) => state.getIn([searchName, 'selected']); exports.getSelectedItems = getSelectedItems;