Merge branch 'develop' of github.com:getinsomnia/insomnia into develop

This commit is contained in:
Gregory Schier
2017-09-17 16:07:36 +02:00
27 changed files with 943 additions and 479 deletions

265
app/common/hotkeys.js Normal file
View File

@@ -0,0 +1,265 @@
// @flow
import keycodes from './keycodes';
import {isMac} from './constants';
import {trackEvent} from '../analytics/index';
export type Hotkey = {
description: string,
meta: boolean,
alt: boolean,
shift: boolean,
keycode: number | Array<number>
};
export const SHOW_WORKSPACE_SETTINGS: Hotkey = {
description: 'Show Workspace Settings',
meta: true,
alt: false,
shift: true,
keycode: keycodes.comma
};
export const SHOW_REQUEST_SETTINGS: Hotkey = {
description: 'Show Request Settings',
meta: true,
alt: true,
shift: true,
keycode: keycodes.comma
};
export const SHOW_SETTINGS: Hotkey = {
description: 'Show App Preferences',
meta: true,
alt: false,
shift: false,
keycode: keycodes.comma
};
export const TOGGLE_MAIN_MENU: Hotkey = {
description: 'Toggle Main Menu',
meta: true,
alt: true,
shift: false,
keycode: keycodes.comma
};
export const TOGGLE_SIDEBAR: Hotkey = {
description: 'Toggle Sidebar',
meta: true,
alt: false,
shift: false,
keycode: keycodes.backslash
};
export const SHOW_QUICK_SWITCHER: Hotkey = {
description: 'Switch Requests',
meta: true,
alt: false,
shift: false,
keycode: keycodes.p
};
export const SHOW_AUTOCOMPLETE: Hotkey = {
description: 'Show Autocomplete',
meta: true,
alt: false,
shift: false,
keycode: keycodes.space
};
export const SEND_REQUEST: Hotkey = {
description: 'Send Request',
meta: true,
alt: false,
shift: false,
keycode: [keycodes.enter, keycodes.r]
};
export const SEND_REQUEST_F5: Hotkey = {
description: 'Send Request',
meta: false,
alt: false,
shift: false,
keycode: keycodes.f5
};
export const SHOW_SEND_OPTIONS: Hotkey = {
description: 'Send Request (Options)',
meta: true,
alt: false,
shift: true,
keycode: keycodes.enter
};
export const SHOW_ENVIRONMENTS: Hotkey = {
description: 'Show Environment Editor',
meta: true,
alt: false,
shift: false,
keycode: keycodes.e
};
export const TOGGLE_ENVIRONMENTS_MENU: Hotkey = {
description: 'Switch Environments',
meta: true,
alt: false,
shift: true,
keycode: keycodes.e
};
export const TOGGLE_METHOD_DROPDOWN: Hotkey = {
description: 'Change HTTP Method',
meta: true,
alt: false,
shift: true,
keycode: keycodes.l
};
export const TOGGLE_HISTORY_DROPDOWN: Hotkey = {
description: 'Show Request History',
meta: true,
alt: false,
shift: true,
keycode: keycodes.h
};
export const FOCUS_URL: Hotkey = {
description: 'Focus URL',
meta: true,
alt: false,
shift: false,
keycode: keycodes.l
};
export const GENERATE_CODE: Hotkey = {
description: 'Generate Code',
meta: true,
alt: false,
shift: true,
keycode: keycodes.g
};
export const FOCUS_FILTER: Hotkey = {
description: 'Filter Sidebar',
meta: true,
alt: false,
shift: true,
keycode: keycodes.f
};
export const SHOW_COOKIES: Hotkey = {
description: 'Edit Cookies',
meta: true,
alt: false,
shift: false,
keycode: keycodes.k
};
export const CREATE_REQUEST: Hotkey = {
description: 'Create Request',
meta: true,
alt: false,
shift: false,
keycode: keycodes.n
};
export const CREATE_FOLDER: Hotkey = {
description: 'Create Folder',
meta: true,
alt: false,
shift: true,
keycode: keycodes.n
};
export const DUPLICATE_REQUEST: Hotkey = {
description: 'Duplicate Request',
meta: true,
alt: false,
shift: false,
keycode: keycodes.d
};
export const CLOSE_DROPDOWN: Hotkey = {
description: 'Close Dropdown',
meta: false,
alt: false,
shift: false,
keycode: keycodes.esc
};
export const CLOSE_MODAL: Hotkey = {
description: 'Close Modal',
meta: false,
alt: false,
shift: false,
keycode: keycodes.esc
};
export function pressedHotKey (e: KeyboardEvent, definition: Hotkey): boolean {
const isMetaPressed = isMac() ? e.metaKey : e.ctrlKey;
const isAltPressed = isMac() ? e.ctrlKey : e.altKey;
const isShiftPressed = e.shiftKey;
const {meta, alt, shift, keycode} = definition;
const codes = Array.isArray(keycode) ? keycode : [keycode];
for (const code of codes) {
if ((alt && !isAltPressed) || (!alt && isAltPressed)) {
continue;
}
if ((meta && !isMetaPressed) || (!meta && isMetaPressed)) {
continue;
}
if ((shift && !isShiftPressed) || (!shift && isShiftPressed)) {
continue;
}
if (code !== e.keyCode) {
continue;
}
return true;
}
return false;
}
export function executeHotKey (
e: KeyboardEvent,
definition: Hotkey,
callback: Function
): void {
if (pressedHotKey(e, definition)) {
callback();
trackEvent('Hotkey', definition.description);
}
}
export function getChar (hotkey: Hotkey) {
const codes = Array.isArray(hotkey.keycode) ? hotkey.keycode : [hotkey.keycode];
const chars = [];
for (const keycode of codes) {
const v = Object.keys(keycodes).find(k => keycodes[k] === keycode);
if (!v) {
console.error('Invalid hotkey', hotkey);
} else if (v.toUpperCase() === 'ENTER') {
chars.push('Enter');
} else if (v.toUpperCase() === 'COMMA') {
chars.push(',');
} else if (v.toUpperCase() === 'BACKSLASH') {
chars.push('\\');
} else if (v.toUpperCase() === 'FORWARDSLASH') {
chars.push('/');
} else if (v.toUpperCase() === 'SPACE') {
chars.push('Space');
} else {
chars.push(v.toUpperCase());
}
}
return chars[0] || 'unknown';
}

101
app/common/keycodes.js Normal file
View File

@@ -0,0 +1,101 @@
export default {
backspace: 8,
tab: 9,
enter: 13,
shift: 16,
ctrl: 17,
alt: 18,
pausebreak: 19,
capslock: 20,
esc: 27,
space: 32,
pageup: 33,
pagedown: 34,
end: 35,
home: 36,
leftarrow: 37,
uparrow: 38,
rightarrow: 39,
downarrow: 40,
insert: 45,
delete: 46,
0: 48,
1: 49,
2: 50,
3: 51,
4: 52,
5: 53,
6: 54,
7: 55,
8: 56,
9: 57,
a: 65,
b: 66,
c: 67,
d: 68,
e: 69,
f: 70,
g: 71,
h: 72,
i: 73,
j: 74,
k: 75,
l: 76,
m: 77,
n: 78,
o: 79,
p: 80,
q: 81,
r: 82,
s: 83,
t: 84,
u: 85,
v: 86,
w: 87,
x: 88,
y: 89,
z: 90,
leftwindowkey: 91,
rightwindowkey: 92,
selectkey: 93,
numpad0: 96,
numpad1: 97,
numpad2: 98,
numpad3: 99,
numpad4: 100,
numpad5: 101,
numpad6: 102,
numpad7: 103,
numpad8: 104,
numpad9: 105,
multiply: 106,
add: 107,
subtract: 109,
decimalpoint: 110,
divide: 111,
f1: 112,
f2: 113,
f3: 114,
f4: 115,
f5: 116,
f6: 117,
f7: 118,
f8: 119,
f9: 120,
f10: 121,
f11: 122,
f12: 123,
numlock: 144,
scrolllock: 145,
semicolon: 186,
equalsign: 187,
comma: 188,
dash: 189,
period: 190,
forwardslash: 191,
graveaccent: 192,
openbracket: 219,
backslash: 220,
closebracket: 221,
singlequote: 222
};

