Request Switcher

This commit is contained in:
Gregory Schier committed 2016-07-20 11:35:08 -07:00
1 parent 1ecbe35d87
commit e1711cb96c
12 files changed
+300 -47

No files matched your search

+18 -12
View File
@@ -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"/>
]
});
}
+199 -11
View File
@@ -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
&nbsp;
<span className="monospace">↑ ↓</span> &nbsp;to navigate
&nbsp;&nbsp;&nbsp;
<span className="monospace">↵</span> &nbsp;to select
&nbsp;&nbsp;&nbsp;
<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}
&nbsp;&nbsp;
<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;
+9 -3
View File
@@ -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;
+5 -3
View File
@@ -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;
+23 -9
View File
@@ -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
View File
@@ -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>
)
+2 -4
View File
@@ -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;
}
}
+2
View File
@@ -75,6 +75,8 @@ label > .form-control,
}
}
.btn:focus:not(:disabled),
.btn.focus:not(:disabled),
.btn:hover:not(:disabled) {
background: $hl-sm;
}
+4
View File
@@ -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;
+22
View File
@@ -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;
}
}
+1 -1
View File
@@ -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;
+1
View File
@@ -14,6 +14,7 @@
/* Components */
@import 'components/wrapper';
@import 'components/requestswitcher';
@import 'components/changelog';
@import 'components/sidebar';
@import 'components/pane';