import React, {PropTypes, PureComponent} from 'react'; import autobind from 'autobind-decorator'; import ReactDOM from 'react-dom'; import classnames from 'classnames'; import Button from '../base/button'; import Modal from '../base/modal'; import ModalHeader from '../base/modal-header'; import ModalBody from '../base/modal-body'; import MethodTag from '../tags/method-tag'; import * as models from '../../../models'; @autobind class RequestSwitcherModal extends PureComponent { constructor (props) { super(props); this.state = { searchString: '', requestGroups: [], requests: [], workspaces: [], matchedRequests: [], matchedWorkspaces: [], activeIndex: -1 }; } _handleKeydown (e) { const keyCode = e.keyCode; if (keyCode === 38 || (keyCode === 9 && e.shiftKey)) { // Up or Shift+Tab this._setActiveIndex(this.state.activeIndex - 1); } else if (keyCode === 40 || keyCode === 9) { // Down or Tab this._setActiveIndex(this.state.activeIndex + 1); } else if (keyCode === 13) { // Enter this._activateCurrentIndex(); } else { return; } e.preventDefault(); } _setModalRef (n) { this.modal = n; } _setInputRef (n) { this._input = n; } _setActiveIndex (activeIndex) { const maxIndex = this.state.matchedRequests.length + this.state.matchedWorkspaces.length; if (activeIndex < 0) { activeIndex = this.state.matchedRequests.length - 1; } else if (activeIndex >= maxIndex) { activeIndex = 0; } this.setState({activeIndex}); } _activateCurrentIndex () { const { activeIndex, matchedRequests, matchedWorkspaces } = this.state; if (activeIndex < matchedRequests.length) { // Activate the request if there is one const request = matchedRequests[activeIndex]; this._activateRequest(request); } else if (activeIndex < (matchedRequests.length + matchedWorkspaces.length)) { // Activate the workspace if there is one const index = activeIndex - matchedRequests.length; const workspace = matchedWorkspaces[index]; this._activateWorkspace(workspace); } else { // Create request if no match this._createRequestFromSearch(); } } async _createRequestFromSearch () { const {activeRequestParentId} = this.props; const {searchString} = this.state; // Create the request if nothing matched const patch = { name: searchString, parentId: activeRequestParentId }; const request = await models.request.create(patch); this._activateRequest(request); } _activateWorkspace (workspace) { if (!workspace) { return; } this.props.handleSetActiveWorkspace(workspace._id); this.modal.hide(); } _activateRequest (request) { if (!request) { return; } this.props.activateRequest(request._id); this.modal.hide(); } _handleChange (e) { this._handleChangeValue(e.target.value); } async _handleChangeValue (searchString) { const {workspaceChildren, workspaces} = this.props; const {workspaceId, activeRequestParentId} = this.props; // OPTIMIZATION: This only filters if we have a filter let matchedRequests = workspaceChildren.filter(d => d.type === models.request.type); if (searchString) { const specialCharacters = ['.', '^', '$', '*', '+', '-', '?', '(', ')', '[', ']', '{', '}', '\\', '|']; const regexSearchString = searchString .toLowerCase() .split('') .map((c) => specialCharacters.includes(c) ? `\\${c}` : c) .join('.*'); const toMatch = new RegExp(regexSearchString); matchedRequests = matchedRequests.filter(r => { const name = r.name.toLowerCase(); const id = r._id.toLowerCase(); // Fuzzy match searchString to name const matchesName = toMatch.test(name); // Match exact Id const matchesId = id === toMatch; return matchesName || matchesId; }); } matchedRequests = matchedRequests.sort((a, b) => { if (a.parentId === b.parentId) { // Sort Requests by name inside of the same parent // TODO: Sort by quality of match (eg. start vs mid string, etc) return a.name > b.name ? 1 : -1; } else { // Sort RequestGroups by relevance if Request isn't in same parent if (a.parentId === activeRequestParentId) { return -1; } else if (b.parentId === activeRequestParentId) { return 1; } else { return a.parentId > b.parentId ? -1 : 1; } } }).slice(0, 20); // show 20 max const matchedWorkspaces = workspaces .filter(w => w._id !== workspaceId) .filter(w => { const name = w.name.toLowerCase(); const toMatch = searchString.toLowerCase(); return name.indexOf(toMatch) !== -1; }); const activeIndex = searchString ? 0 : -1; this.setState({ activeIndex, searchString, matchedRequests, matchedWorkspaces }); } async show () { this.modal.show(); await this._handleChangeValue(''); this._input.focus(); } hide () { this.modal.hide(); } toggle () { if (this.modal.isOpen()) { this.hide(); } else { this.show(); } } render () { const { searchString, activeIndex, matchedRequests, matchedWorkspaces } = this.state; const {workspaceChildren} = this.props; const requestGroups = workspaceChildren.filter(d => d.type === models.requestGroup.type); return (
tab or   ↑ ↓  to navigate      to select     esc to dismiss
Quick Switch
{(matchedRequests.length === 0 && matchedWorkspaces.length === 0) && (

No matches found for {searchString}

)}
); } } RequestSwitcherModal.propTypes = { handleSetActiveWorkspace: PropTypes.func.isRequired, activateRequest: PropTypes.func.isRequired, workspaceId: PropTypes.string.isRequired, activeRequestParentId: PropTypes.string.isRequired, workspaceChildren: PropTypes.arrayOf(PropTypes.object).isRequired, workspaces: PropTypes.arrayOf(PropTypes.object).isRequired }; export default RequestSwitcherModal;