View File

@@ -1,4 +1,4 @@
import urlMatchesCertHost from '../url-matches-cert-host';
import {urlMatchesCertHost} from '../url-matches-cert-host';
import {globalBeforeEach} from '../../__jest__/before-each';
describe('urlMatchesCertHost', () => {

View File

@@ -25,7 +25,7 @@ import * as plugins from '../plugins/index';
import * as pluginContexts from '../plugins/context/index';
import {getAuthHeader} from './authentication';
import {cookiesFromJar, jarFromCookies} from '../common/cookies';
import urlMatchesCertHost from './url-matches-cert-host';
import {urlMatchesCertHost} from './url-matches-cert-host';
import aws4 from 'aws4';
export type ResponsePatch = {

View File

@@ -4,7 +4,7 @@ import {escapeRegex, setDefaultProtocol} from '../common/misc';
const DEFAULT_PORT = 443;
export default function urlMatchesCertHost (certificateHost, requestUrl) {
export function urlMatchesCertHost (certificateHost, requestUrl) {
const cHostWithProtocol = setDefaultProtocol(certificateHost, 'https:');
const {hostname, port} = urlParse(requestUrl);
const {hostname: cHostname, port: cPort} = certificateUrlParse(cHostWithProtocol);
@@ -14,5 +14,9 @@ export default function urlMatchesCertHost (certificateHost, requestUrl) {
const cHostnameRegex = escapeRegex(cHostname || '').replace(/\\\*/g, '.*');
return (assumedCPort === assumedPort && !!hostname.match(`^${cHostnameRegex}$`));
if (assumedCPort !== assumedPort) {
return false;
}
return !!(hostname || '').match(`^${cHostnameRegex}$`);
}

View File

@@ -1,27 +1,21 @@
// @flow
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import type {Hotkey as HotkeyType} from '../../../../common/hotkeys';
import Hotkey from '../../hotkey';
type Props = {
hotkey: HotkeyType,
};
class DropdownHint extends PureComponent {
props: Props;
render () {
const {char, shift, alt} = this.props;
const {hotkey} = this.props;
return (
<Hotkey
className="dropdown__hint"
char={char}
alt={alt}
shift={shift}
/>
<Hotkey className="dropdown__hint" hotkey={hotkey}/>
);
}
}
DropdownHint.propTypes = {
char: PropTypes.string.isRequired,
// Optional
alt: PropTypes.bool,
shift: PropTypes.bool
};
export default DropdownHint;

View File

@@ -7,6 +7,9 @@ import DropdownButton from './dropdown-button';
import DropdownItem from './dropdown-item';
import DropdownDivider from './dropdown-divider';
import {fuzzyMatch} from '../../../../common/misc';
import KeydownBinder from '../../keydown-binder';
import * as hotkeys from '../../../../common/hotkeys';
import {executeHotKey} from '../../../../common/hotkeys';
@autobind
class Dropdown extends PureComponent {
@@ -113,10 +116,9 @@ class Dropdown extends PureComponent {
this._handleDropdownNavigation(e);
if (e.key === 'Escape') {
e.preventDefault();
executeHotKey(e, hotkeys.CLOSE_DROPDOWN, () => {
this.hide();
}
});
}
_checkSizeAndPosition () {
@@ -242,8 +244,6 @@ class Dropdown extends PureComponent {
}
componentDidMount () {
document.body.addEventListener('keydown', this._handleBodyKeyDown);
// Move the element to the body so we can position absolutely
if (this._dropdownMenu) {
const el = ReactDOM.findDOMNode(this._dropdownMenu);
@@ -252,8 +252,6 @@ class Dropdown extends PureComponent {
}
componentWillUnmount () {
document.body.removeEventListener('keydown', this._handleBodyKeyDown);
// Remove the element from the body
if (this._dropdownMenu) {
const el = ReactDOM.findDOMNode(this._dropdownMenu);
@@ -272,7 +270,7 @@ class Dropdown extends PureComponent {
this.props.onHide && this.props.onHide();
}
show () {
show (filterVisible = false) {
const bodyHeight = document.body.getBoundingClientRect().height;
const dropdownTop = this._node.getBoundingClientRect().top;
const dropUp = dropdownTop > bodyHeight - 200;
@@ -280,7 +278,7 @@ class Dropdown extends PureComponent {
this.setState({
open: true,
dropUp,
filterVisible: false,
filterVisible,
filter: '',
filterItems: null,
filterActiveIndex: -1,
@@ -290,11 +288,11 @@ class Dropdown extends PureComponent {
this.props.onOpen && this.props.onOpen();
}
toggle () {
toggle (filterVisible = false) {
if (this.state.open) {
this.hide();
} else {
this.show();
this.show(filterVisible);
}
}
@@ -405,14 +403,16 @@ class Dropdown extends PureComponent {
}
return (
<div style={style}
className={classes}
ref={this._setRef}
onClick={this._handleClick}
tabIndex="-1"
onMouseDown={this._handleMouseDown}>
{finalChildren}
</div>
<KeydownBinder stopMetaPropagation onKeydown={this._handleBodyKeyDown} disabled={!open}>
<div style={style}
className={classes}
ref={this._setRef}
onClick={this._handleClick}
tabIndex="-1"
onMouseDown={this._handleMouseDown}>
{finalChildren}
</div>
</KeydownBinder>
);
}
}

View File

@@ -2,7 +2,9 @@ import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import autobind from 'autobind-decorator';
import classnames from 'classnames';
import {isMac} from '../../../common/constants';
import KeydownBinder from '../keydown-binder';
import * as hotkeys from '../../../common/hotkeys';
import {pressedHotKey} from '../../../common/hotkeys';
// Keep global z-index reference so that every modal will
// appear over top of an existing one.
@@ -25,20 +27,13 @@ class Modal extends PureComponent {
return;
}
// Don't bubble up meta events up past the modal no matter what
// Example: ctrl+Enter to send requests
const isMeta = isMac() ? e.metaKey : e.ctrlKey;
if (isMeta) {
e.stopPropagation();
}
// Don't check for close keys if we don't want them
if (this.props.noEscape) {
return;
}
const closeOnKeyCodes = this.props.closeOnKeyCodes || [];
const pressedEscape = e.keyCode === 27;
const pressedEscape = pressedHotKey(e, hotkeys.CLOSE_MODAL);
const pressedElse = closeOnKeyCodes.find(c => c === e.keyCode);
if (pressedEscape || pressedElse) {
@@ -134,19 +129,20 @@ class Modal extends PureComponent {
}
return (
<div ref={this._setModalRef}
onKeyDown={this._handleKeyDown}
tabIndex="-1"
className={classes}
style={styles}
onClick={this._handleClick}>
<div className="modal__backdrop overlay theme--overlay" data-close-modal></div>
<div className="modal__content__wrapper">
<div className="modal__content" key={forceRefreshCounter}>
{children}
<KeydownBinder stopMetaPropagation scoped onKeydown={this._handleKeyDown}>
<div ref={this._setModalRef}
tabIndex="-1"
className={classes}
style={styles}
onClick={this._handleClick}>
<div className="modal__backdrop overlay theme--overlay" data-close-modal></div>
<div className="modal__content__wrapper">
<div className="modal__content" key={forceRefreshCounter}>
{children}
</div>
</div>
</div>
</div>
</KeydownBinder>
);
}
}

View File

@@ -6,6 +6,9 @@ import {Dropdown, DropdownButton, DropdownDivider, DropdownHint, DropdownItem} f
import {showModal} from '../modals/index';
import {trackEvent} from '../../../analytics/index';
import Tooltip from '../tooltip';
import {executeHotKey} from '../../../common/hotkeys';
import * as hotkeys from '../../../common/hotkeys';
import KeydownBinder from '../keydown-binder';
@autobind
class EnvironmentsDropdown extends PureComponent {
@@ -18,6 +21,10 @@ class EnvironmentsDropdown extends PureComponent {
showModal(EnvironmentsModal, this.props.workspace);
}
_setDropdownRef (n) {
this._dropdown = n;
}
renderEnvironmentItem (environment) {
return (
<DropdownItem key={environment._id}
@@ -29,6 +36,12 @@ class EnvironmentsDropdown extends PureComponent {
);
}
_handleKeydown (e) {
executeHotKey(e, hotkeys.TOGGLE_ENVIRONMENTS_MENU, () => {
this._dropdown && this._dropdown.toggle(true);
});
}
render () {
const {
className,
@@ -52,40 +65,42 @@ class EnvironmentsDropdown extends PureComponent {
}
return (
<Dropdown {...other} className={className}>
<DropdownButton className="btn btn--super-compact no-wrap">
<div className="sidebar__menu__thing">
{(!activeEnvironment && subEnvironments.length > 0) && (
<Tooltip message="No environments active. Please select one to use."
className="space-right"
position="right">
<i className="fa fa-exclamation-triangle notice"/>
</Tooltip>
)}
<div className="sidebar__menu__thing__text">
{activeEnvironment && activeEnvironment.color && (
<i className="fa fa-circle space-right" style={{color: activeEnvironment.color}}/>
<KeydownBinder onKeydown={this._handleKeydown}>
<Dropdown ref={this._setDropdownRef} {...other} className={className}>
<DropdownButton className="btn btn--super-compact no-wrap">
<div className="sidebar__menu__thing">
{(!activeEnvironment && subEnvironments.length > 0) && (
<Tooltip message="No environments active. Please select one to use."
className="space-right"
position="right">
<i className="fa fa-exclamation-triangle notice"/>
</Tooltip>
)}
{description}
<div className="sidebar__menu__thing__text">
{activeEnvironment && activeEnvironment.color && (
<i className="fa fa-circle space-right" style={{color: activeEnvironment.color}}/>
)}
{description}
</div>
<i className="space-left fa fa-caret-down"/>
</div>
<i className="space-left fa fa-caret-down"/>
</div>
</DropdownButton>
</DropdownButton>
<DropdownDivider>Activate Environment</DropdownDivider>
{subEnvironments.map(this.renderEnvironmentItem)}
<DropdownDivider>Activate Environment</DropdownDivider>
{subEnvironments.map(this.renderEnvironmentItem)}
<DropdownItem value={null} onClick={this._handleActivateEnvironment}>
<i className="fa fa-empty"/> No Environment
</DropdownItem>
<DropdownItem value={null} onClick={this._handleActivateEnvironment}>
<i className="fa fa-empty"/> No Environment
</DropdownItem>
<DropdownDivider>General</DropdownDivider>
<DropdownDivider>General</DropdownDivider>
<DropdownItem onClick={this._handleShowEnvironmentModal}>
<i className="fa fa-wrench"/> Manage Environments
<DropdownHint char="E"/>
</DropdownItem>
</Dropdown>
<DropdownItem onClick={this._handleShowEnvironmentModal}>
<i className="fa fa-wrench"/> Manage Environments
<DropdownHint hotkey={hotkeys.SHOW_ENVIRONMENTS}/>
</DropdownItem>
</Dropdown>
</KeydownBinder>
);
}
}

View File

@@ -10,6 +10,10 @@ const LOCALSTORAGE_KEY = 'insomnia.httpMethods';
@autobind
class MethodDropdown extends PureComponent {
_setDropdownRef (n) {
this._dropdown = n;
}
_handleSetCustomMethod () {
let recentMethods;
try {
@@ -62,6 +66,10 @@ class MethodDropdown extends PureComponent {
trackEvent('Request', 'Set Method', method);
}
toggle () {
this._dropdown && this._dropdown.toggle(true);
}
render () {
const {
method,
@@ -70,7 +78,7 @@ class MethodDropdown extends PureComponent {
...extraProps
} = this.props;
return (
<Dropdown className="method-dropdown" right={right}>
<Dropdown ref={this._setDropdownRef} className="method-dropdown" right={right}>
<DropdownButton type="button" {...extraProps}>
{method} <i className="fa fa-caret-down"/>
</DropdownButton>

View File

@@ -6,6 +6,7 @@ import {Dropdown, DropdownButton, DropdownHint, DropdownItem} from '../base/drop
import * as models from '../../../models';
import {trackEvent} from '../../../analytics/index';
import {DropdownDivider} from '../base/dropdown/index';
import * as hotkeys from '../../../common/hotkeys';
@autobind
class RequestActionsDropdown extends PureComponent {
@@ -53,10 +54,11 @@ class RequestActionsDropdown extends PureComponent {
</DropdownButton>
<DropdownItem onClick={this._handleDuplicate}>
<i className="fa fa-copy"/> Duplicate
<DropdownHint char="D"/>
<DropdownHint hotkey={hotkeys.DUPLICATE_REQUEST}/>
</DropdownItem>
<DropdownItem onClick={this._handleGenerateCode}>
<i className="fa fa-code"/> Generate Code
<DropdownHint hotkey={hotkeys.GENERATE_CODE}/>
</DropdownItem>
<DropdownItem onClick={this._handleCopyAsCurl}
buttonClass={PromptButton}
@@ -72,7 +74,7 @@ class RequestActionsDropdown extends PureComponent {
<DropdownItem onClick={handleShowSettings}>
<i className="fa fa-wrench"/> Settings
<DropdownHint alt shift char=","/>
<DropdownHint hotkey={hotkeys.SHOW_REQUEST_SETTINGS}/>
</DropdownItem>
</Dropdown>
);

View File

@@ -8,6 +8,7 @@ import * as models from '../../../models';
import {showModal} from '../modals';
import {trackEvent} from '../../../analytics/index';
import {showPrompt} from '../modals/index';
import * as hotkeys from '../../../common/hotkeys';
@autobind
class RequestGroupActionsDropdown extends PureComponent {
@@ -69,10 +70,11 @@ class RequestGroupActionsDropdown extends PureComponent {
</DropdownButton>
<DropdownItem onClick={this._handleRequestCreate}>
<i className="fa fa-plus-circle"/> New Request
<DropdownHint char="N"></DropdownHint>
<DropdownHint hotkey={hotkeys.CREATE_REQUEST}/>
</DropdownItem>
<DropdownItem onClick={this._handleRequestGroupCreate}>
<i className="fa fa-folder"/> New Folder
<DropdownHint hotkey={hotkeys.CREATE_FOLDER}/>
</DropdownItem>
<DropdownDivider />
<DropdownItem onClick={this._handleRequestGroupDuplicate}>

View File

@@ -7,9 +7,16 @@ import StatusTag from '../tags/status-tag';
import TimeTag from '../tags/time-tag';
import PromptButton from '../base/prompt-button';
import {trackEvent} from '../../../analytics/index';
import KeydownBinder from '../keydown-binder';
import * as hotkeys from '../../../common/hotkeys';
import {executeHotKey} from '../../../common/hotkeys';
@autobind
class ResponseHistoryDropdown extends PureComponent {
_setDropdownRef (n) {
this._dropdown = n;
}
_handleDeleteResponses () {
trackEvent('History', 'Delete Responses');
this.props.handleDeleteResponses(this.props.requestId);
@@ -25,6 +32,12 @@ class ResponseHistoryDropdown extends PureComponent {
this.props.handleSetActiveResponse(response);
}
_handleKeydown (e) {
executeHotKey(e, hotkeys.TOGGLE_HISTORY_DROPDOWN, () => {
this._dropdown && this._dropdown.toggle(true);
});
}
componentWillUnmount () {
clearTimeout(this._interval);
}
@@ -68,28 +81,31 @@ class ResponseHistoryDropdown extends PureComponent {
const isLatestResponseActive = !responses.length || activeResponse._id === responses[0]._id;
return (
<Dropdown key={activeResponse ? activeResponse._id : 'n/a'} {...extraProps}>
<DropdownButton className="btn btn--super-compact tall">
{isLatestResponseActive
? <i className="fa fa-history"/>
: <i className="fa fa-thumb-tack"/>}
</DropdownButton>
<DropdownDivider>Response History</DropdownDivider>
<DropdownItem buttonClass={PromptButton}
addIcon
onClick={this._handleDeleteResponse}>
<i className="fa fa-trash-o"/>
Delete Current Response
</DropdownItem>
<DropdownItem buttonClass={PromptButton}
addIcon
onClick={this._handleDeleteResponses}>
<i className="fa fa-trash-o"/>
Clear History
</DropdownItem>
<DropdownDivider>Past Responses</DropdownDivider>
{responses.map(this.renderDropdownItem)}
</Dropdown>
<KeydownBinder onKeydown={this._handleKeydown}>
<Dropdown ref={this._setDropdownRef}
key={activeResponse ? activeResponse._id : 'n/a'} {...extraProps}>
<DropdownButton className="btn btn--super-compact tall">
{isLatestResponseActive
? <i className="fa fa-history"/>
: <i className="fa fa-thumb-tack"/>}
</DropdownButton>
<DropdownDivider>Response History</DropdownDivider>
<DropdownItem buttonClass={PromptButton}
addIcon
onClick={this._handleDeleteResponse}>
<i className="fa fa-trash-o"/>
Delete Current Response
</DropdownItem>
<DropdownItem buttonClass={PromptButton}
addIcon
onClick={this._handleDeleteResponses}>
<i className="fa fa-trash-o"/>
Clear History
</DropdownItem>
<DropdownDivider>Past Responses</DropdownDivider>
{responses.map(this.renderDropdownItem)}
</Dropdown>
</KeydownBinder>
);
}
}

View File

@@ -18,6 +18,9 @@ import WorkspaceShareSettingsModal from '../modals/workspace-share-settings-moda
import * as session from '../../../sync/session';
import LoginModal from '../modals/login-modal';
import Tooltip from '../tooltip';
import {executeHotKey} from '../../../common/hotkeys';
import * as hotkeys from '../../../common/hotkeys';
import KeydownBinder from '../keydown-binder';
@autobind
class WorkspaceDropdown extends PureComponent {
@@ -44,6 +47,10 @@ class WorkspaceDropdown extends PureComponent {
}
}
_setDropdownRef (n) {
this._dropdown = n;
}
_handleShowLogin () {
showModal(LoginModal);
}
@@ -90,6 +97,12 @@ class WorkspaceDropdown extends PureComponent {
});
}
_handleKeydown (e) {
executeHotKey(e, hotkeys.TOGGLE_MAIN_MENU, () => {
this._dropdown && this._dropdown.toggle(true);
});
}
render () {
const {
className,
@@ -112,83 +125,86 @@ class WorkspaceDropdown extends PureComponent {
);
return (
<Dropdown beside
className={classes}
onOpen={this._handleDropdownOpen}
onHide={this._handleDropdownHide}
{...other}>
<DropdownButton className="btn wide">
<h1 className="no-pad text-left">
<div className="pull-right">
{isLoading ? <i className="fa fa-refresh fa-spin"/> : null}
{unseenWorkspaces.length > 0 && (
<Tooltip message={unseenWorkspacesMessage} position="bottom">
<i className="fa fa-asterisk space-left"/>
</Tooltip>
)}
<i className="fa fa-caret-down space-left"/>
</div>
{activeWorkspace.name}
</h1>
</DropdownButton>
<DropdownDivider>{activeWorkspace.name}</DropdownDivider>
<DropdownItem onClick={this._handleShowWorkspaceSettings}>
<i className="fa fa-wrench"/> Workspace Settings
<DropdownHint shift char=","/>
</DropdownItem>
<KeydownBinder onKeydown={this._handleKeydown}>
<Dropdown beside
ref={this._setDropdownRef}
className={classes}
onOpen={this._handleDropdownOpen}
onHide={this._handleDropdownHide}
{...other}>
<DropdownButton className="btn wide">
<h1 className="no-pad text-left">
<div className="pull-right">
{isLoading ? <i className="fa fa-refresh fa-spin"/> : null}
{unseenWorkspaces.length > 0 && (
<Tooltip message={unseenWorkspacesMessage} position="bottom">
<i className="fa fa-asterisk space-left"/>
</Tooltip>
)}
<i className="fa fa-caret-down space-left"/>
</div>
{activeWorkspace.name}
</h1>
</DropdownButton>
<DropdownDivider>{activeWorkspace.name}</DropdownDivider>
<DropdownItem onClick={this._handleShowWorkspaceSettings}>
<i className="fa fa-wrench"/> Workspace Settings
<DropdownHint hotkey={hotkeys.SHOW_WORKSPACE_SETTINGS}/>
</DropdownItem>
<DropdownItem onClick={this._handleShowShareSettings}>
<i className="fa fa-globe"/> Share <strong>{activeWorkspace.name}</strong>
</DropdownItem>
<DropdownItem onClick={this._handleShowShareSettings}>
<i className="fa fa-globe"/> Share <strong>{activeWorkspace.name}</strong>
</DropdownItem>
<DropdownDivider>Switch Workspace</DropdownDivider>
<DropdownDivider>Switch Workspace</DropdownDivider>
{nonActiveWorkspaces.map(w => {
const isUnseen = !!unseenWorkspaces.find(v => v._id === w._id);
return (
<DropdownItem key={w._id} onClick={this._handleSwitchWorkspace} value={w._id}>
<i className="fa fa-random"/> To <strong>{w.name}</strong>
{isUnseen && (
<Tooltip message="This workspace is new">
<i className="width-auto fa fa-asterisk surprise"/>
</Tooltip>
)}
{nonActiveWorkspaces.map(w => {
const isUnseen = !!unseenWorkspaces.find(v => v._id === w._id);
return (
<DropdownItem key={w._id} onClick={this._handleSwitchWorkspace} value={w._id}>
<i className="fa fa-random"/> To <strong>{w.name}</strong>
{isUnseen && (
<Tooltip message="This workspace is new">
<i className="width-auto fa fa-asterisk surprise"/>
</Tooltip>
)}
</DropdownItem>
);
})}
<DropdownItem onClick={this._handleWorkspaceCreate}>
<i className="fa fa-empty"/> New Workspace
</DropdownItem>
<DropdownDivider>Insomnia Version {getAppVersion()}</DropdownDivider>
<DropdownItem onClick={this._handleShowSettings}>
<i className="fa fa-cog"/> Preferences
<DropdownHint hotkey={hotkeys.SHOW_SETTINGS}/>
</DropdownItem>
<DropdownItem onClick={this._handleShowExport}>
<i className="fa fa-share"/> Import/Export
</DropdownItem>
{/* Not Logged In */}
{!this.state.loggedIn && (
<DropdownItem key="login" onClick={this._handleShowLogin}>
<i className="fa fa-sign-in"/> Log In
</DropdownItem>
);
})}
)}
<DropdownItem onClick={this._handleWorkspaceCreate}>
<i className="fa fa-empty"/> New Workspace
</DropdownItem>
<DropdownDivider>Insomnia Version {getAppVersion()}</DropdownDivider>
<DropdownItem onClick={this._handleShowSettings}>
<i className="fa fa-cog"/> Preferences
<DropdownHint char=","/>
</DropdownItem>
<DropdownItem onClick={this._handleShowExport}>
<i className="fa fa-share"/> Import/Export
</DropdownItem>
{/* Not Logged In */}
{!this.state.loggedIn && (
<DropdownItem key="login" onClick={this._handleShowLogin}>
<i className="fa fa-sign-in"/> Log In
</DropdownItem>
)}
{!this.state.loggedIn && (
<DropdownItem key="invite"
buttonClass={Link}
href="https://insomnia.rest/pricing/"
button>
<i className="fa fa-users"/> Upgrade to Plus
<i className="fa fa-star surprise fa-outline"/>
</DropdownItem>
)}
</Dropdown>
{!this.state.loggedIn && (
<DropdownItem key="invite"
buttonClass={Link}
href="https://insomnia.rest/pricing/"
button>
<i className="fa fa-users"/> Upgrade to Plus
<i className="fa fa-star surprise fa-outline"/>
</DropdownItem>
)}
</Dropdown>
</KeydownBinder>
);
}
}

View File

@@ -269,7 +269,8 @@ class OAuth2Auth extends React.PureComponent {
'redirectUrl',
this._handleChangeRedirectUrl,
'This can be whatever you want or need it to be. Insomnia will automatically ' +
'detect the redirect from the browser window and extract the credentials.'
'detect a redirect in the client browser window and extract the code from the ' +
'redirected URL'
);
const state = this.renderInputRow(

View File

@@ -1,34 +1,40 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
// @flow
import React from 'react';
import classnames from 'classnames';
import type {Hotkey as HotkeyType} from '../../common/hotkeys';
import {ALT_SYM, CTRL_SYM, isMac, joinHotKeys, MOD_SYM, SHIFT_SYM} from '../../common/constants';
import * as hotkeys from '../../common/hotkeys';
type Props = {
hotkey: HotkeyType,
// Optional
className?: string
};
class Hotkey extends React.PureComponent {
props: Props;
class Hotkey extends PureComponent {
render () {
const {char, shift, alt, ctrl, className} = this.props;
const {hotkey, className} = this.props;
const {alt, shift, meta} = hotkey;
const chars = [ ];
alt && chars.push(ALT_SYM);
shift && chars.push(SHIFT_SYM);
ctrl && chars.push(CTRL_SYM);
!ctrl && chars.push(MOD_SYM);
chars.push(char);
if (meta) {
chars.push(isMac() ? MOD_SYM : CTRL_SYM);
}
chars.push(hotkeys.getChar(hotkey));
return (
<span className={`${isMac() ? 'font-normal' : ''} ${className}`}>
<span className={classnames(className, {'font-normal': isMac()})}>
{joinHotKeys(chars)}
</span>
);
}
}
Hotkey.propTypes = {
char: PropTypes.string.isRequired,
// Optional
alt: PropTypes.bool,
shift: PropTypes.bool,
ctrl: PropTypes.bool,
className: PropTypes.string
};
export default Hotkey;

View File

@@ -0,0 +1,57 @@
// @flow
import React from 'react';
import ReactDOM from 'react-dom';
import autobind from 'autobind-decorator';
import {isMac} from '../../common/constants';
type Props = {
onKeydown: Function,
children: React.Children,
disabled?: boolean,
scoped?: boolean,
stopMetaPropagation?: boolean
};
@autobind
class KeydownBinder extends React.Component {
props: Props;
_handleKeydown (e: KeyboardEvent) {
const {stopMetaPropagation, onKeydown, disabled} = this.props;
if (disabled) {
return;
}
const isMeta = isMac() ? e.metaKey : e.ctrlKey;
if (stopMetaPropagation && isMeta) {
e.stopPropagation();
}
onKeydown(e);
}
componentDidMount () {
if (this.props.scoped) {
const el = ReactDOM.findDOMNode(this);
el && el.addEventListener('keydown', this._handleKeydown);
} else {
document.body && document.body.addEventListener('keydown', this._handleKeydown);
}
}
componentWillUnmount () {
if (this.props.scoped) {
const el = ReactDOM.findDOMNode(this);
el && el.removeEventListener('keydown', this._handleKeydown);
} else {
document.body && document.body.removeEventListener('keydown', this._handleKeydown);
}
}
render () {
return this.props.children;
}
}
export default KeydownBinder;

View File

@@ -103,8 +103,10 @@ class WorkspaceShareSettingsModal extends PureComponent {
async show () {
this._resetState();
await this._load();
this.modal.show();
// This takes a while, so do it after show()
await this._load();
}
hide () {

View File

@@ -24,6 +24,7 @@ import {showModal} from './modals/index';
import RequestSettingsModal from './modals/request-settings-modal';
import MarkdownPreview from './markdown-preview';
import type {Settings} from '../../models/settings';
import * as hotkeys from '../../common/hotkeys';
@autobind
class RequestPane extends React.PureComponent {
@@ -199,19 +200,19 @@ class RequestPane extends React.PureComponent {
<tr>
<td>New Request</td>
<td className="text-right">
<code><Hotkey char="N"/></code>
<code><Hotkey hotkey={hotkeys.CREATE_REQUEST}/></code>
</td>
</tr>
<tr>
<td>Switch Requests</td>
<td className="text-right">
<code><Hotkey char="P"/></code>
<code><Hotkey hotkey={hotkeys.SHOW_QUICK_SWITCHER}/></code>
</td>
</tr>
<tr>
<td>Edit Environments</td>
<td className="text-right">
<code><Hotkey char="E"/></code>
<code><Hotkey hotkey={hotkeys.SHOW_ENVIRONMENTS}/></code>
</td>
</tr>
</tbody>

View File

@@ -9,6 +9,9 @@ import {showPrompt} from './modals/index';
import MethodDropdown from './dropdowns/method-dropdown';
import PromptButton from './base/prompt-button';
import OneLineEditor from './codemirror/one-line-editor';
import {executeHotKey} from '../../common/hotkeys';
import * as hotkeys from '../../common/hotkeys';
import KeydownBinder from './keydown-binder';
@autobind
class RequestUrlBar extends PureComponent {
@@ -28,6 +31,10 @@ class RequestUrlBar extends PureComponent {
this._dropdown = n;
}
_setMethodDropdownRef (n) {
this._methodDropdown = n;
}
_setInputRef (n) {
this._input = n;
}
@@ -109,13 +116,25 @@ class RequestUrlBar extends PureComponent {
return;
}
// meta+l
const metaPressed = isMac() ? e.metaKey : e.ctrlKey;
if (metaPressed && e.keyCode === 76) {
e.preventDefault();
executeHotKey(e, hotkeys.FOCUS_URL, () => {
if (!this._input) {
return;
}
this._input.focus();
this._input.selectAll();
}
});
executeHotKey(e, hotkeys.TOGGLE_METHOD_DROPDOWN, () => {
if (!this._methodDropdown) {
return;
}
this._methodDropdown.toggle();
});
executeHotKey(e, hotkeys.SHOW_SEND_OPTIONS, () => {
this._dropdown.toggle(true);
});
}
_handleSend () {
@@ -201,14 +220,6 @@ class RequestUrlBar extends PureComponent {
this._handleFormSubmit(e);
}
componentDidMount () {
document.body.addEventListener('keydown', this._handleKeyDown);
}
componentWillUnmount () {
document.body.removeEventListener('keydown', this._handleKeyDown);
}
componentWillReceiveProps (nextProps) {
if (nextProps.requestId !== this.props.requestId) {
this._handleResetTimeouts();
@@ -252,7 +263,7 @@ class RequestUrlBar extends PureComponent {
<DropdownDivider>Basic</DropdownDivider>
<DropdownItem type="submit">
<i className="fa fa-arrow-circle-o-right"/> Send Now
<DropdownHint char="Enter"/>
<DropdownHint hotkey={hotkeys.SEND_REQUEST}/>
</DropdownItem>
<DropdownItem onClick={this._handleGenerateCode}>
<i className="fa fa-code"/> Generate Client Code
@@ -296,26 +307,30 @@ class RequestUrlBar extends PureComponent {
} = this.props;
return (
<div className="urlbar">
<MethodDropdown onChange={this._handleMethodChange} method={method}>
{method} <i className="fa fa-caret-down"/>
</MethodDropdown>
<form onSubmit={this._handleFormSubmit}>
<OneLineEditor
key={uniquenessKey}
ref={this._setInputRef}
onPaste={this._handleUrlPaste}
forceEditor
type="text"
render={handleRender}
getAutocompleteConstants={handleAutocompleteUrls}
getRenderContext={handleGetRenderContext}
placeholder="https://api.myproduct.com/v1/users"
defaultValue={url}
onChange={this._handleUrlChange}/>
{this.renderSendButton()}
</form>
</div>
<KeydownBinder onKeydown={this._handleKeyDown}>
<div className="urlbar">
<MethodDropdown ref={this._setMethodDropdownRef}
onChange={this._handleMethodChange}
method={method}>
{method} <i className="fa fa-caret-down"/>
</MethodDropdown>
<form onSubmit={this._handleFormSubmit}>
<OneLineEditor
key={uniquenessKey}
ref={this._setInputRef}
onPaste={this._handleUrlPaste}
forceEditor
type="text"
render={handleRender}
getAutocompleteConstants={handleAutocompleteUrls}
getRenderContext={handleGetRenderContext}
placeholder="https://api.myproduct.com/v1/users"
defaultValue={url}
onChange={this._handleUrlChange}/>
{this.renderSendButton()}
</form>
</div>
</KeydownBinder>
);
}
}

View File

@@ -25,6 +25,7 @@ import {getSetCookieHeaders, nullFn} from '../../common/misc';
import {cancelCurrentRequest} from '../../network/network';
import {trackEvent} from '../../analytics';
import Hotkey from './hotkey';
import * as hotkeys from '../../common/hotkeys';
@autobind
class ResponsePane extends PureComponent {
@@ -186,25 +187,25 @@ class ResponsePane extends PureComponent {
<tr>
<td>Send Request</td>
<td className="text-right">
<code><Hotkey char="Enter"/></code>
<code><Hotkey hotkey={hotkeys.SEND_REQUEST}/></code>
</td>
</tr>
<tr>
<td>Focus Url Bar</td>
<td className="text-right">
<code><Hotkey char="L"/></code>
<code><Hotkey hotkey={hotkeys.FOCUS_URL}/></code>
</td>
</tr>
<tr>
<td>Manage Cookies</td>
<td className="text-right">
<code><Hotkey char="K"/></code>
<code><Hotkey hotkey={hotkeys.SHOW_COOKIES}/></code>
</td>
</tr>
<tr>
<td>Edit Environments</td>
<td className="text-right">
<code><Hotkey char="E"/></code>
<code><Hotkey hotkey={hotkeys.SHOW_ENVIRONMENTS}/></code>
</td>
</tr>
</tbody>

View File

@@ -1,13 +1,16 @@
import React, {PureComponent} from 'react';
import autobind from 'autobind-decorator';
import Hotkey from '../hotkey';
import * as hotkeys from '../../../common/hotkeys';
@autobind
class Shortcuts extends PureComponent {
renderHotkey (name, char, shift, alt, ctrl) {
renderHotkey (hotkey, i) {
return (
<tr>
<td>{name}</td>
<tr key={i}>
<td>{hotkey.description}</td>
<td className="text-right">
<code><Hotkey char={char} shift={shift} alt={alt} ctrl={ctrl}/></code>
<code><Hotkey hotkey={hotkey}/></code>
</td>
</tr>
);
@@ -18,19 +21,24 @@ class Shortcuts extends PureComponent {
<div>
<table className="table--fancy">
<tbody>
{this.renderHotkey('Switch Requests', 'P')}
{this.renderHotkey('Send Request', 'Enter')}
{this.renderHotkey('New Request', 'N')}
{this.renderHotkey('Duplicate Request', 'D')}
{this.renderHotkey('Show Cookie Manager', 'K')}
{this.renderHotkey('Show Environment Editor', 'E')}
{this.renderHotkey('Focus URL Bar', 'L')}
{this.renderHotkey('Toggle Sidebar', '\\')}
{this.renderHotkey('Show Autocomplete Dropdown', 'Space', false, false, true)}
{this.renderHotkey('Show App Preferences', ',')}
{this.renderHotkey('Show Workspace Settings', ',', true)}
{this.renderHotkey('Show Request Settings', ',', true, true)}
{this.renderHotkey('Show Keyboard Shortcuts', '?')}
{this.renderHotkey(hotkeys.SHOW_QUICK_SWITCHER)}
{this.renderHotkey(hotkeys.SEND_REQUEST)}
{this.renderHotkey(hotkeys.SHOW_SEND_OPTIONS)}
{this.renderHotkey(hotkeys.CREATE_REQUEST)}
{this.renderHotkey(hotkeys.CREATE_FOLDER)}
{this.renderHotkey(hotkeys.DUPLICATE_REQUEST)}
{this.renderHotkey(hotkeys.SHOW_COOKIES)}
{this.renderHotkey(hotkeys.SHOW_ENVIRONMENTS)}
{this.renderHotkey(hotkeys.TOGGLE_ENVIRONMENTS_MENU)}
{this.renderHotkey(hotkeys.FOCUS_URL)}
{this.renderHotkey(hotkeys.TOGGLE_METHOD_DROPDOWN)}
{this.renderHotkey(hotkeys.TOGGLE_SIDEBAR)}
{this.renderHotkey(hotkeys.TOGGLE_HISTORY_DROPDOWN)}
{this.renderHotkey(hotkeys.SHOW_AUTOCOMPLETE)}
{this.renderHotkey(hotkeys.SHOW_SETTINGS)}
{this.renderHotkey(hotkeys.SHOW_WORKSPACE_SETTINGS)}
{this.renderHotkey(hotkeys.SHOW_REQUEST_SETTINGS)}
{this.renderHotkey(hotkeys.TOGGLE_MAIN_MENU)}
</tbody>
</table>
</div>

View File

@@ -4,9 +4,16 @@ import autobind from 'autobind-decorator';
import {Dropdown, DropdownHint, DropdownButton, DropdownItem} from '../base/dropdown';
import {DEBOUNCE_MILLIS} from '../../../common/constants';
import {trackEvent} from '../../../analytics/index';
import KeydownBinder from '../keydown-binder';
import {executeHotKey} from '../../../common/hotkeys';
import * as hotkeys from '../../../common/hotkeys';
@autobind
class SidebarFilter extends PureComponent {
_setInputRef (n) {
this._input = n;
}
_handleOnChange (e) {
const value = e.target.value;
@@ -32,31 +39,40 @@ class SidebarFilter extends PureComponent {
trackEvent('Request', 'Create', 'Sidebar Filter');
}
_handleKeydown (e) {
executeHotKey(e, hotkeys.FOCUS_FILTER, () => {
this._input && this._input.focus();
});
}
render () {
return (
<div className="sidebar__filter">
<div className="form-control form-control--outlined">
<input
type="text"
placeholder="Filter"
defaultValue={this.props.filter}
onChange={this._handleOnChange}
/>
<KeydownBinder onKeydown={this._handleKeydown}>
<div className="sidebar__filter">
<div className="form-control form-control--outlined">
<input
ref={this._setInputRef}
type="text"
placeholder="Filter"
defaultValue={this.props.filter}
onChange={this._handleOnChange}
/>
</div>
<Dropdown right>
<DropdownButton className="btn btn--compact">
<i className="fa fa-plus-circle"/>
</DropdownButton>
<DropdownItem onClick={this._handleRequestCreate}>
<i className="fa fa-plus-circle"/> New Request
<DropdownHint hotkey={hotkeys.CREATE_REQUEST}/>
</DropdownItem>
<DropdownItem onClick={this._handleRequestGroupCreate}>
<i className="fa fa-folder"/> New Folder
<DropdownHint hotkey={hotkeys.CREATE_FOLDER}/>
</DropdownItem>
</Dropdown>
</div>
<Dropdown right>
<DropdownButton className="btn btn--compact">
<i className="fa fa-plus-circle"/>
</DropdownButton>
<DropdownItem onClick={this._handleRequestCreate}>
<i className="fa fa-plus-circle"/> New Request
<DropdownHint char="N"></DropdownHint>
</DropdownItem>
<DropdownItem onClick={this._handleRequestGroupCreate}>
<i className="fa fa-folder"/> New Folder
</DropdownItem>
</Dropdown>
</div>
</KeydownBinder>
);
}
}

View File

@@ -95,7 +95,7 @@ class SidebarRequestRow extends PureComponent {
if (!request) {
node = (
<li className={classes}>
<div className="sidebar__item" tabIndex={0}>
<div className="sidebar__item">
<button className="sidebar__clickable" onClick={this._handleRequestCreateFromEmpty}>
<em className="faded">click to add first request...</em>
</button>

View File

@@ -16,7 +16,7 @@ import CookiesModal from '../components/modals/cookies-modal';
import RequestSwitcherModal from '../components/modals/request-switcher-modal';
import ChangelogModal from '../components/modals/changelog-modal';
import SettingsModal, {TAB_INDEX_SHORTCUTS} from '../components/modals/settings-modal';
import {COLLAPSE_SIDEBAR_REMS, DEFAULT_PANE_HEIGHT, DEFAULT_PANE_WIDTH, DEFAULT_SIDEBAR_WIDTH, getAppVersion, isMac, MAX_PANE_HEIGHT, MAX_PANE_WIDTH, MAX_SIDEBAR_REMS, MIN_PANE_HEIGHT, MIN_PANE_WIDTH, MIN_SIDEBAR_REMS, PREVIEW_MODE_SOURCE} from '../../common/constants';
import {COLLAPSE_SIDEBAR_REMS, DEFAULT_PANE_HEIGHT, DEFAULT_PANE_WIDTH, DEFAULT_SIDEBAR_WIDTH, getAppVersion, MAX_PANE_HEIGHT, MAX_PANE_WIDTH, MAX_SIDEBAR_REMS, MIN_PANE_HEIGHT, MIN_PANE_WIDTH, MIN_SIDEBAR_REMS, PREVIEW_MODE_SOURCE} from '../../common/constants';
import * as globalActions from '../redux/modules/global';
import * as db from '../../common/database';
import * as models from '../../models';
@@ -35,17 +35,9 @@ import * as render from '../../common/render';
import {getKeys} from '../../templating/utils';
import {showAlert, showPrompt} from '../components/modals/index';
import {exportHar} from '../../common/har';
const KEY_ENTER = 13;
const KEY_COMMA = 188;
const KEY_D = 68;
const KEY_E = 69;
const KEY_K = 75;
const KEY_L = 76;
const KEY_N = 78;
const KEY_P = 80;
const KEY_R = 82;
const KEY_F5 = 116;
import * as hotkeys from '../../common/hotkeys';
import KeydownBinder from '../components/keydown-binder';
import {executeHotKey} from '../../common/hotkeys';
@autobind
class App extends PureComponent {
@@ -66,106 +58,52 @@ class App extends PureComponent {
this._savePaneWidth = debounce(paneWidth => this._updateActiveWorkspaceMeta({paneWidth}));
this._savePaneHeight = debounce(paneHeight => this._updateActiveWorkspaceMeta({paneHeight}));
this._saveSidebarWidth = debounce(sidebarWidth => this._updateActiveWorkspaceMeta({sidebarWidth}));
this._saveSidebarWidth = debounce(
sidebarWidth => this._updateActiveWorkspaceMeta({sidebarWidth}));
this._globalKeyMap = null;
}
_setGlobalKeyMap () {
this._globalKeyMap = [
{ // Show Workspace Settings
meta: true,
shift: true,
alt: false,
key: KEY_COMMA,
callback: () => {
const {activeWorkspace} = this.props;
showModal(WorkspaceSettingsModal, activeWorkspace);
trackEvent('HotKey', 'Workspace Settings');
[hotkeys.SHOW_WORKSPACE_SETTINGS, () => {
const {activeWorkspace} = this.props;
showModal(WorkspaceSettingsModal, activeWorkspace);
}],
[hotkeys.SHOW_REQUEST_SETTINGS, () => {
if (this.props.activeRequest) {
showModal(RequestSettingsModal, {request: this.props.activeRequest});
}
}, {
meta: true,
shift: true,
alt: true,
key: KEY_COMMA,
callback: () => {
if (this.props.activeRequest) {
showModal(RequestSettingsModal, {request: this.props.activeRequest});
trackEvent('HotKey', 'Request Settings');
}
}
}, {
meta: true,
shift: false,
alt: false,
key: KEY_P,
callback: () => {
showModal(RequestSwitcherModal);
trackEvent('HotKey', 'Quick Switcher');
}
}, {
meta: true,
shift: false,
alt: false,
key: [KEY_ENTER, KEY_R],
callback: this._handleSendShortcut
}, {
meta: false,
shift: false,
alt: false,
key: [KEY_F5],
callback: this._handleSendShortcut
}, {
meta: true,
shift: false,
alt: false,
key: KEY_E,
callback: () => {
const {activeWorkspace} = this.props;
showModal(WorkspaceEnvironmentsEditModal, activeWorkspace);
trackEvent('HotKey', 'Environments');
}
}, {
meta: true,
shift: false,
alt: false,
key: KEY_L,
callback: () => {
const node = document.body.querySelector('.urlbar input');
node && node.focus();
trackEvent('HotKey', 'Url');
}
}, {
meta: true,
shift: false,
alt: false,
key: KEY_K,
callback: () => {
const {activeWorkspace} = this.props;
showModal(CookiesModal, activeWorkspace);
trackEvent('HotKey', 'Cookies');
}
}, {
meta: true,
shift: false,
alt: false,
key: KEY_N,
callback: () => {
const {activeRequest, activeWorkspace} = this.props;
const parentId = activeRequest ? activeRequest.parentId : activeWorkspace._id;
this._requestCreate(parentId);
trackEvent('HotKey', 'Request Create');
}
}, {
meta: true,
shift: false,
alt: false,
key: KEY_D,
callback: async () => {
await this._requestDuplicate(this.props.activeRequest);
trackEvent('HotKey', 'Request Duplicate');
}
}
}],
[hotkeys.SHOW_QUICK_SWITCHER, () => {
showModal(RequestSwitcherModal);
}],
[hotkeys.SEND_REQUEST, this._handleSendShortcut],
[hotkeys.SEND_REQUEST_F5, this._handleSendShortcut],
[hotkeys.SHOW_ENVIRONMENTS, () => {
const {activeWorkspace} = this.props;
showModal(WorkspaceEnvironmentsEditModal, activeWorkspace);
}],
[hotkeys.SHOW_COOKIES, () => {
const {activeWorkspace} = this.props;
showModal(CookiesModal, activeWorkspace);
}],
[hotkeys.CREATE_REQUEST, () => {
const {activeRequest, activeWorkspace} = this.props;
const parentId = activeRequest ? activeRequest.parentId : activeWorkspace._id;
this._requestCreate(parentId);
}],
[hotkeys.CREATE_FOLDER, () => {
const {activeRequest, activeWorkspace} = this.props;
const parentId = activeRequest ? activeRequest.parentId : activeWorkspace._id;
this._requestGroupCreate(parentId);
}],
[hotkeys.GENERATE_CODE, async () => {
showModal(GenerateCodeModal, this.props.activeRequest);
}],
[hotkeys.DUPLICATE_REQUEST, async () => {
await this._requestDuplicate(this.props.activeRequest);
}]
];
}
@@ -175,7 +113,6 @@ class App extends PureComponent {
activeRequest ? activeRequest._id : 'n/a',
activeEnvironment ? activeEnvironment._id : 'n/a',
);
trackEvent('HotKey', 'Send');
}
_setRequestPaneRef (n) {
@@ -629,31 +566,8 @@ class App extends PureComponent {
}
_handleKeyDown (e) {
const isMetaPressed = isMac() ? e.metaKey : e.ctrlKey;
const isAltPressed = isMac() ? e.ctrlKey : e.altKey;
const isShiftPressed = e.shiftKey;
for (const {meta, shift, alt, key, callback} of this._globalKeyMap) {
const keys = Array.isArray(key) ? key : [key];
for (const key of keys) {
if ((alt && !isAltPressed) || (!alt && isAltPressed)) {
continue;
}
if ((meta && !isMetaPressed) || (!meta && isMetaPressed)) {
continue;
}
if ((shift && !isShiftPressed) || (!shift && isShiftPressed)) {
continue;
}
if (key !== e.keyCode) {
continue;
}
callback();
}
for (const [definition, callback] of this._globalKeyMap) {
executeHotKey(e, definition, callback);
}
}
@@ -708,7 +622,6 @@ class App extends PureComponent {
// Bind mouse and key handlers
document.addEventListener('mouseup', this._handleMouseUp);
document.addEventListener('mousemove', this._handleMouseMove);
document.addEventListener('keydown', this._handleKeyDown);
this._setGlobalKeyMap();
// Update title
@@ -797,55 +710,56 @@ class App extends PureComponent {
// Remove mouse and key handlers
document.removeEventListener('mouseup', this._handleMouseUp);
document.removeEventListener('mousemove', this._handleMouseMove);
document.removeEventListener('keydown', this._handleKeyDown);
}
render () {
return (
<div className="app">
<Wrapper
{...this.props}
ref={this._setWrapperRef}
paneWidth={this.state.paneWidth}
paneHeight={this.state.paneHeight}
sidebarWidth={this.state.sidebarWidth}
handleCreateRequestForWorkspace={this._requestCreateForWorkspace}
handleSetRequestGroupCollapsed={this._handleSetRequestGroupCollapsed}
handleActivateRequest={this._handleSetActiveRequest}
handleSetRequestPaneRef={this._setRequestPaneRef}
handleSetResponsePaneRef={this._setResponsePaneRef}
handleSetSidebarRef={this._setSidebarRef}
handleStartDragSidebar={this._startDragSidebar}
handleResetDragSidebar={this._resetDragSidebar}
handleStartDragPaneHorizontal={this._startDragPaneHorizontal}
handleStartDragPaneVertical={this._startDragPaneVertical}
handleResetDragPaneHorizontal={this._resetDragPaneHorizontal}
handleResetDragPaneVertical={this._resetDragPaneVertical}
handleCreateRequest={this._requestCreate}
handleRender={this._handleRenderText}
handleGetRenderContext={this._handleGetRenderContext}
handleDuplicateRequest={this._requestDuplicate}
handleDuplicateRequestGroup={this._requestGroupDuplicate}
handleDuplicateWorkspace={this._workspaceDuplicate}
handleCreateRequestGroup={this._requestGroupCreate}
handleGenerateCode={this._handleGenerateCode}
handleGenerateCodeForActiveRequest={this._handleGenerateCodeForActiveRequest}
handleCopyAsCurl={this._handleCopyAsCurl}
handleSetResponsePreviewMode={this._handleSetResponsePreviewMode}
handleSetResponseFilter={this._handleSetResponseFilter}
handleSendRequestWithEnvironment={this._handleSendRequestWithEnvironment}
handleSendAndDownloadRequestWithEnvironment={this._handleSendAndDownloadRequestWithEnvironment}
handleSetActiveResponse={this._handleSetActiveResponse}
handleSetActiveRequest={this._handleSetActiveRequest}
handleSetActiveEnvironment={this._handleSetActiveEnvironment}
handleSetSidebarFilter={this._handleSetSidebarFilter}
handleToggleMenuBar={this._handleToggleMenuBar}
/>
<Toast/>
<KeydownBinder onKeydown={this._handleKeyDown}>
<div className="app">
<Wrapper
{...this.props}
ref={this._setWrapperRef}
paneWidth={this.state.paneWidth}
paneHeight={this.state.paneHeight}
sidebarWidth={this.state.sidebarWidth}
handleCreateRequestForWorkspace={this._requestCreateForWorkspace}
handleSetRequestGroupCollapsed={this._handleSetRequestGroupCollapsed}
handleActivateRequest={this._handleSetActiveRequest}
handleSetRequestPaneRef={this._setRequestPaneRef}
handleSetResponsePaneRef={this._setResponsePaneRef}
handleSetSidebarRef={this._setSidebarRef}
handleStartDragSidebar={this._startDragSidebar}
handleResetDragSidebar={this._resetDragSidebar}
handleStartDragPaneHorizontal={this._startDragPaneHorizontal}
handleStartDragPaneVertical={this._startDragPaneVertical}
handleResetDragPaneHorizontal={this._resetDragPaneHorizontal}
handleResetDragPaneVertical={this._resetDragPaneVertical}
handleCreateRequest={this._requestCreate}
handleRender={this._handleRenderText}
handleGetRenderContext={this._handleGetRenderContext}
handleDuplicateRequest={this._requestDuplicate}
handleDuplicateRequestGroup={this._requestGroupDuplicate}
handleDuplicateWorkspace={this._workspaceDuplicate}
handleCreateRequestGroup={this._requestGroupCreate}
handleGenerateCode={this._handleGenerateCode}
handleGenerateCodeForActiveRequest={this._handleGenerateCodeForActiveRequest}
handleCopyAsCurl={this._handleCopyAsCurl}
handleSetResponsePreviewMode={this._handleSetResponsePreviewMode}
handleSetResponseFilter={this._handleSetResponseFilter}
handleSendRequestWithEnvironment={this._handleSendRequestWithEnvironment}
handleSendAndDownloadRequestWithEnvironment={this._handleSendAndDownloadRequestWithEnvironment}
handleSetActiveResponse={this._handleSetActiveResponse}
handleSetActiveRequest={this._handleSetActiveRequest}
handleSetActiveEnvironment={this._handleSetActiveEnvironment}
handleSetSidebarFilter={this._handleSetSidebarFilter}
handleToggleMenuBar={this._handleToggleMenuBar}
/>
<Toast/>
{/* Block all mouse activity by showing an overlay while dragging */}
{this.state.showDragOverlay ? <div className="blocker-overlay"></div> : null}
</div>
{/* Block all mouse activity by showing an overlay while dragging */}
{this.state.showDragOverlay ? <div className="blocker-overlay"></div> : null}
</div>
</KeydownBinder>
);
}
}

View File

@@ -45,7 +45,8 @@
label {
font-weight: 600;
display: block;
padding-top: @padding-xs;
margin-top: @padding-xs;
padding-top: 0;
* {
font-weight: normal;
@@ -279,3 +280,24 @@ input[type="color"] {
height: @line-height-xs !important;
padding: @padding-xxs !important;
}
label {
// So we can highlight from CSS checkbox selector
position: relative;
z-index: 1;
input[type="checkbox"]:focus,
input[type="radio"]:focus {
&::before {
z-index: -1;
content: ' ';
position: absolute;
top: -@padding-xxs;
bottom: -@padding-xxs;
left: -@padding-xxs;
right: -@padding-xxs;
border-radius: @radius-sm;
background-color: var(--hl-sm);
}
}
}

View File

@@ -242,18 +242,19 @@
}
}
// Active sidebar request
&.sidebar__item--request > button {
border-left: @padding-xs solid transparent;
& > button {
border-left: @padding-xxs solid transparent;
}
&.sidebar__item--active.sidebar__item--request > button,
&.sidebar__item--active.sidebar__item--request > .sidebar__actions,
&:hover > button,
& > button:focus,
&:hover .sidebar__actions {
transition: background-color 130ms ease-out;
&.sidebar__item--active.sidebar__item--request,
&:focus,
&:hover {
background-color: @hl-xs;
transition: background-color 0ms;
}
& > button:focus {
border-left-color: var(--color-surprise);
}
.tag {
@@ -287,31 +288,31 @@
// Padding for nested folders
.sidebar__list .sidebar__clickable {
padding-left: @padding-md;
padding-left: @padding-sm;
}
.sidebar__list .sidebar__list .sidebar__clickable {
padding-left: @padding-md * 2;
padding-left: @padding-sm + @padding-md * 1;
}
.sidebar__list .sidebar__list .sidebar__list .sidebar__clickable {
padding-left: @padding-md * 3;
padding-left: @padding-sm + @padding-md * 2;
}
.sidebar__list .sidebar__list .sidebar__list .sidebar__list .sidebar__clickable {
padding-left: @padding-md * 4;
padding-left: @padding-sm + @padding-md * 3;
}
.sidebar__list .sidebar__list .sidebar__list .sidebar__list .sidebar__list .sidebar__clickable {
padding-left: @padding-md * 5;
padding-left: @padding-sm + @padding-md * 4;
}
.sidebar__list .sidebar__list .sidebar__list .sidebar__list .sidebar__list .sidebar__list .sidebar__clickable {
padding-left: @padding-md * 6;
padding-left: @padding-sm + @padding-md * 5;
}
.sidebar__list .sidebar__list .sidebar__list .sidebar__list .sidebar__list .sidebar__list .sidebar__list .sidebar__clickable {
padding-left: @padding-md * 7;
padding-left: @padding-sm + @padding-md * 6;
}
// ~~~~~~~~~~~~~~~ //
@@ -332,6 +333,7 @@
}
& > button:hover,
& .dropdown > button:focus,
& > .dropdown:hover > button {
color: var(--color-font);
}