mirror of
https://github.com/Kong/insomnia.git
synced 2026-10-21 14:25:08 -04:00
Request Switcher
This commit is contained in:
1 parent
1ecbe35d87
commit
e1711cb96c
12 files changed
+300
-47
No files matched your search
@@ -45,7 +45,7 @@ class ChangelogModal extends ModalComponent {
|
||||
|
||||
if (!changelog) {
|
||||
html = [
|
||||
<div className="txt-lg">
|
||||
<div key="spinner" className="txt-lg">
|
||||
<i className="fa fa-refresh fa-spin"></i>
|
||||
</div>
|
||||
];
|
||||
@@ -55,19 +55,19 @@ class ChangelogModal extends ModalComponent {
|
||||
changelog.map(change => {
|
||||
html = [
|
||||
...html,
|
||||
<h1>v{change.version} Changes</h1>
|
||||
<h1 key="changes">v{change.version} Changes</h1>
|
||||
];
|
||||
if (change.summary) {
|
||||
if (!Array.isArray(change.summary)) {
|
||||
html = [
|
||||
...html,
|
||||
<p>{change.summary}</p>
|
||||
<p key="summary">{change.summary}</p>
|
||||
]
|
||||
} else {
|
||||
html = [
|
||||
...html,
|
||||
<p><strong>{change.summary[0]}</strong></p>,
|
||||
...change.summary.slice(1).map(text => <p>{text}</p>)
|
||||
<p key="summary"><strong>{change.summary[0]}</strong></p>,
|
||||
...change.summary.slice(1).map((text, i) => <p key={`summary[${i}]`}>{text}</p>)
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -75,30 +75,36 @@ class ChangelogModal extends ModalComponent {
|
||||
if (change.major && change.major.length) {
|
||||
html = [
|
||||
...html,
|
||||
<h3>Noteworthy</h3>,
|
||||
<ul>{change.major.map(text => <li>{text}</li>)}</ul>
|
||||
<h3 key="major">Noteworthy</h3>,
|
||||
<ul key="major.list">
|
||||
{change.major.map((text, i) => <li key={i}>{text}</li>)}
|
||||
</ul>
|
||||
];
|
||||
}
|
||||
|
||||
if (change.fixes && change.fixes.length) {
|
||||
html = [
|
||||
...html,
|
||||
<h3>Fixes</h3>,
|
||||
<ul>{change.fixes.map(text => <li>{text}</li>)}</ul>
|
||||
<h3 key="fixes">Fixes</h3>,
|
||||
<ul key="fixes.list">
|
||||
{change.fixes.map(text => <li>{text}</li>)}
|
||||
</ul>
|
||||
];
|
||||
}
|
||||
|
||||
if (change.minor && change.minor.length) {
|
||||
html = [
|
||||
...html,
|
||||
<h3>Minor Changes</h3>,
|
||||
<ul>{change.minor.map(text => <li>{text}</li>)}</ul>
|
||||
<h3 key="minor">Minor Changes</h3>,
|
||||
<ul key="minor.list">
|
||||
{change.minor.map((text, i) => <li key={i}>{text}</li>)}
|
||||
</ul>
|
||||
];
|
||||
}
|
||||
|
||||
html = [
|
||||
...html,
|
||||
<hr/>
|
||||
<hr key="hr"/>
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,26 +1,214 @@
|
||||
import React from 'react';
|
||||
import React, {PropTypes} from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import Modal from './base/Modal';
|
||||
import ModalBody from './base/ModalBody';
|
||||
import ModalHeader from './base/ModalHeader';
|
||||
import ModalFooter from './base/ModalFooter';
|
||||
import ModalBody from './base/ModalBody';
|
||||
import MethodTag from './MethodTag';
|
||||
import ModalComponent from './lib/ModalComponent';
|
||||
import * as db from '../database';
|
||||
|
||||
|
||||
class RequestSwitcherModal extends ModalComponent {
|
||||
render() {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
searchString: '',
|
||||
matchedRequests: [],
|
||||
requestGroups: [],
|
||||
activeIndex: -1
|
||||
}
|
||||
}
|
||||
|
||||
_setActiveIndex (activeIndex) {
|
||||
if (activeIndex < 0) {
|
||||
activeIndex = this.state.matchedRequests.length - 1;
|
||||
} else if (activeIndex >= this.state.matchedRequests.length) {
|
||||
activeIndex = 0;
|
||||
}
|
||||
|
||||
this.setState({activeIndex});
|
||||
}
|
||||
|
||||
_activateCurrentIndex () {
|
||||
if (this.state.matchedRequests.length) {
|
||||
// Activate the request if there is one
|
||||
this._activateRequest(this.state.matchedRequests[this.state.activeIndex]);
|
||||
} else {
|
||||
// Create the request if nothing matched
|
||||
const name = this.state.searchString;
|
||||
const parentId = this.props.activeRequest.parentId;
|
||||
|
||||
db.requestCreate({name, parentId}).then(request => {
|
||||
this._activateRequest(request);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_activateRequest (request) {
|
||||
if (!request) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.activateRequest(request);
|
||||
this.hide();
|
||||
}
|
||||
|
||||
_handleChange (searchString) {
|
||||
Promise.all([
|
||||
db.requestAll(),
|
||||
db.requestGroupAll()
|
||||
]).then(([
|
||||
requests,
|
||||
requestGroups
|
||||
]) => {
|
||||
const {activeRequest} = this.props;
|
||||
const activeRequestGroup = requestGroups.find(rg => rg._id === activeRequest.parentId);
|
||||
|
||||
const matchedRequests = requests.sort(
|
||||
(a, b) => {
|
||||
const rgA = requestGroups.find(rg => rg._id === a.parentId);
|
||||
const rgB = requestGroups.find(rg => rg._id === b.parentId);
|
||||
|
||||
if (rgA === activeRequestGroup && rgB !== activeRequestGroup) {
|
||||
return -1;
|
||||
} else if (rgB === activeRequestGroup && rgA !== activeRequestGroup) {
|
||||
return 1;
|
||||
} else {
|
||||
// TODO: Sort by quality of match (eg. start of string vs mid string, etc)
|
||||
return (rgA || {}).name > (rgB || {}).name ? 1 : -1;
|
||||
}
|
||||
}
|
||||
).filter(r => {
|
||||
if (!searchString) {
|
||||
return true;
|
||||
} else {
|
||||
return r.name.toLowerCase().indexOf(searchString.toLowerCase()) !== -1;
|
||||
}
|
||||
});
|
||||
|
||||
const activeIndex = searchString ? 0 : -1;
|
||||
|
||||
this.setState({
|
||||
activeIndex,
|
||||
matchedRequests,
|
||||
requestGroups,
|
||||
searchString
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_focusInput () {
|
||||
// Need to focus after the Modal has shown, or else it won't exist yet
|
||||
setTimeout(() => {
|
||||
this.refs.input.focus();
|
||||
});
|
||||
}
|
||||
|
||||
show () {
|
||||
throw new Error('show() not supposed to be called on RequestSwitcherModal');
|
||||
}
|
||||
|
||||
toggle () {
|
||||
super.toggle();
|
||||
this._focusInput();
|
||||
this._handleChange('');
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
super.componentDidMount();
|
||||
|
||||
ReactDOM.findDOMNode(this.refs.modal).addEventListener('keydown', 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();
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
const {matchedRequests, requestGroups, searchString, activeIndex} = this.state;
|
||||
|
||||
return (
|
||||
<Modal ref="modal" {...this.props}>
|
||||
<ModalHeader>Switch Request</ModalHeader>
|
||||
<ModalBody className="pad">
|
||||
<p>DO IT</p>
|
||||
<Modal ref="modal" top={true} {...this.props}>
|
||||
<ModalHeader hideCloseButton={true}>
|
||||
<p className="pull-right txt-md">
|
||||
<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
|
||||
</p>
|
||||
<p>Jump To Request</p>
|
||||
</ModalHeader>
|
||||
<ModalBody className="pad request-switcher">
|
||||
<div className="form-control form-control--outlined no-margin">
|
||||
<input
|
||||
type="text"
|
||||
ref="input"
|
||||
value={searchString}
|
||||
onChange={e => this._handleChange(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<ul className="pad-top">
|
||||
{matchedRequests.map((r, i) => {
|
||||
const requestGroup = requestGroups.find(rg => rg._id === r.parentId);
|
||||
|
||||
return (
|
||||
<li key={r._id}>
|
||||
<button onClick={e => this._activateRequest(r)}
|
||||
className={classnames('btn btn--compact wide text-left', {focus: activeIndex === i})}>
|
||||
{requestGroup ? (
|
||||
<div className="pull-right faint italic">
|
||||
{requestGroup.name}
|
||||
|
||||
<i className="fa fa-folder-o"></i>
|
||||
</div>
|
||||
) : null}
|
||||
<MethodTag method={r.method}/>
|
||||
<strong>{r.name}</strong>
|
||||
</button>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
|
||||
{matchedRequests.length === 0 ? (
|
||||
<div className="text-center">
|
||||
<p>
|
||||
No matches found for <strong>{searchString}</strong>
|
||||
</p>
|
||||
<button className="btn btn--outlined btn--compact"
|
||||
onClick={e => this._activateCurrentIndex()}>
|
||||
Create a request named {searchString}
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</ModalBody>
|
||||
<ModalFooter className="text-right">
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RequestSwitcherModal.propTypes = {};
|
||||
RequestSwitcherModal.propTypes = {
|
||||
activateRequest: PropTypes.func.isRequired,
|
||||
activeRequest: PropTypes.object.isRequired,
|
||||
workspaceId: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default RequestSwitcherModal;
|
||||
@@ -27,6 +27,10 @@ class Input extends Component {
|
||||
} else {
|
||||
this.refs.input.value = this.props.value || '';
|
||||
}
|
||||
|
||||
if (this.props.autofocus) {
|
||||
this.focus();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
@@ -34,7 +38,7 @@ class Input extends Component {
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
this._updateValueFromProps()
|
||||
this._updateValueFromProps();
|
||||
}
|
||||
|
||||
focus () {
|
||||
@@ -46,7 +50,8 @@ class Input extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const {value, ...other} = this.props;
|
||||
// NOTE: We're taking props base <input> doesn't need
|
||||
const {value, autofocus, ...other} = this.props;
|
||||
return (
|
||||
<input
|
||||
{...other}
|
||||
@@ -63,7 +68,8 @@ Input.propTypes = {
|
||||
PropTypes.number,
|
||||
PropTypes.string,
|
||||
PropTypes.bool
|
||||
])
|
||||
]),
|
||||
autofocus: PropTypes.bool
|
||||
};
|
||||
|
||||
export default Input;
|
||||
@@ -57,14 +57,15 @@ class Modal extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const {tall, className} = this.props;
|
||||
const {tall, top, className} = this.props;
|
||||
const {open} = this.state;
|
||||
|
||||
const classes = classnames(
|
||||
'modal',
|
||||
className,
|
||||
{'modal--open': open},
|
||||
{'modal--fixed-height': tall}
|
||||
{'modal--fixed-height': tall},
|
||||
{'modal--fixed-top': top}
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -82,7 +83,8 @@ class Modal extends Component {
|
||||
}
|
||||
|
||||
Modal.propTypes = {
|
||||
tall: PropTypes.bool
|
||||
tall: PropTypes.bool,
|
||||
top: PropTypes.bool
|
||||
};
|
||||
|
||||
export default Modal;
|
||||
|
||||
@@ -1,13 +1,27 @@
|
||||
import React from 'react';
|
||||
import React, {PropTypes} from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
const ModalHeader = ({className, children}) => (
|
||||
<div className={classnames('modal__header', className)}>
|
||||
<button className="btn btn--compact modal__close-btn" data-close-modal="true">
|
||||
<i className="fa fa-times"></i>
|
||||
</button>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
const ModalHeader = ({hideCloseButton, className, children}) => {
|
||||
let closeButton = null;
|
||||
|
||||
if (!hideCloseButton) {
|
||||
closeButton = (
|
||||
<button className="btn btn--compact modal__close-btn" data-close-modal="true">
|
||||
<i className="fa fa-times"></i>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classnames('modal__header', className)}>
|
||||
{closeButton}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ModalHeader.propTypes = {
|
||||
hideCloseButton: PropTypes.bool
|
||||
};
|
||||
|
||||
export default ModalHeader;
|
||||
+14
-4
@@ -150,11 +150,17 @@ class App extends Component {
|
||||
console.warn('-- Recreating Sort Keys --');
|
||||
|
||||
requestGroups.map((r, i) => {
|
||||
db.requestGroupUpdate(r, {metaSortKey: i * 100, parentId: requestGroupToTarget.parentId});
|
||||
db.requestGroupUpdate(r, {
|
||||
metaSortKey: i * 100,
|
||||
parentId: requestGroupToTarget.parentId
|
||||
});
|
||||
});
|
||||
} else {
|
||||
const metaSortKey = afterKey - (afterKey - beforeKey) / 2;
|
||||
db.requestGroupUpdate(requestGroupToMove, {metaSortKey, parentId: requestGroupToTarget.parentId});
|
||||
db.requestGroupUpdate(requestGroupToMove, {
|
||||
metaSortKey,
|
||||
parentId: requestGroupToTarget.parentId
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -226,7 +232,7 @@ class App extends Component {
|
||||
if (a.metaSortKey === b.metaSortKey) {
|
||||
return a._id > b._id ? -1 : 1;
|
||||
} else {
|
||||
return a.metaSortKey < b.metaSortKey ? -1 : 1;
|
||||
return a.metaSortKey < b.metaSortKey ? -1 : 1;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -485,8 +491,12 @@ class App extends Component {
|
||||
<AlertModal />
|
||||
<ChangelogModal />
|
||||
<SettingsModal />
|
||||
<RequestSwitcherModal />
|
||||
<CurlExportModal />
|
||||
<RequestSwitcherModal
|
||||
workspaceId={workspace._id}
|
||||
activeRequest={activeRequest}
|
||||
activateRequest={r => db.workspaceUpdate(workspace, {metaActiveRequestId: r._id})}
|
||||
/>
|
||||
<EnvironmentEditModal onChange={rg => db.requestGroupUpdate(rg)}/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -72,14 +72,12 @@
|
||||
|
||||
.dropdown__divider__label {
|
||||
position: relative;
|
||||
top: -0.85rem;
|
||||
top: -0.80rem;
|
||||
// HACK: Fine adjustment
|
||||
color: $hl-xxl;
|
||||
color: $hl;
|
||||
padding-right: 1em;
|
||||
background: #fff;
|
||||
font-size: $font-size-xs;
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,8 @@ label > .form-control,
|
||||
}
|
||||
}
|
||||
|
||||
.btn:focus:not(:disabled),
|
||||
.btn.focus:not(:disabled),
|
||||
.btn:hover:not(:disabled) {
|
||||
background: $hl-sm;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,10 @@
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
&.modal--fixed-top .modal__content {
|
||||
margin: 0 auto auto auto;
|
||||
}
|
||||
|
||||
.modal__header {
|
||||
border-bottom: 1px solid $hl-md;
|
||||
height: $line-height-md;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
@import '../constants/dimensions';
|
||||
@import '../constants/colors';
|
||||
|
||||
.request-switcher {
|
||||
ul {
|
||||
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: $hl-xs;
|
||||
}
|
||||
|
||||
.btn.focus {
|
||||
background: $hl-sm;
|
||||
}
|
||||
|
||||
.tag {
|
||||
width: 3em;
|
||||
text-align: left;
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ $hl-xl: rgba(140, 140, 140, 0.5);
|
||||
$hl-xxl: rgba(140, 140, 140, 0.7);
|
||||
$hl: rgba(140, 140, 140, 1);
|
||||
|
||||
$success: #8fc62b;
|
||||
$success: #88bd29;
|
||||
$notice: #e5df54;
|
||||
$warning: #ffb932;
|
||||
$danger: #ff6d59;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
/* Components */
|
||||
@import 'components/wrapper';
|
||||
@import 'components/requestswitcher';
|
||||
@import 'components/changelog';
|
||||
@import 'components/sidebar';
|
||||
@import 'components/pane';
|
||||
|
||||
Reference in new issue
Block a user