Add workspace switching to quick switcher

This commit is contained in:
Gregory Schier 2016-09-10 11:30:50 -07:00
parent 0aec888c2e
commit ccbbda274d
4 changed files with 124 additions and 40 deletions

View File

@ -36,14 +36,20 @@ class Dropdown extends Component {
}
componentDidMount () {
ReactDOM.findDOMNode(this).addEventListener('keydown', e => {
this._bodyKeydownHandler = e => {
if (this.state.open && e.keyCode === 27) {
e.preventDefault();
e.stopPropagation();
// Pressed escape
this.hide();
}
});
};
document.body.addEventListener('keydown', this._bodyKeydownHandler);
}
componentWillUnmount () {
document.body.removeEventListener('keydown', this._bodyKeydownHandler);
}
render () {
@ -62,8 +68,7 @@ class Dropdown extends Component {
return (
<div className={classes}
onClick={this._handleClick.bind(this)}
onMouseDown={e => e.preventDefault()}
ref={n => this._node = n}>
onMouseDown={e => e.preventDefault()}>
{this.props.children}
<div className="dropdown__backdrop"></div>
</div>

View File

@ -143,9 +143,7 @@ class CookiesModal extends Component {
}
}
CookiesModal.propTypes = {
onChange: PropTypes.func.isRequired
};
CookiesModal.propTypes = {};
// export CookiesModal;
export default CookiesModal;

View File

@ -14,15 +14,17 @@ class RequestSwitcherModal extends Component {
this.state = {
searchString: '',
matchedRequests: [],
matchedWorkspaces: [],
requestGroups: [],
activeIndex: -1
}
}
_setActiveIndex (activeIndex) {
const maxIndex = this.state.matchedRequests.length + this.state.matchedWorkspaces.length;
if (activeIndex < 0) {
activeIndex = this.state.matchedRequests.length - 1;
} else if (activeIndex >= this.state.matchedRequests.length) {
} else if (activeIndex >= maxIndex) {
activeIndex = 0;
}
@ -30,21 +32,51 @@ class RequestSwitcherModal extends Component {
}
_activateCurrentIndex () {
if (this.state.matchedRequests.length) {
// Activate the request if there is one
const request = this.state.matchedRequests[this.state.activeIndex];
this._activateRequest(request);
} else {
// Create the request if nothing matched
const name = this.state.searchString;
const parentId = this.props.activeRequestParentId;
const {
activeIndex,
matchedRequests,
matchedWorkspaces
} = this.state;
db.requestCreate({name, parentId}).then(request => {
this._activateRequest(request);
});
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();
}
}
_createRequestFromSearch () {
const {activeRequestParentId} = this.props;
const {searchString} = this.state;
// Create the request if nothing matched
const patch = {
name: searchString,
parentId: activeRequestParentId
};
db.requestCreate(patch).then(request => {
this._activateRequest(request);
});
}
_activateWorkspace (workspace) {
if (!workspace) {
return;
}
this.props.activateWorkspace(workspace);
this.modal.hide();
}
_activateRequest (request) {
if (!request) {
return;
@ -59,10 +91,12 @@ class RequestSwitcherModal extends Component {
Promise.all([
db.requestAll(),
db.requestGroupAll()
db.requestGroupAll(),
db.workspaceAll()
]).then(([
allRequests,
allRequestGroups
allRequestGroups,
allWorkspaces
]) => {
// TODO: Support nested RequestGroups
// Filter out RequestGroups that don't belong to this Workspace
@ -82,9 +116,14 @@ class RequestSwitcherModal extends Component {
const parentId = this.props.activeRequestParentId;
// OPTIMIZATION: This only filters if we have a filter
let matchedRequests = !searchString ? requests : requests.filter(
r => r.name.toLowerCase().indexOf(searchString.toLowerCase()) !== -1
);
let matchedRequests = requests;
if (searchString) {
matchedRequests = matchedRequests.filter(r => {
const name = r.name.toLowerCase();
const toMatch = searchString.toLowerCase();
return name.indexOf(toMatch) !== -1
});
}
// OPTIMIZATION: Apply sort after the filter so we have to sort less
matchedRequests = matchedRequests.sort(
@ -107,11 +146,24 @@ class RequestSwitcherModal extends Component {
}
);
let matchedWorkspaces = [];
if (searchString) {
// Only match workspaces if there is a search
matchedWorkspaces = allWorkspaces
.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,
matchedRequests,
matchedWorkspaces,
requestGroups,
searchString
});
@ -152,6 +204,7 @@ class RequestSwitcherModal extends Component {
render () {
const {
matchedRequests,
matchedWorkspaces,
requestGroups,
searchString,
activeIndex
@ -170,7 +223,7 @@ class RequestSwitcherModal extends Component {
&nbsp;&nbsp;&nbsp;
<span className="monospace">esc</span> to dismiss
</p>
<p>Jump To Request</p>
<p>Quick Switch</p>
</ModalHeader>
<ModalBody className="pad request-switcher">
<div className="form-control form-control--outlined no-margin">
@ -208,9 +261,31 @@ class RequestSwitcherModal extends Component {
</li>
)
})}
{matchedRequests.length && matchedWorkspaces.length ? (
<hr/>
) : null}
{matchedWorkspaces.map((w, i) => {
const buttonClasses = classnames(
'btn btn--compact wide text-left',
{focus: (activeIndex - matchedRequests.length) === i}
);
return (
<li key={w._id}>
<button onClick={e => this._activateRequest(w)}
className={buttonClasses}>
<i className="fa fa-random"></i> Switch to
{" "}
<strong>{w.name}</strong>
</button>
</li>
)
})}
</ul>
{matchedRequests.length === 0 ? (
{!matchedRequests.length && !matchedWorkspaces.length ? (
<div className="text-center">
<p>
No matches found for <strong>{searchString}</strong>
@ -229,6 +304,7 @@ class RequestSwitcherModal extends Component {
RequestSwitcherModal.propTypes = {
activateRequest: PropTypes.func.isRequired,
activateWorkspace: PropTypes.func.isRequired,
workspaceId: PropTypes.string.isRequired,
activeRequestParentId: PropTypes.string.isRequired
};

View File

@ -5,9 +5,7 @@ import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import HTML5Backend from 'react-dnd-html5-backend';
import {DragDropContext} from 'react-dnd';
import Mousetrap from '../lib/mousetrap';
import {addModal} from '../components/modals';
import WorkspaceEnvironmentsEditModal from '../components/modals/WorkspaceEnvironmentsEditModal';
import CookiesModal from '../components/modals/CookiesModal';
@ -23,21 +21,21 @@ import ResponsePane from '../components/ResponsePane';
import Sidebar from '../components/sidebar/Sidebar';
import {PREVIEW_MODE_FRIENDLY} from '../lib/previewModes';
import {
MAX_PANE_WIDTH, MIN_PANE_WIDTH,
MAX_PANE_WIDTH,
MIN_PANE_WIDTH,
DEFAULT_PANE_WIDTH,
MAX_SIDEBAR_REMS,
MIN_SIDEBAR_REMS,
DEFAULT_SIDEBAR_WIDTH
} from '../lib/constants'
DEFAULT_SIDEBAR_WIDTH,
CHECK_FOR_UPDATES_INTERVAL
} from '../lib/constants';
import * as GlobalActions from '../redux/modules/global';
import * as RequestActions from '../redux/modules/requests';
import * as WorkspaceActions from '../redux/modules/workspaces';
import * as db from '../database';
import {importCurl} from '../lib/export/curl';
import {trackEvent} from '../lib/analytics';
import {getAppVersion} from '../lib/appInfo';
import {CHECK_FOR_UPDATES_INTERVAL} from '../lib/constants';
import {getModal} from '../components/modals/index';
@ -473,7 +471,8 @@ class App extends Component {
const gridTemplateColumns = `${sidebarWidth}rem 0 ${paneWidth}fr 0 ${1 - paneWidth}fr`;
return (
<div id="wrapper" className="wrapper" style={{gridTemplateColumns: gridTemplateColumns}}>
<div id="wrapper" className="wrapper"
style={{gridTemplateColumns: gridTemplateColumns}}>
<Sidebar
ref={n => this._sidebar = n}
showEnvironmentsModal={() => getModal(WorkspaceEnvironmentsEditModal).show(workspace)}
@ -494,7 +493,10 @@ class App extends Component {
/>
<div className="drag drag--sidebar">
<div onMouseDown={e => {e.preventDefault(); this._startDragSidebar()}}
<div onMouseDown={e => {
e.preventDefault();
this._startDragSidebar()
}}
onDoubleClick={() => this._resetDragSidebar()}>
</div>
</div>
@ -548,6 +550,7 @@ class App extends Component {
workspaceId={workspace._id}
activeRequestParentId={activeRequest ? activeRequest.parentId : workspace._id}
activateRequest={r => db.workspaceUpdate(workspace, {metaActiveRequestId: r._id})}
activateWorkspace={w => actions.workspaces.activate(w)}
/>
<EnvironmentEditModal
ref={m => addModal(m)}
@ -555,9 +558,7 @@ class App extends Component {
<WorkspaceEnvironmentsEditModal
ref={m => addModal(m)}
onChange={w => db.workspaceUpdate(w)}/>
<CookiesModal
ref={m => addModal(m)}
onChange={() => console.log('TODO: COOKIES!!!')}/>
<CookiesModal ref={m => addModal(m)}/>
{/*<div className="toast toast--show">*/}
{/*<div className="toast__message">How's it going?</div>*/}
@ -574,6 +575,9 @@ App.propTypes = {
requests: PropTypes.shape({
send: PropTypes.func.isRequired
}).isRequired,
workspaces: PropTypes.shape({
activate: PropTypes.func.isRequired
}).isRequired,
global: PropTypes.shape({
importFile: PropTypes.func.isRequired
}).isRequired
@ -604,7 +608,8 @@ function mapDispatchToProps (dispatch) {
return {
actions: {
global: bindActionCreators(GlobalActions, dispatch),
requests: bindActionCreators(RequestActions, dispatch)
requests: bindActionCreators(RequestActions, dispatch),
workspaces: bindActionCreators(WorkspaceActions, dispatch)
}
}
}