2017-02-28 21:32:23 +00:00
|
|
|
import React, {PropTypes, PureComponent} from 'react';
|
2016-07-20 18:35:08 +00:00
|
|
|
import ReactDOM from 'react-dom';
|
|
|
|
import classnames from 'classnames';
|
2017-02-28 21:32:23 +00:00
|
|
|
import Button from '../base/Button';
|
2016-07-22 22:27:04 +00:00
|
|
|
import Modal from '../base/Modal';
|
|
|
|
import ModalHeader from '../base/ModalHeader';
|
|
|
|
import ModalBody from '../base/ModalBody';
|
2016-08-15 17:04:36 +00:00
|
|
|
import MethodTag from '../tags/MethodTag';
|
2016-11-10 05:56:23 +00:00
|
|
|
import * as models from '../../../models';
|
2016-07-20 18:35:08 +00:00
|
|
|
|
2016-07-06 20:18:26 +00:00
|
|
|
|
2017-02-28 21:32:23 +00:00
|
|
|
class RequestSwitcherModal extends PureComponent {
|
2016-11-26 07:33:55 +00:00
|
|
|
state = {
|
|
|
|
searchString: '',
|
|
|
|
requestGroups: [],
|
|
|
|
requests: [],
|
|
|
|
workspaces: [],
|
|
|
|
matchedRequests: [],
|
|
|
|
matchedWorkspaces: [],
|
|
|
|
activeIndex: -1
|
|
|
|
};
|
2016-07-20 18:35:08 +00:00
|
|
|
|
2017-02-28 21:32:23 +00:00
|
|
|
_setModalRef = n => this.modal = n;
|
|
|
|
_focusRef = n => n && n.focus();
|
|
|
|
|
2016-07-20 18:35:08 +00:00
|
|
|
_setActiveIndex (activeIndex) {
|
2016-09-10 18:30:50 +00:00
|
|
|
const maxIndex = this.state.matchedRequests.length + this.state.matchedWorkspaces.length;
|
2016-07-20 18:35:08 +00:00
|
|
|
if (activeIndex < 0) {
|
|
|
|
activeIndex = this.state.matchedRequests.length - 1;
|
2016-09-10 18:30:50 +00:00
|
|
|
} else if (activeIndex >= maxIndex) {
|
2016-07-20 18:35:08 +00:00
|
|
|
activeIndex = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.setState({activeIndex});
|
|
|
|
}
|
|
|
|
|
2017-02-28 21:32:23 +00:00
|
|
|
_activateCurrentIndex = () => {
|
2016-09-10 18:30:50 +00:00
|
|
|
const {
|
|
|
|
activeIndex,
|
|
|
|
matchedRequests,
|
|
|
|
matchedWorkspaces
|
|
|
|
} = this.state;
|
|
|
|
|
|
|
|
if (activeIndex < matchedRequests.length) {
|
2016-07-20 18:35:08 +00:00
|
|
|
// Activate the request if there is one
|
2016-09-10 18:30:50 +00:00
|
|
|
const request = matchedRequests[activeIndex];
|
2016-09-02 05:45:12 +00:00
|
|
|
this._activateRequest(request);
|
2016-09-10 18:30:50 +00:00
|
|
|
} 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);
|
2016-07-20 18:35:08 +00:00
|
|
|
} else {
|
2016-09-10 18:30:50 +00:00
|
|
|
// Create request if no match
|
|
|
|
this._createRequestFromSearch();
|
|
|
|
}
|
2017-02-28 21:32:23 +00:00
|
|
|
};
|
2016-07-20 18:35:08 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
async _createRequestFromSearch () {
|
2016-09-10 18:30:50 +00:00
|
|
|
const {activeRequestParentId} = this.props;
|
|
|
|
const {searchString} = this.state;
|
|
|
|
|
|
|
|
// Create the request if nothing matched
|
|
|
|
const patch = {
|
|
|
|
name: searchString,
|
|
|
|
parentId: activeRequestParentId
|
|
|
|
};
|
|
|
|
|
2016-11-10 01:15:27 +00:00
|
|
|
const request = await models.request.create(patch);
|
2016-10-02 20:57:00 +00:00
|
|
|
this._activateRequest(request);
|
2016-09-10 18:30:50 +00:00
|
|
|
}
|
|
|
|
|
2017-02-28 21:32:23 +00:00
|
|
|
_activateWorkspace = workspace => {
|
2016-09-10 18:30:50 +00:00
|
|
|
if (!workspace) {
|
|
|
|
return;
|
2016-07-20 18:35:08 +00:00
|
|
|
}
|
2016-09-10 18:30:50 +00:00
|
|
|
|
2016-11-16 17:18:39 +00:00
|
|
|
this.props.handleSetActiveWorkspace(workspace._id);
|
2016-09-10 18:30:50 +00:00
|
|
|
this.modal.hide();
|
2017-02-28 21:32:23 +00:00
|
|
|
};
|
2016-07-20 18:35:08 +00:00
|
|
|
|
2017-02-28 21:32:23 +00:00
|
|
|
_activateRequest = request => {
|
2016-07-20 18:35:08 +00:00
|
|
|
if (!request) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-11-25 23:09:17 +00:00
|
|
|
this.props.activateRequest(request._id);
|
2016-08-15 17:04:36 +00:00
|
|
|
this.modal.hide();
|
2017-02-28 21:32:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
_handleChange = e => this._handleChangeValue(e.target.value);
|
2016-07-20 18:35:08 +00:00
|
|
|
|
2017-02-28 21:32:23 +00:00
|
|
|
_handleChangeValue = async searchString => {
|
2017-01-24 06:06:40 +00:00
|
|
|
const {workspaceChildren, workspaces} = this.props;
|
2016-11-26 07:33:55 +00:00
|
|
|
const {workspaceId, activeRequestParentId} = this.props;
|
2016-07-21 22:18:29 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
// OPTIMIZATION: This only filters if we have a filter
|
2017-01-24 06:06:40 +00:00
|
|
|
let matchedRequests = workspaceChildren.filter(d => d.type === models.request.type);
|
2016-10-02 20:57:00 +00:00
|
|
|
if (searchString) {
|
|
|
|
matchedRequests = matchedRequests.filter(r => {
|
|
|
|
const name = r.name.toLowerCase();
|
2017-02-26 23:47:45 +00:00
|
|
|
const id = r._id.toLowerCase();
|
2016-10-02 20:57:00 +00:00
|
|
|
const toMatch = searchString.toLowerCase();
|
2017-02-26 23:47:45 +00:00
|
|
|
|
|
|
|
// Match substring of name
|
|
|
|
const matchesName = name.indexOf(toMatch) >= 0;
|
|
|
|
|
|
|
|
// Match exact Id
|
|
|
|
const matchesId = id === toMatch;
|
|
|
|
|
|
|
|
return matchesName || matchesId;
|
2016-10-02 20:57:00 +00:00
|
|
|
});
|
|
|
|
}
|
2016-07-22 17:02:42 +00:00
|
|
|
|
2017-01-25 20:50:35 +00:00
|
|
|
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;
|
2016-10-02 20:57:00 +00:00
|
|
|
} else {
|
2017-01-25 20:50:35 +00:00
|
|
|
return a.parentId > b.parentId ? -1 : 1;
|
2016-07-20 18:35:08 +00:00
|
|
|
}
|
2016-09-10 18:30:50 +00:00
|
|
|
}
|
2017-01-25 20:50:35 +00:00
|
|
|
}).slice(0, 20); // show 20 max
|
2016-10-02 20:57:00 +00:00
|
|
|
|
2017-01-25 20:50:35 +00:00
|
|
|
const matchedWorkspaces = workspaces
|
|
|
|
.filter(w => w._id !== workspaceId)
|
|
|
|
.filter(w => {
|
|
|
|
const name = w.name.toLowerCase();
|
|
|
|
const toMatch = searchString.toLowerCase();
|
2017-02-26 23:47:45 +00:00
|
|
|
return name.indexOf(toMatch) !== -1;
|
2017-01-25 20:50:35 +00:00
|
|
|
});
|
2016-09-10 18:30:50 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
const activeIndex = searchString ? 0 : -1;
|
2016-07-20 18:35:08 +00:00
|
|
|
|
2016-10-02 20:57:00 +00:00
|
|
|
this.setState({
|
|
|
|
activeIndex,
|
2016-11-26 07:33:55 +00:00
|
|
|
searchString,
|
2016-10-02 20:57:00 +00:00
|
|
|
matchedRequests,
|
|
|
|
matchedWorkspaces,
|
2016-07-20 18:35:08 +00:00
|
|
|
});
|
2017-02-28 21:32:23 +00:00
|
|
|
};
|
2016-07-20 18:35:08 +00:00
|
|
|
|
2016-11-26 07:33:55 +00:00
|
|
|
async show () {
|
2016-08-15 17:04:36 +00:00
|
|
|
this.modal.show();
|
2017-02-28 21:32:23 +00:00
|
|
|
this._handleChangeValue('');
|
2016-07-20 18:35:08 +00:00
|
|
|
}
|
|
|
|
|
2016-11-26 07:33:55 +00:00
|
|
|
async toggle () {
|
2016-08-15 17:04:36 +00:00
|
|
|
this.modal.toggle();
|
2017-02-28 21:32:23 +00:00
|
|
|
this._handleChangeValue('');
|
2016-07-20 18:35:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount () {
|
2016-08-15 17:04:36 +00:00
|
|
|
ReactDOM.findDOMNode(this).addEventListener('keydown', e => {
|
2016-07-20 18:35:08 +00:00
|
|
|
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();
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
render () {
|
2016-09-02 05:45:12 +00:00
|
|
|
const {
|
|
|
|
searchString,
|
2016-11-26 07:33:55 +00:00
|
|
|
activeIndex,
|
|
|
|
matchedRequests,
|
|
|
|
matchedWorkspaces
|
2016-09-02 05:45:12 +00:00
|
|
|
} = this.state;
|
2016-07-20 18:35:08 +00:00
|
|
|
|
2016-11-26 07:33:55 +00:00
|
|
|
const {workspaceChildren} = this.props;
|
|
|
|
const requestGroups = workspaceChildren.filter(d => d.type === models.requestGroup.type);
|
|
|
|
|
2016-07-06 20:18:26 +00:00
|
|
|
return (
|
2017-02-28 21:32:23 +00:00
|
|
|
<Modal ref={this._setModalRef} top={true} dontFocus={true}>
|
2016-07-20 18:35:08 +00:00
|
|
|
<ModalHeader hideCloseButton={true}>
|
2017-01-20 21:54:03 +00:00
|
|
|
<div className="pull-right txt-md pad-right">
|
2016-07-20 18:35:08 +00:00
|
|
|
<span className="monospace">tab</span> or
|
|
|
|
|
|
|
|
<span className="monospace">↑ ↓</span> to navigate
|
|
|
|
|
|
|
|
<span className="monospace">↵</span> to select
|
|
|
|
|
|
|
|
<span className="monospace">esc</span> to dismiss
|
2016-11-23 19:33:24 +00:00
|
|
|
</div>
|
|
|
|
<div>Quick Switch</div>
|
2016-07-20 18:35:08 +00:00
|
|
|
</ModalHeader>
|
|
|
|
<ModalBody className="pad request-switcher">
|
|
|
|
<div className="form-control form-control--outlined no-margin">
|
|
|
|
<input
|
|
|
|
type="text"
|
2017-02-28 21:32:23 +00:00
|
|
|
ref={this._focusRef}
|
2016-08-15 17:04:36 +00:00
|
|
|
value={searchString}
|
2017-02-28 21:32:23 +00:00
|
|
|
onChange={this._handleChange}
|
2016-07-20 18:35:08 +00:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<ul className="pad-top">
|
|
|
|
{matchedRequests.map((r, i) => {
|
2016-11-26 07:33:55 +00:00
|
|
|
const requestGroup = requestGroups.find(rg => rg._id === r.parentId);
|
2016-09-02 05:45:12 +00:00
|
|
|
const buttonClasses = classnames(
|
|
|
|
'btn btn--compact wide text-left',
|
|
|
|
{focus: activeIndex === i}
|
|
|
|
);
|
2016-07-20 18:35:08 +00:00
|
|
|
|
|
|
|
return (
|
|
|
|
<li key={r._id}>
|
2017-02-28 21:32:23 +00:00
|
|
|
<Button onClick={this._activateRequest} value={r} className={buttonClasses}>
|
2016-07-20 18:35:08 +00:00
|
|
|
{requestGroup ? (
|
2017-01-25 20:50:35 +00:00
|
|
|
<div className="pull-right faint italic">
|
|
|
|
{requestGroup.name}
|
|
|
|
|
|
|
|
<i className="fa fa-folder-o"></i>
|
|
|
|
</div>
|
|
|
|
) : null}
|
2016-07-20 18:35:08 +00:00
|
|
|
<MethodTag method={r.method}/>
|
|
|
|
<strong>{r.name}</strong>
|
2017-02-28 21:32:23 +00:00
|
|
|
</Button>
|
2016-07-20 18:35:08 +00:00
|
|
|
</li>
|
|
|
|
)
|
|
|
|
})}
|
2016-09-10 18:30:50 +00:00
|
|
|
|
2016-11-26 07:33:55 +00:00
|
|
|
{matchedRequests.length && matchedWorkspaces.length ? <hr/> : null}
|
2016-09-10 18:30:50 +00:00
|
|
|
|
|
|
|
{matchedWorkspaces.map((w, i) => {
|
|
|
|
const buttonClasses = classnames(
|
|
|
|
'btn btn--compact wide text-left',
|
|
|
|
{focus: (activeIndex - matchedRequests.length) === i}
|
|
|
|
);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<li key={w._id}>
|
2017-02-28 21:32:23 +00:00
|
|
|
<Button onClick={this._activateWorkspace} value={w} className={buttonClasses}>
|
2016-09-10 18:42:23 +00:00
|
|
|
<i className="fa fa-random"></i>
|
|
|
|
|
|
|
|
Switch to <strong>{w.name}</strong>
|
2017-02-28 21:32:23 +00:00
|
|
|
</Button>
|
2016-09-10 18:30:50 +00:00
|
|
|
</li>
|
|
|
|
)
|
|
|
|
})}
|
2016-07-20 18:35:08 +00:00
|
|
|
</ul>
|
|
|
|
|
2016-09-10 18:30:50 +00:00
|
|
|
{!matchedRequests.length && !matchedWorkspaces.length ? (
|
2017-01-25 20:50:35 +00:00
|
|
|
<div className="text-center">
|
|
|
|
<p>
|
|
|
|
No matches found for <strong>{searchString}</strong>
|
|
|
|
</p>
|
|
|
|
|
|
|
|
<button className="btn btn--outlined btn--compact"
|
|
|
|
disabled={!searchString}
|
2017-02-28 21:32:23 +00:00
|
|
|
onClick={this._activateCurrentIndex}>
|
2017-01-25 20:50:35 +00:00
|
|
|
Create a request named {searchString}
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
) : null}
|
2016-07-06 20:18:26 +00:00
|
|
|
</ModalBody>
|
|
|
|
</Modal>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-20 18:35:08 +00:00
|
|
|
RequestSwitcherModal.propTypes = {
|
2016-11-16 17:18:39 +00:00
|
|
|
handleSetActiveWorkspace: PropTypes.func.isRequired,
|
2016-07-20 18:35:08 +00:00
|
|
|
activateRequest: PropTypes.func.isRequired,
|
2016-07-20 19:09:21 +00:00
|
|
|
workspaceId: PropTypes.string.isRequired,
|
2016-11-26 07:33:55 +00:00
|
|
|
activeRequestParentId: PropTypes.string.isRequired,
|
|
|
|
workspaceChildren: PropTypes.arrayOf(PropTypes.object).isRequired,
|
2017-01-24 06:06:40 +00:00
|
|
|
workspaces: PropTypes.arrayOf(PropTypes.object).isRequired,
|
2016-07-20 18:35:08 +00:00
|
|
|
};
|
2016-07-06 20:18:26 +00:00
|
|
|
|
|
|
|
export default RequestSwitcherModal;
|