Cleaned up request switcher

This commit is contained in:
Gregory Schier 2018-03-29 11:40:37 -04:00
parent e17332c7de
commit 6493f57e3b
4 changed files with 101 additions and 79 deletions

View File

@ -1,8 +1,7 @@
import React, {PureComponent} from 'react'; // @flow
import PropTypes from 'prop-types'; import * as React from 'react';
import autobind from 'autobind-decorator'; import autobind from 'autobind-decorator';
import classnames from 'classnames'; import classnames from 'classnames';
import {buildQueryStringFromParams, joinUrlAndQueryString} from 'insomnia-url';
import Button from '../base/button'; import Button from '../base/button';
import Modal from '../base/modal'; import Modal from '../base/modal';
import ModalHeader from '../base/modal-header'; import ModalHeader from '../base/modal-header';
@ -10,10 +9,35 @@ import ModalBody from '../base/modal-body';
import MethodTag from '../tags/method-tag'; import MethodTag from '../tags/method-tag';
import * as models from '../../../models'; import * as models from '../../../models';
import {fuzzyMatchAll} from '../../../common/misc'; import {fuzzyMatchAll} from '../../../common/misc';
import type {RequestGroup} from '../../../models/request-group';
import type {Request} from '../../../models/request';
import type {Workspace} from '../../../models/workspace';
type Props = {
handleSetActiveWorkspace: (id: string) => void,
activateRequest: (id: string) => void,
workspaceId: string,
activeRequestParentId: string,
workspaceChildren: Array<Request | RequestGroup>,
workspaces: Array<Workspace>
};
type State = {
searchString: string,
requestGroups: Array<RequestGroup>,
requests: Array<Request>,
workspaces: Array<Workspace>,
matchedRequests: Array<Request>,
matchedWorkspaces: Array<Workspace>,
activeIndex: number
};
@autobind @autobind
class RequestSwitcherModal extends PureComponent { class RequestSwitcherModal extends React.PureComponent<Props, State> {
constructor (props) { modal: ?Modal;
_input: ?HTMLInputElement;
constructor (props: Props) {
super(props); super(props);
this.state = { this.state = {
@ -27,7 +51,7 @@ class RequestSwitcherModal extends PureComponent {
}; };
} }
_handleKeydown (e) { _handleKeydown (e: KeyboardEvent) {
const keyCode = e.keyCode; const keyCode = e.keyCode;
if (keyCode === 38 || (keyCode === 9 && e.shiftKey)) { if (keyCode === 38 || (keyCode === 9 && e.shiftKey)) {
@ -46,15 +70,15 @@ class RequestSwitcherModal extends PureComponent {
e.preventDefault(); e.preventDefault();
} }
_setModalRef (n) { _setModalRef (n: ?Modal) {
this.modal = n; this.modal = n;
} }
_setInputRef (n) { _setInputRef (n: ?HTMLInputElement) {
this._input = n; this._input = n;
} }
_setActiveIndex (activeIndex) { _setActiveIndex (activeIndex: number) {
const maxIndex = this.state.matchedRequests.length + this.state.matchedWorkspaces.length; const maxIndex = this.state.matchedRequests.length + this.state.matchedWorkspaces.length;
if (activeIndex < 0) { if (activeIndex < 0) {
activeIndex = this.state.matchedRequests.length - 1; activeIndex = this.state.matchedRequests.length - 1;
@ -80,7 +104,9 @@ class RequestSwitcherModal extends PureComponent {
// Activate the workspace if there is one // Activate the workspace if there is one
const index = activeIndex - matchedRequests.length; const index = activeIndex - matchedRequests.length;
const workspace = matchedWorkspaces[index]; const workspace = matchedWorkspaces[index];
this._activateWorkspace(workspace); if (workspace) {
this._activateWorkspace(workspace);
}
} else { } else {
// Create request if no match // Create request if no match
this._createRequestFromSearch(); this._createRequestFromSearch();
@ -101,66 +127,64 @@ class RequestSwitcherModal extends PureComponent {
this._activateRequest(request); this._activateRequest(request);
} }
_activateWorkspace (workspace) { _activateWorkspace (workspace: Workspace) {
if (!workspace) {
return;
}
this.props.handleSetActiveWorkspace(workspace._id); this.props.handleSetActiveWorkspace(workspace._id);
this.modal.hide(); this.modal && this.modal.hide();
} }
_activateRequest (request) { _activateRequest (request: ?Request) {
if (!request) { if (!request) {
return; return;
} }
this.props.activateRequest(request._id); this.props.activateRequest(request._id);
this.modal.hide(); this.modal && this.modal.hide();
} }
_handleChange (e) { _handleChange (e: SyntheticEvent<HTMLInputElement>) {
this._handleChangeValue(e.target.value); this._handleChangeValue(e.currentTarget.value);
} }
/** /** Return array of path segments for given request or folder */
* Appends path of ancestor groups, delimited by forward slashes _groupOf (requestOrRequestGroup: Request | RequestGroup): Array<string> {
* E.g. Folder1/Folder2/Folder3
*/
_groupOf (requestOrRequestGroup) {
const {workspaceChildren} = this.props; const {workspaceChildren} = this.props;
const requestGroups = workspaceChildren.filter(d => d.type === models.requestGroup.type); const requestGroups = workspaceChildren.filter(d => d.type === models.requestGroup.type);
const matchedGroups = requestGroups.filter(g => g._id === requestOrRequestGroup.parentId); const matchedGroups = requestGroups.filter(g => g._id === requestOrRequestGroup.parentId);
const currentGroupName = requestOrRequestGroup.type === models.requestGroup.type && requestOrRequestGroup.name ? `${requestOrRequestGroup.name}` : ''; const currentGroupName = requestOrRequestGroup.type === models.requestGroup.type
? `${requestOrRequestGroup.name}`
: '';
// It's the final parent
if (matchedGroups.length === 0) { if (matchedGroups.length === 0) {
return currentGroupName; return [currentGroupName];
} }
const parentGroup = this._groupOf(matchedGroups[0]); // Still has more parents
const parentGroupText = parentGroup ? `${parentGroup}/` : ''; if (currentGroupName) {
const group = `${parentGroupText}${currentGroupName}`; return [currentGroupName, ...this._groupOf(matchedGroups[0])];
}
return group; // It's the child
return this._groupOf(matchedGroups[0]);
} }
_isMatch (searchStrings) { _isMatch (searchStrings: string): (Request) => boolean {
return (request) => { return (request: Request): boolean => {
let finalUrl = request.url; // Disable URL filtering until we have proper UI to show this
if (request.parameters) { // let finalUrl = request.url;
finalUrl = joinUrlAndQueryString( // if (request.parameters) {
finalUrl, // finalUrl = joinUrlAndQueryString(
buildQueryStringFromParams(request.parameters)); // finalUrl,
} // buildQueryStringFromParams(request.parameters));
// }
// Match request attributes // Match request attributes
const matchesAttributes = fuzzyMatchAll(searchStrings, const matchesAttributes = fuzzyMatchAll(searchStrings, [
[ request.name,
request.name, // finalUrl,
finalUrl, // request.method,
request.method, this._groupOf(request).join('/')
this._groupOf(request) ]);
]);
// Match exact Id // Match exact Id
const matchesId = request._id === searchStrings; const matchesId = request._id === searchStrings;
@ -169,7 +193,7 @@ class RequestSwitcherModal extends PureComponent {
}; };
} }
async _handleChangeValue (searchString) { async _handleChangeValue (searchString: string) {
const {workspaceChildren, workspaces} = this.props; const {workspaceChildren, workspaces} = this.props;
const {workspaceId, activeRequestParentId} = this.props; const {workspaceId, activeRequestParentId} = this.props;
@ -218,16 +242,18 @@ class RequestSwitcherModal extends PureComponent {
async show () { async show () {
await this._handleChangeValue(''); await this._handleChangeValue('');
this.modal.show(); this.modal && this.modal.show();
setTimeout(() => this._input.focus(), 100); setTimeout(() => {
this._input && this._input.focus();
}, 100);
} }
hide () { hide () {
this.modal.hide(); this.modal && this.modal.hide();
} }
toggle () { toggle () {
if (this.modal.isOpen()) { if (this.modal && this.modal.isOpen()) {
this.hide(); this.hide();
} else { } else {
this.show(); this.show();
@ -248,14 +274,15 @@ class RequestSwitcherModal extends PureComponent {
return ( return (
<Modal ref={this._setModalRef} dontFocus tall> <Modal ref={this._setModalRef} dontFocus tall>
<ModalHeader hideCloseButton> <ModalHeader hideCloseButton>
<div className="pull-right txt-sm pad-right"> <div className="pull-right txt-sm pad-right tall">
<span className="monospace">tab</span> or <span className="vertically-center">
&nbsp; <div>
<span className="monospace"> </span> &nbsp;to navigate <span className="monospace">tab</span> or&nbsp;
&nbsp;&nbsp;&nbsp; <span className="monospace"></span> to navigate&nbsp;&nbsp;&nbsp;&nbsp;
<span className="monospace"></span> &nbsp;to select <span className="monospace"></span> &nbsp;to select&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp; <span className="monospace">esc</span> to dismiss
<span className="monospace">esc</span> to dismiss </div>
</span>
</div> </div>
<div>Quick Switch</div> <div>Quick Switch</div>
</ModalHeader> </ModalHeader>
@ -264,7 +291,7 @@ class RequestSwitcherModal extends PureComponent {
<div className="form-control form-control--outlined no-margin"> <div className="form-control form-control--outlined no-margin">
<input <input
type="text" type="text"
placeholder="Fuzzy filter by request name, folder, url, method, or query parameters" placeholder="Filter by name or folder"
ref={this._setInputRef} ref={this._setInputRef}
value={searchString} value={searchString}
onChange={this._handleChange} onChange={this._handleChange}
@ -284,7 +311,7 @@ class RequestSwitcherModal extends PureComponent {
<Button onClick={this._activateRequest} value={r} className={buttonClasses}> <Button onClick={this._activateRequest} value={r} className={buttonClasses}>
{requestGroup && ( {requestGroup && (
<div className="pull-right faint italic"> <div className="pull-right faint italic">
{requestGroup.name} {this._groupOf(r).join(' / ')}
&nbsp;&nbsp; &nbsp;&nbsp;
<i className="fa fa-folder-o"/> <i className="fa fa-folder-o"/>
</div> </div>
@ -339,13 +366,4 @@ class RequestSwitcherModal extends PureComponent {
} }
} }
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; export default RequestSwitcherModal;

View File

@ -52,7 +52,7 @@ class SidebarFilter extends PureComponent {
<input <input
ref={this._setInputRef} ref={this._setInputRef}
type="text" type="text"
placeholder="Filter by name, folder, url" placeholder="Filter"
defaultValue={this.props.filter} defaultValue={this.props.filter}
onChange={this._handleOnChange} onChange={this._handleOnChange}
/> />

View File

@ -113,13 +113,16 @@ export const selectSidebarChildren = createSelector(
const hasMatchedChildren = child.children.find(c => c.hidden === false); const hasMatchedChildren = child.children.find(c => c.hidden === false);
// Try to match request attributes // Try to match request attributes
const {name, url, method, parameters} = child.doc; const {name, method} = child.doc;
// Don't use URL/parameters yet until we have UI to show it
// const {name, url, method, parameters} = child.doc;
const hasMatchedAttributes = fuzzyMatchAll(sidebarFilter, [ const hasMatchedAttributes = fuzzyMatchAll(sidebarFilter, [
name, name,
url, // url,
method, method,
...(parameters ? parameters.map(p => `${p.name}=${p.value}`) : []), // ...(parameters ? parameters.map(p => `${p.name}=${p.value}`) : []),
...parentNames ...parentNames
]); ]);
@ -191,11 +194,7 @@ export const selectUnseenWorkspaces = createSelector(
const {workspaces, workspaceMetas} = entities; const {workspaces, workspaceMetas} = entities;
return workspaces.filter(workspace => { return workspaces.filter(workspace => {
const meta = workspaceMetas.find(m => m.parentId === workspace._id); const meta = workspaceMetas.find(m => m.parentId === workspace._id);
if (meta && !meta.hasSeen) { return !!(meta && !meta.hasSeen);
return true;
} else {
return false;
}
}); });
} }
); );

View File

@ -0,0 +1,5 @@
declare module 'reselect' {
declare module.exports: {
createSelector: Function
}
}