mirror of
https://github.com/Kong/insomnia
synced 2024-11-07 22:30:15 +00:00
Workspaces (#7)
* Got a hacky workspace implementation running * Removed some hax with reducer composition * Moved some more around * Moved files back out * Started on entities reducer * Split up some components * Moved nested modules back out of workspaces * Started on new Sidebar tree stuff * Better store stuff * Some more tweaks * Removed workspace update action * Re-implemented filtering in the Sidbare * Switch to get the newest response
This commit is contained in:
parent
97f579f5e5
commit
e9d64ebb23
@ -3,17 +3,17 @@ import Editor from './base/Editor'
|
|||||||
|
|
||||||
class RequestBodyEditor extends Component {
|
class RequestBodyEditor extends Component {
|
||||||
render () {
|
render () {
|
||||||
const {request, onChange, className} = this.props;
|
const {body, contentType, requestId, onChange, className} = this.props;
|
||||||
const mode = request.contentType || 'text/plain';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Editor
|
<Editor
|
||||||
value={request.body}
|
value={body}
|
||||||
className={className}
|
className={className}
|
||||||
|
debounceMillis={400}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
uniquenessKey={request._id}
|
uniquenessKey={requestId}
|
||||||
options={{
|
options={{
|
||||||
mode: mode,
|
mode: contentType,
|
||||||
placeholder: 'request body here...'
|
placeholder: 'request body here...'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -22,10 +22,13 @@ class RequestBodyEditor extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
RequestBodyEditor.propTypes = {
|
RequestBodyEditor.propTypes = {
|
||||||
request: PropTypes.shape({
|
// Functions
|
||||||
body: PropTypes.string.isRequired
|
onChange: PropTypes.func.isRequired,
|
||||||
}).isRequired,
|
|
||||||
onChange: PropTypes.func.isRequired
|
// Other
|
||||||
|
requestId: PropTypes.string.isRequired,
|
||||||
|
body: PropTypes.string.isRequired,
|
||||||
|
contentType: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RequestBodyEditor;
|
export default RequestBodyEditor;
|
||||||
|
132
app/components/RequestPane.js
Normal file
132
app/components/RequestPane.js
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import React, {Component, PropTypes} from 'react'
|
||||||
|
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs'
|
||||||
|
|
||||||
|
import KeyValueEditor from '../components/base/KeyValueEditor'
|
||||||
|
import Dropdown from '../components/base/Dropdown'
|
||||||
|
|
||||||
|
import RequestBodyEditor from '../components/RequestBodyEditor'
|
||||||
|
import RequestAuthEditor from '../components/RequestAuthEditor'
|
||||||
|
import RequestUrlBar from '../components/RequestUrlBar'
|
||||||
|
|
||||||
|
class RequestPane extends Component {
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
request,
|
||||||
|
sendRequest,
|
||||||
|
updateRequestUrl,
|
||||||
|
updateRequestMethod,
|
||||||
|
updateRequestBody,
|
||||||
|
updateRequestParams,
|
||||||
|
updateRequestAuthentication,
|
||||||
|
updateRequestHeaders
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (!request) {
|
||||||
|
return (
|
||||||
|
<section className="grid__cell section section--bordered grid--v grid--start">
|
||||||
|
<header className="header bg-super-light section__header"></header>
|
||||||
|
<div className="section__body grid__cell"></div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="grid__cell section section--bordered">
|
||||||
|
<div className="grid--v wide">
|
||||||
|
<div className="header section__header">
|
||||||
|
<RequestUrlBar
|
||||||
|
uniquenessKey={request._id}
|
||||||
|
sendRequest={() => sendRequest(request)}
|
||||||
|
onUrlChange={updateRequestUrl}
|
||||||
|
onMethodChange={updateRequestMethod}
|
||||||
|
url={request.url}
|
||||||
|
method={request.method}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Tabs className="grid__cell grid--v section__body">
|
||||||
|
<TabList className="grid grid--start">
|
||||||
|
<Tab className="no-wrap grid grid--center">
|
||||||
|
<button>JSON</button>
|
||||||
|
<Dropdown>
|
||||||
|
<button><i className="fa fa-caret-down"></i></button>
|
||||||
|
<ul>
|
||||||
|
{/*<li><button><i className="fa fa-picture-o"></i> File Upload</button></li>*/}
|
||||||
|
<li><button><i className="fa fa-bars"></i> Form Data</button></li>
|
||||||
|
<li><button><i className="fa fa-code"></i> XML</button></li>
|
||||||
|
<li><button><i className="fa fa-file-text"></i> Plain Text</button></li>
|
||||||
|
</ul>
|
||||||
|
</Dropdown>
|
||||||
|
</Tab>
|
||||||
|
<Tab>
|
||||||
|
<button className="no-wrap">
|
||||||
|
Params {request.params.length ? `(${request.params.length})` : ''}
|
||||||
|
</button>
|
||||||
|
</Tab>
|
||||||
|
<Tab>
|
||||||
|
<button className="no-wrap">
|
||||||
|
Headers {request.headers.length ? `(${request.headers.length})` : ''}
|
||||||
|
</button>
|
||||||
|
</Tab>
|
||||||
|
</TabList>
|
||||||
|
<TabPanel className="grid__cell editor-wrapper">
|
||||||
|
<RequestBodyEditor
|
||||||
|
onChange={updateRequestBody}
|
||||||
|
requestId={request._id}
|
||||||
|
contentType={request.contentType}
|
||||||
|
body={request.body}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel className="grid__cell grid__cell--scroll--v">
|
||||||
|
<div>
|
||||||
|
<KeyValueEditor
|
||||||
|
className="pad"
|
||||||
|
namePlaceholder="name"
|
||||||
|
valuePlaceholder="value"
|
||||||
|
uniquenessKey={request._id}
|
||||||
|
pairs={request.params}
|
||||||
|
onChange={updateRequestParams}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel className="grid__cell grid__cell--scroll--v">
|
||||||
|
<div>
|
||||||
|
<div className="pad">
|
||||||
|
<label>Basic Authentication</label>
|
||||||
|
<RequestAuthEditor
|
||||||
|
request={request}
|
||||||
|
onChange={updateRequestAuthentication}
|
||||||
|
/>
|
||||||
|
<br/>
|
||||||
|
<label>Other Headers</label>
|
||||||
|
<KeyValueEditor
|
||||||
|
namePlaceholder="My-Header"
|
||||||
|
valuePlaceholder="Value"
|
||||||
|
uniquenessKey={request._id}
|
||||||
|
pairs={request.headers}
|
||||||
|
onChange={updateRequestHeaders}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabPanel>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestPane.propTypes = {
|
||||||
|
// Functions
|
||||||
|
sendRequest: PropTypes.func.isRequired,
|
||||||
|
updateRequestUrl: PropTypes.func.isRequired,
|
||||||
|
updateRequestMethod: PropTypes.func.isRequired,
|
||||||
|
updateRequestBody: PropTypes.func.isRequired,
|
||||||
|
updateRequestParams: PropTypes.func.isRequired,
|
||||||
|
updateRequestAuthentication: PropTypes.func.isRequired,
|
||||||
|
updateRequestHeaders: PropTypes.func.isRequired,
|
||||||
|
|
||||||
|
// Other
|
||||||
|
request: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RequestPane;
|
@ -4,36 +4,42 @@ import Dropdown from './base/Dropdown';
|
|||||||
import {METHODS} from '../lib/constants';
|
import {METHODS} from '../lib/constants';
|
||||||
|
|
||||||
class UrlInput extends Component {
|
class UrlInput extends Component {
|
||||||
|
_handleFormSubmit (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.props.sendRequest();
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const {sendRequest, onUrlChange, onMethodChange, request} = this.props;
|
const {onUrlChange, onMethodChange, uniquenessKey, url, method} = this.props;
|
||||||
return (
|
return (
|
||||||
<div className="tall grid grid--center wide bg-super-light">
|
<div className="tall grid grid--center wide bg-super-light">
|
||||||
<Dropdown className="tall">
|
<Dropdown className="tall">
|
||||||
<button className="pad tall txt-md">
|
<button className="pad tall txt-md">
|
||||||
{request.method} <i className="fa fa-caret-down"></i>
|
{method}
|
||||||
|
<i className="fa fa-caret-down"></i>
|
||||||
</button>
|
</button>
|
||||||
<ul>
|
<ul>
|
||||||
{METHODS.map((method) => (
|
{METHODS.map(m => (
|
||||||
<li key={method}>
|
<li key={m}>
|
||||||
<button onClick={onMethodChange.bind(null, method)}>
|
<button onClick={onMethodChange.bind(null, m)}>
|
||||||
{method}
|
{m}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
<form className="tall grid__cell form-control form-control--wide"
|
<form className="tall grid__cell form-control form-control--wide"
|
||||||
onSubmit={e => {e.preventDefault(); sendRequest(request)}}>
|
onSubmit={this._handleFormSubmit.bind(this)}>
|
||||||
<DebouncingInput
|
<DebouncingInput
|
||||||
type="text"
|
type="text"
|
||||||
className="txt-md"
|
className="txt-md"
|
||||||
placeholder="http://echo.insomnia.rest/status/200"
|
placeholder="http://echo.insomnia.rest/status/200"
|
||||||
value={request.url}
|
value={url}
|
||||||
debounceMillis={1000}
|
debounceMillis={1000}
|
||||||
uniquenessKey={request._id}
|
uniquenessKey={uniquenessKey}
|
||||||
onChange={onUrlChange}/>
|
onChange={onUrlChange}/>
|
||||||
</form>
|
</form>
|
||||||
<button className="btn btn--compact txt-lg" onClick={sendRequest.bind(null, request)}>
|
<button className="btn btn--compact txt-lg" onClick={this._handleFormSubmit.bind(this)}>
|
||||||
Send
|
Send
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -45,10 +51,9 @@ UrlInput.propTypes = {
|
|||||||
sendRequest: PropTypes.func.isRequired,
|
sendRequest: PropTypes.func.isRequired,
|
||||||
onUrlChange: PropTypes.func.isRequired,
|
onUrlChange: PropTypes.func.isRequired,
|
||||||
onMethodChange: PropTypes.func.isRequired,
|
onMethodChange: PropTypes.func.isRequired,
|
||||||
request: PropTypes.shape({
|
uniquenessKey: PropTypes.string.isRequired,
|
||||||
url: PropTypes.string.isRequired,
|
url: PropTypes.string.isRequired,
|
||||||
method: PropTypes.string.isRequired
|
method: PropTypes.string.isRequired
|
||||||
}).isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default UrlInput;
|
export default UrlInput;
|
||||||
|
90
app/components/ResponsePane.js
Normal file
90
app/components/ResponsePane.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import React, {Component, PropTypes} from 'react'
|
||||||
|
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs'
|
||||||
|
|
||||||
|
import Dropdown from '../components/base/Dropdown'
|
||||||
|
import Editor from '../components/base/Editor'
|
||||||
|
import StatusTag from '../components/StatusTag'
|
||||||
|
import SizeTag from '../components/SizeTag'
|
||||||
|
import TimeTag from '../components/TimeTag'
|
||||||
|
|
||||||
|
class ResponsePane extends Component {
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
response
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (!response) {
|
||||||
|
return (
|
||||||
|
<section className="grid__cell section grid--v grid--start">
|
||||||
|
<header className="header bg-light section__header"></header>
|
||||||
|
<div className="section__body grid__cell"></div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="grid__cell section">
|
||||||
|
<div className="grid--v wide">
|
||||||
|
<header
|
||||||
|
className="grid grid--center header text-center bg-super-light txt-sm section__header">
|
||||||
|
{!response ? null : (
|
||||||
|
<div>
|
||||||
|
<StatusTag
|
||||||
|
statusCode={response.statusCode}
|
||||||
|
statusMessage={response.statusMessage}
|
||||||
|
/>
|
||||||
|
<TimeTag milliseconds={response.millis}/>
|
||||||
|
<SizeTag bytes={response.bytes}/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</header>
|
||||||
|
<Tabs className="grid__cell grid--v section__body">
|
||||||
|
<TabList className="grid grid--start">
|
||||||
|
<Tab className="no-wrap grid grid--center">
|
||||||
|
<button>Preview</button>
|
||||||
|
<Dropdown>
|
||||||
|
<button><i className="fa fa-caret-down"></i></button>
|
||||||
|
<ul>
|
||||||
|
<li><button><i className="fa fa-eye"></i> Preview</button></li>
|
||||||
|
<li><button><i className="fa fa-code"></i> Source</button></li>
|
||||||
|
<li><button><i className="fa fa-file"></i> Raw</button></li>
|
||||||
|
</ul>
|
||||||
|
</Dropdown>
|
||||||
|
</Tab>
|
||||||
|
<Tab><button>Headers</button></Tab>
|
||||||
|
</TabList>
|
||||||
|
<TabPanel className="grid__cell editor-wrapper">
|
||||||
|
<Editor
|
||||||
|
value={response && response.body || ''}
|
||||||
|
prettify={true}
|
||||||
|
options={{
|
||||||
|
mode: response && response.contentType || 'text/plain',
|
||||||
|
readOnly: true,
|
||||||
|
placeholder: 'nothing yet...'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel className="grid__cell grid__cell--scroll--v">
|
||||||
|
<div className="wide">
|
||||||
|
<div className="grid--v grid--start pad">
|
||||||
|
{!response ? null : response.headers.map((h, i) => (
|
||||||
|
<div className="grid grid__cell grid__cell--no-flex selectable" key={i}>
|
||||||
|
<div className="grid__cell">{h.name}</div>
|
||||||
|
<div className="grid__cell">{h.value}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabPanel>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponsePane.propTypes = {
|
||||||
|
response: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResponsePane;
|
@ -1,132 +1,83 @@
|
|||||||
import React, {Component, PropTypes} from 'react'
|
import React, {Component, PropTypes} from 'react'
|
||||||
import classnames from 'classnames'
|
|
||||||
import WorkspaceDropdown from './../containers/WorkspaceDropdown'
|
import WorkspaceDropdown from './../containers/WorkspaceDropdown'
|
||||||
import RequestActionsDropdown from './../containers/RequestActionsDropdown'
|
|
||||||
import RequestGroupActionsDropdown from './../containers/RequestGroupActionsDropdown'
|
|
||||||
import DebouncingInput from './base/DebouncingInput'
|
import DebouncingInput from './base/DebouncingInput'
|
||||||
import MethodTag from './MethodTag'
|
import SidebarRequestGroupRow from './SidebarRequestGroupRow'
|
||||||
import * as db from '../database'
|
import SidebarRequestRow from './SidebarRequestRow'
|
||||||
|
|
||||||
class Sidebar extends Component {
|
class Sidebar extends Component {
|
||||||
onFilterChange (value) {
|
onFilterChange (value) {
|
||||||
this.props.changeFilter(value);
|
this.props.changeFilter(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderRequestGroupRow (requestGroup = null) {
|
_filterChildren (filter, children, extra = null) {
|
||||||
const {
|
return children.filter(child => {
|
||||||
activeFilter,
|
if (child.doc.type !== 'Request') {
|
||||||
activeRequest,
|
|
||||||
addRequestToRequestGroup,
|
|
||||||
toggleRequestGroup,
|
|
||||||
requests
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
let filteredRequests = requests.filter(
|
|
||||||
r => {
|
|
||||||
// TODO: Move this to a lib file
|
|
||||||
|
|
||||||
if (!activeFilter) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestGroupName = requestGroup ? requestGroup.name : '';
|
const request = child.doc;
|
||||||
const toMatch = `${requestGroupName}✌${r.method}✌${r.name}`.toLowerCase();
|
|
||||||
const matchTokens = activeFilter.toLowerCase().split(' ');
|
const otherMatches = extra || '';
|
||||||
|
const toMatch = `${request.method}❅${request.name}❅${otherMatches}`.toLowerCase();
|
||||||
|
const matchTokens = filter.toLowerCase().split(' ');
|
||||||
|
|
||||||
for (let i = 0; i < matchTokens.length; i++) {
|
for (let i = 0; i < matchTokens.length; i++) {
|
||||||
let token = `${matchTokens[i]}`;
|
let token = `${matchTokens[i]}`;
|
||||||
if (toMatch.indexOf(token) === -1) {
|
if (toMatch.indexOf(token) === -1) {
|
||||||
|
// Filter failed. Don't render children
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
})
|
||||||
);
|
|
||||||
|
|
||||||
if (!requestGroup) {
|
|
||||||
filteredRequests = filteredRequests.filter(r => !r.parent);
|
|
||||||
return filteredRequests.map(request => this.renderRequestRow(request));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grab all of the children for this request group
|
_renderChildren (children, requestGroup) {
|
||||||
filteredRequests = filteredRequests.filter(r => r.parent === requestGroup._id);
|
const {filter} = this.props;
|
||||||
|
|
||||||
// Don't show folder if it was not in the filter
|
const filteredChildren = this._filterChildren(
|
||||||
if (activeFilter && !filteredRequests.length) {
|
filter,
|
||||||
|
children,
|
||||||
|
requestGroup && requestGroup.name
|
||||||
|
).sort((a, b) => a.doc._id > b.doc._id ? -1 : 1);
|
||||||
|
|
||||||
|
return filteredChildren.map(child => {
|
||||||
|
if (child.doc.type === 'Request') {
|
||||||
|
return (
|
||||||
|
<SidebarRequestRow
|
||||||
|
key={child.doc._id}
|
||||||
|
activateRequest={this.props.activateRequest}
|
||||||
|
isActive={child.doc._id === this.props.activeRequestId}
|
||||||
|
request={child.doc}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
} else if (child.doc.type === 'RequestGroup') {
|
||||||
|
const requestGroup = child.doc;
|
||||||
|
const isActive = !!child.children.find(c => c.doc._id === this.props.activeRequestId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarRequestGroupRow
|
||||||
|
key={requestGroup._id}
|
||||||
|
isActive={isActive}
|
||||||
|
hideIfNoChildren={filter}
|
||||||
|
toggleRequestGroup={this.props.toggleRequestGroup}
|
||||||
|
addRequestToRequestGroup={this.props.addRequestToRequestGroup}
|
||||||
|
numChildren={child.children.length}
|
||||||
|
requestGroup={requestGroup}
|
||||||
|
>
|
||||||
|
{this._renderChildren(child.children, requestGroup)}
|
||||||
|
</SidebarRequestGroupRow>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
console.error('Unknown child type', child.doc.type);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
})
|
||||||
const isActive = activeRequest && filteredRequests.find(r => r._id == activeRequest._id);
|
|
||||||
|
|
||||||
let folderIconClass = 'fa-folder';
|
|
||||||
let expanded = !requestGroup.collapsed;
|
|
||||||
folderIconClass += !expanded ? '' : '-open';
|
|
||||||
folderIconClass += isActive ? '' : '-o';
|
|
||||||
|
|
||||||
const sidebarItemClassNames = classnames(
|
|
||||||
'sidebar__item',
|
|
||||||
'sidebar__item--bordered',
|
|
||||||
{'sidebar__item--active': isActive}
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li key={requestGroup._id}>
|
|
||||||
<div className={sidebarItemClassNames}>
|
|
||||||
<div className="sidebar__item__row sidebar__item__row--heading">
|
|
||||||
<button onClick={e => toggleRequestGroup(requestGroup)}>
|
|
||||||
<i className={'fa ' + folderIconClass}></i>
|
|
||||||
{requestGroup.name}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="sidebar__item__btn grid">
|
|
||||||
<button onClick={(e) => addRequestToRequestGroup(requestGroup)}>
|
|
||||||
<i className="fa fa-plus-circle"></i>
|
|
||||||
</button>
|
|
||||||
<RequestGroupActionsDropdown
|
|
||||||
requestGroup={requestGroup}
|
|
||||||
right={true}
|
|
||||||
className="tall"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul>
|
|
||||||
{expanded && !filteredRequests.length ? this.renderRequestRow() : null}
|
|
||||||
{!expanded ? null : filteredRequests.map(request => this.renderRequestRow(request, requestGroup))}
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderRequestRow (request = null, requestGroup = null) {
|
|
||||||
const {activeRequest, activateRequest} = this.props;
|
|
||||||
const isActive = request && activeRequest && request._id === activeRequest._id;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li key={request ? request._id : 'none'}>
|
|
||||||
<div className={'sidebar__item ' + (isActive ? 'sidebar__item--active' : '')}>
|
|
||||||
<div className="sidebar__item__row">
|
|
||||||
{request ? (
|
|
||||||
<button onClick={() => {activateRequest(request)}}>
|
|
||||||
<MethodTag method={request.method}/> {request.name}
|
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<button className="italic">No Requests</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{request ? (
|
|
||||||
<RequestActionsDropdown
|
|
||||||
className="sidebar__item__btn"
|
|
||||||
right={true}
|
|
||||||
request={request}
|
|
||||||
requestGroup={requestGroup}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const {activeFilter, requestGroups} = this.props;
|
const {filter, children} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="sidebar bg-dark grid--v section section--bordered">
|
<section className="sidebar bg-dark grid--v section section--bordered">
|
||||||
@ -136,8 +87,7 @@ class Sidebar extends Component {
|
|||||||
<div className="grid--v grid--start grid__cell section__body">
|
<div className="grid--v grid--start grid__cell section__body">
|
||||||
<ul
|
<ul
|
||||||
className="grid--v grid--start grid__cell sidebar__scroll hover-scrollbars sidebar__request-list">
|
className="grid--v grid--start grid__cell sidebar__scroll hover-scrollbars sidebar__request-list">
|
||||||
{this.renderRequestGroupRow(null)}
|
{this._renderChildren(children)}
|
||||||
{requestGroups.map(requestGroup => this.renderRequestGroupRow(requestGroup))}
|
|
||||||
</ul>
|
</ul>
|
||||||
<div className="grid grid--center">
|
<div className="grid grid--center">
|
||||||
<div className="grid__cell form-control form-control--underlined">
|
<div className="grid__cell form-control form-control--underlined">
|
||||||
@ -145,7 +95,7 @@ class Sidebar extends Component {
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="Filter Items"
|
placeholder="Filter Items"
|
||||||
debounceMillis={300}
|
debounceMillis={300}
|
||||||
value={activeFilter}
|
value={filter}
|
||||||
onChange={this.onFilterChange.bind(this)}/>
|
onChange={this.onFilterChange.bind(this)}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -156,14 +106,19 @@ class Sidebar extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Sidebar.propTypes = {
|
Sidebar.propTypes = {
|
||||||
|
// Functions
|
||||||
activateRequest: PropTypes.func.isRequired,
|
activateRequest: PropTypes.func.isRequired,
|
||||||
|
toggleRequestGroup: PropTypes.func.isRequired,
|
||||||
addRequestToRequestGroup: PropTypes.func.isRequired,
|
addRequestToRequestGroup: PropTypes.func.isRequired,
|
||||||
changeFilter: PropTypes.func.isRequired,
|
changeFilter: PropTypes.func.isRequired,
|
||||||
toggleRequestGroup: PropTypes.func.isRequired,
|
|
||||||
activeFilter: PropTypes.string,
|
// Other
|
||||||
requests: PropTypes.array.isRequired,
|
children: PropTypes.array.isRequired,
|
||||||
requestGroups: PropTypes.array.isRequired,
|
workspaceId: PropTypes.string.isRequired,
|
||||||
activeRequest: PropTypes.object
|
|
||||||
|
// Optional
|
||||||
|
filter: PropTypes.string,
|
||||||
|
activeRequestId: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Sidebar;
|
export default Sidebar;
|
||||||
|
79
app/components/SidebarRequestGroupRow.js
Normal file
79
app/components/SidebarRequestGroupRow.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import React, {Component, PropTypes} from 'react'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
import RequestGroupActionsDropdown from './../containers/RequestGroupActionsDropdown'
|
||||||
|
import SidebarRequestRow from './SidebarRequestRow'
|
||||||
|
|
||||||
|
class SidebarRequestGroupRow extends Component {
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
children,
|
||||||
|
hideIfNoChildren,
|
||||||
|
requestGroup,
|
||||||
|
isActive,
|
||||||
|
toggleRequestGroup,
|
||||||
|
addRequestToRequestGroup
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
// If we are supposed to have children, but aren't passed any, we are probably
|
||||||
|
// filtering so don't render anything
|
||||||
|
if (hideIfNoChildren && children.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let folderIconClass = 'fa-folder';
|
||||||
|
let expanded = !requestGroup.collapsed;
|
||||||
|
folderIconClass += !expanded ? '' : '-open';
|
||||||
|
folderIconClass += isActive ? '' : '-o';
|
||||||
|
|
||||||
|
const sidebarItemClassNames = classnames(
|
||||||
|
'sidebar__item',
|
||||||
|
'sidebar__item--bordered',
|
||||||
|
{'sidebar__item--active': isActive}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={requestGroup._id}>
|
||||||
|
<div className={sidebarItemClassNames}>
|
||||||
|
<div className="sidebar__item__row sidebar__item__row--heading">
|
||||||
|
<button onClick={e => toggleRequestGroup(requestGroup)}>
|
||||||
|
<i className={'fa ' + folderIconClass}></i>
|
||||||
|
{requestGroup.name}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="sidebar__item__btn grid">
|
||||||
|
<button onClick={(e) => addRequestToRequestGroup(requestGroup)}>
|
||||||
|
<i className="fa fa-plus-circle"></i>
|
||||||
|
</button>
|
||||||
|
<RequestGroupActionsDropdown
|
||||||
|
requestGroup={requestGroup}
|
||||||
|
right={true}
|
||||||
|
className="tall"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
{!expanded || children.length > 0 ? null : (
|
||||||
|
<SidebarRequestRow
|
||||||
|
activateRequest={() => {}}
|
||||||
|
isActive={false}
|
||||||
|
request={null}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{expanded ? children : null}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SidebarRequestGroupRow.propTypes = {
|
||||||
|
// Functions
|
||||||
|
toggleRequestGroup: PropTypes.func.isRequired,
|
||||||
|
addRequestToRequestGroup: PropTypes.func.isRequired,
|
||||||
|
|
||||||
|
// Other
|
||||||
|
isActive: PropTypes.bool.isRequired,
|
||||||
|
hideIfNoChildren: PropTypes.number.isRequired,
|
||||||
|
requestGroup: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SidebarRequestGroupRow;
|
47
app/components/SidebarRequestRow.js
Normal file
47
app/components/SidebarRequestRow.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import React, {Component, PropTypes} from 'react'
|
||||||
|
import RequestActionsDropdown from './../containers/RequestActionsDropdown'
|
||||||
|
import MethodTag from './MethodTag'
|
||||||
|
|
||||||
|
class SidebarRequestRow extends Component {
|
||||||
|
render () {
|
||||||
|
const {request, requestGroup, isActive, activateRequest} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={request ? request._id : 'none'}>
|
||||||
|
<div className={'sidebar__item ' + (isActive ? 'sidebar__item--active' : '')}>
|
||||||
|
<div className="sidebar__item__row">
|
||||||
|
{request ? (
|
||||||
|
<button onClick={() => {activateRequest(request)}}>
|
||||||
|
<MethodTag method={request.method}/> {request.name}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button className="italic">No Requests</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{request ? (
|
||||||
|
<RequestActionsDropdown
|
||||||
|
className="sidebar__item__btn"
|
||||||
|
right={true}
|
||||||
|
request={request}
|
||||||
|
requestGroup={requestGroup}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SidebarRequestRow.propTypes = {
|
||||||
|
// Functions
|
||||||
|
activateRequest: PropTypes.func.isRequired,
|
||||||
|
|
||||||
|
// Other
|
||||||
|
isActive: PropTypes.bool.isRequired,
|
||||||
|
|
||||||
|
// Optional
|
||||||
|
requestGroup: PropTypes.object,
|
||||||
|
request: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SidebarRequestRow;
|
@ -1,31 +1,20 @@
|
|||||||
import React, {Component, PropTypes} from 'react'
|
import React, {Component, PropTypes} from 'react'
|
||||||
import {connect} from 'react-redux'
|
import {connect} from 'react-redux'
|
||||||
import {bindActionCreators} from 'redux'
|
import {bindActionCreators} from 'redux'
|
||||||
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs'
|
|
||||||
|
|
||||||
import Editor from '../components/base/Editor'
|
|
||||||
import Prompts from './Prompts'
|
import Prompts from './Prompts'
|
||||||
import KeyValueEditor from '../components/base/KeyValueEditor'
|
|
||||||
import RequestBodyEditor from '../components/RequestBodyEditor'
|
|
||||||
import RequestAuthEditor from '../components/RequestAuthEditor'
|
|
||||||
import RequestUrlBar from '../components/RequestUrlBar'
|
|
||||||
import StatusTag from '../components/StatusTag'
|
|
||||||
import SizeTag from '../components/SizeTag'
|
|
||||||
import TimeTag from '../components/TimeTag'
|
|
||||||
import Sidebar from '../components/Sidebar'
|
|
||||||
import EnvironmentEditModal from '../components/EnvironmentEditModal'
|
import EnvironmentEditModal from '../components/EnvironmentEditModal'
|
||||||
|
import RequestPane from '../components/RequestPane'
|
||||||
|
import ResponsePane from '../components/ResponsePane'
|
||||||
|
import Sidebar from '../components/Sidebar'
|
||||||
|
|
||||||
import * as GlobalActions from '../redux/modules/global'
|
import * as GlobalActions from '../redux/modules/global'
|
||||||
import * as RequestGroupActions from '../redux/modules/requestGroups'
|
import * as RequestGroupActions from '../redux/modules/requestGroups'
|
||||||
import * as RequestActions from '../redux/modules/requests'
|
import * as RequestActions from '../redux/modules/requests'
|
||||||
import * as ModalActions from '../redux/modules/modals'
|
import * as ModalActions from '../redux/modules/modals'
|
||||||
import * as TabActions from '../redux/modules/tabs'
|
|
||||||
|
|
||||||
import * as db from '../database'
|
import * as db from '../database'
|
||||||
|
|
||||||
// Don't inject component styles (use our own)
|
|
||||||
Tabs.setUseDefaultStyles(false);
|
|
||||||
|
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -35,183 +24,74 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderRequestPanel (actions, activeRequest, tabs) {
|
_generateSidebarTree (parentId, entities) {
|
||||||
if (!activeRequest) {
|
const children = entities.filter(e => e.parentId === parentId);
|
||||||
return (
|
|
||||||
<section className="grid__cell section section--bordered grid--v grid--start">
|
|
||||||
<header className="header bg-super-light section__header"></header>
|
|
||||||
<div className="section__body grid__cell"></div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
if (children.length > 0) {
|
||||||
<section className="grid__cell section section--bordered">
|
return children.map(c => ({
|
||||||
<div className="grid--v wide">
|
doc: c,
|
||||||
<div className="header section__header">
|
children: this._generateSidebarTree(c._id, entities)
|
||||||
<RequestUrlBar
|
}));
|
||||||
sendRequest={actions.requests.send}
|
} else {
|
||||||
onUrlChange={url => {db.update(activeRequest, {url})}}
|
return children;
|
||||||
onMethodChange={method => {db.update(activeRequest, {method})}}
|
|
||||||
request={activeRequest}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Tabs className="grid__cell grid--v section__body"
|
|
||||||
onSelect={i => actions.tabs.select('request', i)}
|
|
||||||
selectedIndex={tabs.request || 0}>
|
|
||||||
<TabList className="grid grid--start">
|
|
||||||
<Tab><button>Body</button></Tab>
|
|
||||||
<Tab>
|
|
||||||
<button className="no-wrap">
|
|
||||||
Params {activeRequest.params.length ? `(${activeRequest.params.length})` : ''}
|
|
||||||
</button>
|
|
||||||
</Tab>
|
|
||||||
<Tab><button>Auth</button></Tab>
|
|
||||||
<Tab>
|
|
||||||
<button className="no-wrap">
|
|
||||||
Headers {activeRequest.headers.length ? `(${activeRequest.headers.length})` : ''}
|
|
||||||
</button>
|
|
||||||
</Tab>
|
|
||||||
</TabList>
|
|
||||||
<TabPanel className="grid__cell editor-wrapper">
|
|
||||||
<RequestBodyEditor
|
|
||||||
onChange={body => {db.update(activeRequest, {body})}}
|
|
||||||
request={activeRequest}/>
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel className="grid__cell grid__cell--scroll--v">
|
|
||||||
<div>
|
|
||||||
<KeyValueEditor
|
|
||||||
className="pad"
|
|
||||||
namePlaceholder="name"
|
|
||||||
valuePlaceholder="value"
|
|
||||||
uniquenessKey={activeRequest._id}
|
|
||||||
pairs={activeRequest.params}
|
|
||||||
onChange={params => {db.update(activeRequest, {params})}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel className="grid__cell grid__cell--scroll--v">
|
|
||||||
<div>
|
|
||||||
<RequestAuthEditor
|
|
||||||
className="pad"
|
|
||||||
request={activeRequest}
|
|
||||||
onChange={authentication => {db.update(activeRequest, {authentication})}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel className="grid__cell grid__cell--scroll--v">
|
|
||||||
<div>
|
|
||||||
<KeyValueEditor
|
|
||||||
className="pad"
|
|
||||||
namePlaceholder="My-Header"
|
|
||||||
valuePlaceholder="Value"
|
|
||||||
uniquenessKey={activeRequest._id}
|
|
||||||
pairs={activeRequest.headers}
|
|
||||||
onChange={headers => {db.update(activeRequest, {headers})}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</TabPanel>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderResponsePanel (actions, activeResponse, tabs) {
|
|
||||||
if (!activeResponse) {
|
|
||||||
return (
|
|
||||||
<section className="grid__cell section grid--v grid--start">
|
|
||||||
<header className="header bg-light section__header"></header>
|
|
||||||
<div className="section__body grid__cell"></div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="grid__cell section">
|
|
||||||
<div className="grid--v wide">
|
|
||||||
<header
|
|
||||||
className="grid grid--center header text-center bg-super-light txt-sm section__header">
|
|
||||||
{!activeResponse ? null : (
|
|
||||||
<div>
|
|
||||||
<StatusTag
|
|
||||||
statusCode={activeResponse.statusCode}
|
|
||||||
statusMessage={activeResponse.statusMessage}
|
|
||||||
/>
|
|
||||||
<TimeTag milliseconds={activeResponse.millis}/>
|
|
||||||
<SizeTag bytes={activeResponse.bytes}/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</header>
|
|
||||||
<Tabs className="grid__cell grid--v section__body"
|
|
||||||
onSelect={i => actions.tabs.select('response', i)}
|
|
||||||
selectedIndex={tabs.response || 0}>
|
|
||||||
<TabList className="grid grid--start">
|
|
||||||
<Tab><button>Preview</button></Tab>
|
|
||||||
<Tab><button>Raw</button></Tab>
|
|
||||||
<Tab><button>Headers</button></Tab>
|
|
||||||
</TabList>
|
|
||||||
<TabPanel className="grid__cell editor-wrapper">
|
|
||||||
<Editor
|
|
||||||
value={activeResponse && activeResponse.body || ''}
|
|
||||||
prettify={true}
|
|
||||||
options={{
|
|
||||||
mode: activeResponse && activeResponse.contentType || 'text/plain',
|
|
||||||
readOnly: true,
|
|
||||||
placeholder: 'nothing yet...'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel className="grid__cell editor-wrapper">
|
|
||||||
<Editor
|
|
||||||
value={activeResponse && activeResponse.body || ''}
|
|
||||||
options={{
|
|
||||||
lineWrapping: true,
|
|
||||||
mode: 'text/plain',
|
|
||||||
readOnly: true,
|
|
||||||
placeholder: 'nothing yet...'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel className="grid__cell grid__cell--scroll--v">
|
|
||||||
<div className="wide">
|
|
||||||
<div className="grid--v grid--start pad">
|
|
||||||
{!activeResponse ? null : activeResponse.headers.map((h, i) => (
|
|
||||||
<div className="grid grid__cell grid__cell--no-flex selectable" key={i}>
|
|
||||||
<div className="grid__cell">{h.name}</div>
|
|
||||||
<div className="grid__cell">{h.value}</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</TabPanel>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const {actions, requests, responses, requestGroups, tabs, modals} = this.props;
|
const {actions, modals, workspaces, requests, entities} = this.props;
|
||||||
const activeRequest = requests.all.find(r => r._id === requests.active);
|
|
||||||
const activeResponse = activeRequest ? responses[activeRequest._id] : undefined;
|
// TODO: Factor this out into a selector
|
||||||
|
let workspace = entities.workspaces[workspaces.activeId];
|
||||||
|
if (!workspace) {
|
||||||
|
workspace = entities.workspaces[Object.keys(entities.workspaces)[0]];
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeRequestId = workspace.activeRequestId;
|
||||||
|
const activeRequest = activeRequestId ? entities.requests[activeRequestId] : null;
|
||||||
|
|
||||||
|
const responses = Object.keys(entities.responses).map(id => entities.responses[id]);
|
||||||
|
const allRequests = Object.keys(entities.requests).map(id => entities.requests[id]);
|
||||||
|
const allRequestGroups = Object.keys(entities.requestGroups).map(id => entities.requestGroups[id]);
|
||||||
|
|
||||||
|
const activeResponse = responses.sort(
|
||||||
|
(a, b) => a._id > b._id ? -1 : 1
|
||||||
|
).find(r => r.parentId === activeRequestId);
|
||||||
|
|
||||||
|
const children = this._generateSidebarTree(
|
||||||
|
workspace._id,
|
||||||
|
allRequests.concat(allRequestGroups)
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid bg-super-dark tall">
|
<div className="grid bg-super-dark tall">
|
||||||
<Sidebar
|
<Sidebar
|
||||||
activateRequest={actions.requests.activate}
|
workspaceId={workspace._id}
|
||||||
|
activateRequest={r => db.update(workspace, {activeRequestId: r._id})}
|
||||||
changeFilter={actions.requests.changeFilter}
|
changeFilter={actions.requests.changeFilter}
|
||||||
addRequestToRequestGroup={requestGroup => db.requestCreate({parent: requestGroup._id})}
|
addRequestToRequestGroup={requestGroup => db.requestCreate({parentId: requestGroup._id})}
|
||||||
toggleRequestGroup={requestGroup => db.update(requestGroup, {collapsed: !requestGroup.collapsed})}
|
toggleRequestGroup={requestGroup => db.update(requestGroup, {collapsed: !requestGroup.collapsed})}
|
||||||
activeRequest={activeRequest}
|
activeRequestId={activeRequest ? activeRequest._id : null}
|
||||||
activeFilter={requests.filter}
|
filter={requests.filter}
|
||||||
requestGroups={requestGroups.all}
|
children={children}
|
||||||
requests={requests.all}/>
|
/>
|
||||||
<div className="grid wide grid--collapse">
|
<div className="grid wide grid--collapse">
|
||||||
{this._renderRequestPanel(actions, activeRequest, tabs)}
|
<RequestPane
|
||||||
{this._renderResponsePanel(actions, activeResponse, tabs)}
|
request={activeRequest}
|
||||||
|
sendRequest={actions.requests.send}
|
||||||
|
updateRequestBody={body => db.update(activeRequest, {body})}
|
||||||
|
updateRequestUrl={url => db.update(activeRequest, {url})}
|
||||||
|
updateRequestMethod={method => db.update(activeRequest, {method})}
|
||||||
|
updateRequestParams={params => db.update(activeRequest, {params})}
|
||||||
|
updateRequestAuthentication={authentication => db.update(activeRequest, {authentication})}
|
||||||
|
updateRequestHeaders={headers => db.update(activeRequest, {headers})}
|
||||||
|
/>
|
||||||
|
<ResponsePane
|
||||||
|
response={activeResponse}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Prompts />
|
<Prompts />
|
||||||
|
|
||||||
{modals.map(m => {
|
{modals.map(m => {
|
||||||
if (m.id === EnvironmentEditModal.defaultProps.id) {
|
if (m.id === EnvironmentEditModal.defaultProps.id) {
|
||||||
return (
|
return (
|
||||||
@ -234,43 +114,36 @@ class App extends Component {
|
|||||||
App.propTypes = {
|
App.propTypes = {
|
||||||
actions: PropTypes.shape({
|
actions: PropTypes.shape({
|
||||||
requests: PropTypes.shape({
|
requests: PropTypes.shape({
|
||||||
activate: PropTypes.func.isRequired,
|
|
||||||
update: PropTypes.func.isRequired,
|
|
||||||
remove: PropTypes.func.isRequired,
|
|
||||||
send: PropTypes.func.isRequired,
|
send: PropTypes.func.isRequired,
|
||||||
changeFilter: PropTypes.func.isRequired
|
changeFilter: PropTypes.func.isRequired
|
||||||
}),
|
}),
|
||||||
requestGroups: PropTypes.shape({
|
requestGroups: PropTypes.shape({
|
||||||
remove: PropTypes.func.isRequired,
|
|
||||||
update: PropTypes.func.isRequired,
|
|
||||||
toggle: PropTypes.func.isRequired
|
toggle: PropTypes.func.isRequired
|
||||||
}),
|
}),
|
||||||
modals: PropTypes.shape({
|
modals: PropTypes.shape({
|
||||||
hide: PropTypes.func.isRequired
|
hide: PropTypes.func.isRequired
|
||||||
}),
|
|
||||||
tabs: PropTypes.shape({
|
|
||||||
select: PropTypes.func.isRequired
|
|
||||||
})
|
})
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
requestGroups: PropTypes.shape({
|
entities: PropTypes.shape({
|
||||||
all: PropTypes.array.isRequired
|
requests: PropTypes.object.isRequired,
|
||||||
|
requestGroups: PropTypes.object.isRequired,
|
||||||
|
responses: PropTypes.object.isRequired
|
||||||
|
}).isRequired,
|
||||||
|
workspaces: PropTypes.shape({
|
||||||
|
activeId: PropTypes.string
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
requests: PropTypes.shape({
|
requests: PropTypes.shape({
|
||||||
all: PropTypes.array.isRequired,
|
filter: PropTypes.string.isRequired
|
||||||
active: PropTypes.string // "required" but can be null
|
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
responses: PropTypes.object.isRequired,
|
|
||||||
tabs: PropTypes.object.isRequired,
|
|
||||||
modals: PropTypes.array.isRequired
|
modals: PropTypes.array.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
return {
|
return {
|
||||||
actions: state.actions,
|
actions: state.actions,
|
||||||
|
workspaces: state.workspaces,
|
||||||
requests: state.requests,
|
requests: state.requests,
|
||||||
requestGroups: state.requestGroups,
|
entities: state.entities,
|
||||||
responses: state.responses,
|
|
||||||
tabs: state.tabs,
|
|
||||||
modals: state.modals
|
modals: state.modals
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -279,7 +152,6 @@ function mapDispatchToProps (dispatch) {
|
|||||||
return {
|
return {
|
||||||
actions: {
|
actions: {
|
||||||
global: bindActionCreators(GlobalActions, dispatch),
|
global: bindActionCreators(GlobalActions, dispatch),
|
||||||
tabs: bindActionCreators(TabActions, dispatch),
|
|
||||||
modals: bindActionCreators(ModalActions, dispatch),
|
modals: bindActionCreators(ModalActions, dispatch),
|
||||||
requestGroups: bindActionCreators(RequestGroupActions, dispatch),
|
requestGroups: bindActionCreators(RequestGroupActions, dispatch),
|
||||||
requests: bindActionCreators(RequestActions, dispatch)
|
requests: bindActionCreators(RequestActions, dispatch)
|
||||||
|
@ -3,12 +3,14 @@ import {connect} from 'react-redux'
|
|||||||
import {bindActionCreators} from 'redux'
|
import {bindActionCreators} from 'redux'
|
||||||
|
|
||||||
import * as ModalActions from '../redux/modules/modals'
|
import * as ModalActions from '../redux/modules/modals'
|
||||||
import * as RequestGroupActions from '../redux/modules/requestGroups'
|
|
||||||
import * as RequestActions from '../redux/modules/requests'
|
|
||||||
import PromptModal from '../components/base/PromptModal'
|
import PromptModal from '../components/base/PromptModal'
|
||||||
|
|
||||||
import * as db from '../database'
|
import * as db from '../database'
|
||||||
import {MODAL_REQUEST_RENAME, MODAL_REQUEST_GROUP_RENAME} from '../lib/constants';
|
import {
|
||||||
|
MODAL_REQUEST_RENAME,
|
||||||
|
MODAL_REQUEST_GROUP_RENAME,
|
||||||
|
MODAL_WORKSPACE_RENAME
|
||||||
|
} from '../lib/constants';
|
||||||
|
|
||||||
class Prompts extends Component {
|
class Prompts extends Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@ -29,6 +31,14 @@ class Prompts extends Component {
|
|||||||
db.update(modal.data.requestGroup, {name})
|
db.update(modal.data.requestGroup, {name})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this._prompts[MODAL_WORKSPACE_RENAME] = {
|
||||||
|
header: 'Rename Workspace',
|
||||||
|
submit: 'Rename',
|
||||||
|
onSubmit: (modal, name) => {
|
||||||
|
db.update(modal.data.workspace, {name})
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
@ -65,12 +75,6 @@ Prompts.propTypes = {
|
|||||||
actions: PropTypes.shape({
|
actions: PropTypes.shape({
|
||||||
modals: PropTypes.shape({
|
modals: PropTypes.shape({
|
||||||
hide: PropTypes.func.isRequired
|
hide: PropTypes.func.isRequired
|
||||||
}),
|
|
||||||
requestGroups: PropTypes.shape({
|
|
||||||
update: PropTypes.func.isRequired
|
|
||||||
}),
|
|
||||||
requests: PropTypes.shape({
|
|
||||||
update: PropTypes.func.isRequired
|
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
modals: PropTypes.array.isRequired
|
modals: PropTypes.array.isRequired
|
||||||
@ -86,9 +90,7 @@ function mapStateToProps (state) {
|
|||||||
function mapDispatchToProps (dispatch) {
|
function mapDispatchToProps (dispatch) {
|
||||||
return {
|
return {
|
||||||
actions: {
|
actions: {
|
||||||
requests: bindActionCreators(RequestActions, dispatch),
|
modals: bindActionCreators(ModalActions, dispatch)
|
||||||
modals: bindActionCreators(ModalActions, dispatch),
|
|
||||||
requestGroups: bindActionCreators(RequestGroupActions, dispatch)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ class RequestActionsDropdown extends Component {
|
|||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<button onClick={e => db.requestCopy(request)}>
|
<button onClick={e => db.requestCopy(request)}>
|
||||||
<i className="fa fa-copy"></i> Duplicate
|
<i className="fa fa-copy"></i> Clone
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
@ -38,8 +38,6 @@ class RequestGroupActionsDropdown extends Component {
|
|||||||
|
|
||||||
RequestGroupActionsDropdown.propTypes = {
|
RequestGroupActionsDropdown.propTypes = {
|
||||||
actions: PropTypes.shape({
|
actions: PropTypes.shape({
|
||||||
update: PropTypes.func.isRequired,
|
|
||||||
remove: PropTypes.func.isRequired,
|
|
||||||
showUpdateNamePrompt: PropTypes.func.isRequired,
|
showUpdateNamePrompt: PropTypes.func.isRequired,
|
||||||
showEnvironmentEditModal: PropTypes.func.isRequired
|
showEnvironmentEditModal: PropTypes.func.isRequired
|
||||||
}),
|
}),
|
||||||
|
@ -8,6 +8,7 @@ import {connect} from 'react-redux'
|
|||||||
import Dropdown from '../components/base/Dropdown'
|
import Dropdown from '../components/base/Dropdown'
|
||||||
import DropdownDivider from '../components/base/DropdownDivider'
|
import DropdownDivider from '../components/base/DropdownDivider'
|
||||||
import * as RequestGroupActions from '../redux/modules/requestGroups'
|
import * as RequestGroupActions from '../redux/modules/requestGroups'
|
||||||
|
import * as WorkspaceActions from '../redux/modules/workspaces'
|
||||||
import * as db from '../database'
|
import * as db from '../database'
|
||||||
import importData from '../lib/import'
|
import importData from '../lib/import'
|
||||||
|
|
||||||
@ -20,24 +21,45 @@ class WorkspaceDropdown extends Component {
|
|||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Factor this out into a selector
|
||||||
|
const {entities, workspaces} = this.props;
|
||||||
|
let workspace = entities.workspaces[workspaces.activeId];
|
||||||
|
if (!workspace) {
|
||||||
|
workspace = entities.workspaces[Object.keys(entities.workspaces)[0]];
|
||||||
|
}
|
||||||
|
|
||||||
electron.remote.dialog.showOpenDialog(options, paths => {
|
electron.remote.dialog.showOpenDialog(options, paths => {
|
||||||
paths.map(path => {
|
paths.map(path => {
|
||||||
fs.readFile(path, 'utf8', (err, data) => {
|
fs.readFile(path, 'utf8', (err, data) => {
|
||||||
err || importData(data);
|
err || importData(workspace, data);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_workspaceCreate () {
|
||||||
|
db.workspaceCreate({name: 'New Workspace'}).then(workspace => {
|
||||||
|
this.props.actions.workspaces.activate(workspace);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const {actions, loading, ...other} = this.props;
|
const {actions, loading, workspaces, entities, ...other} = this.props;
|
||||||
|
|
||||||
|
const allWorkspaces = Object.keys(entities.workspaces).map(id => entities.workspaces[id]);
|
||||||
|
|
||||||
|
// TODO: Factor this out into a selector
|
||||||
|
let workspace = entities.workspaces[workspaces.activeId];
|
||||||
|
if (!workspace) {
|
||||||
|
workspace = entities.workspaces[Object.keys(entities.workspaces)[0]];
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown right={true} {...other} className="block">
|
<Dropdown right={true} {...other} className="block">
|
||||||
<button className="btn header__content">
|
<button className="btn header__content">
|
||||||
<div className="grid grid--center">
|
<div className="grid grid--center">
|
||||||
<div className="grid__cell">
|
<div className="grid__cell">
|
||||||
<h1 className="no-pad">Insomnia</h1>
|
<h1 className="no-pad">{workspace.name}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="no-wrap">
|
<div className="no-wrap">
|
||||||
{loading ? <i className="fa fa-refresh fa-spin txt-lg"></i> : ''}
|
{loading ? <i className="fa fa-refresh fa-spin txt-lg"></i> : ''}
|
||||||
@ -50,45 +72,49 @@ class WorkspaceDropdown extends Component {
|
|||||||
<DropdownDivider name="Current Workspace"/>
|
<DropdownDivider name="Current Workspace"/>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<button onClick={e => db.requestCreate()}>
|
<button onClick={e => db.requestCreate({parentId: workspace._id})}>
|
||||||
<i className="fa fa-plus-circle"></i> New Request
|
<i className="fa fa-plus-circle"></i> New Request
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button onClick={e => db.requestGroupCreate()}>
|
<button onClick={e => db.requestGroupCreate({parentId: workspace._id})}>
|
||||||
<i className="fa fa-folder"></i> New Request Group
|
<i className="fa fa-folder"></i> New Request Group
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
{/*<li>
|
||||||
<button onClick={e => actions.showEnvironmentEditModal()}>
|
<button onClick={e => actions.requestGroups.showEnvironmentEditModal()}>
|
||||||
<i className="fa fa-code"></i> Manage Environments
|
<i className="fa fa-code"></i> Manage Environments
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>*/}
|
||||||
<li>
|
<li>
|
||||||
<button onClick={e => this._importDialog()}>
|
<button onClick={e => this._importDialog()}>
|
||||||
<i className="fa fa-share-square-o"></i> Import/Export
|
<i className="fa fa-share-square-o"></i> Import/Export
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button>
|
<button onClick={e => actions.workspaces.showUpdateNamePrompt(workspace)}>
|
||||||
<i className="fa fa-empty"></i> Delete <strong>Sendwithus</strong>
|
<i className="fa fa-empty"></i> Rename <strong>{workspace.name}</strong>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button onClick={e => db.remove(workspace)}>
|
||||||
|
<i className="fa fa-empty"></i> Delete <strong>{workspace.name}</strong>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<DropdownDivider name="Workspaces"/>
|
<DropdownDivider name="Workspaces"/>
|
||||||
|
|
||||||
<li>
|
{allWorkspaces.map(w => {
|
||||||
<button>
|
return w._id === workspace._id ? null : (
|
||||||
<i className="fa fa-random"></i> Switch to <strong>Sendwithus Track</strong>
|
<li key={w._id}>
|
||||||
|
<button onClick={() => actions.workspaces.activate(w)}>
|
||||||
|
<i className="fa fa-random"></i> Switch to <strong>{w.name}</strong>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
<li>
|
<li>
|
||||||
<button>
|
<button onClick={e => this._workspaceCreate()}>
|
||||||
<i className="fa fa-random"></i> Switch to <strong>Default</strong>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<button>
|
|
||||||
<i className="fa fa-blank"></i> Create Workspace
|
<i className="fa fa-blank"></i> Create Workspace
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
@ -105,13 +131,27 @@ class WorkspaceDropdown extends Component {
|
|||||||
|
|
||||||
WorkspaceDropdown.propTypes = {
|
WorkspaceDropdown.propTypes = {
|
||||||
loading: PropTypes.bool.isRequired,
|
loading: PropTypes.bool.isRequired,
|
||||||
|
workspaces: PropTypes.shape({
|
||||||
|
activeId: PropTypes.string
|
||||||
|
}),
|
||||||
|
entities: PropTypes.shape({
|
||||||
|
workspaces: PropTypes.object.isRequired
|
||||||
|
}).isRequired,
|
||||||
actions: PropTypes.shape({
|
actions: PropTypes.shape({
|
||||||
|
requestGroups: PropTypes.shape({
|
||||||
showEnvironmentEditModal: PropTypes.func.isRequired
|
showEnvironmentEditModal: PropTypes.func.isRequired
|
||||||
|
}),
|
||||||
|
workspaces: PropTypes.shape({
|
||||||
|
activate: PropTypes.func.isRequired,
|
||||||
|
showUpdateNamePrompt: PropTypes.func.isRequired
|
||||||
|
})
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
return {
|
return {
|
||||||
|
workspaces: state.workspaces,
|
||||||
|
entities: state.entities,
|
||||||
actions: state.actions,
|
actions: state.actions,
|
||||||
loading: state.global.loading
|
loading: state.global.loading
|
||||||
};
|
};
|
||||||
@ -119,7 +159,10 @@ function mapStateToProps (state) {
|
|||||||
|
|
||||||
function mapDispatchToProps (dispatch) {
|
function mapDispatchToProps (dispatch) {
|
||||||
return {
|
return {
|
||||||
actions: bindActionCreators(RequestGroupActions, dispatch)
|
actions: {
|
||||||
|
requestGroups: bindActionCreators(RequestGroupActions, dispatch),
|
||||||
|
workspaces: bindActionCreators(WorkspaceActions, dispatch)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
border-radius: $radius-md;
|
border-radius: $radius-md;
|
||||||
background: $bg-super-light;
|
background: $bg-super-light;
|
||||||
padding: $padding-xs;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
@ -29,7 +28,7 @@
|
|||||||
padding: $padding-sm $padding-md $padding-sm $padding-sm;
|
padding: $padding-sm $padding-md $padding-sm $padding-sm;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
color: $font-super-light-bg;
|
color: $font-super-light-bg !important;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $hl-sm;
|
background: $hl-sm;
|
||||||
|
@ -9,6 +9,7 @@ $border-color: $hl-md;
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
align-content: flex-start;
|
align-content: flex-start;
|
||||||
height: $line-height-sm;
|
height: $line-height-sm;
|
||||||
|
line-height: $line-height-sm;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -21,12 +22,16 @@ $border-color: $hl-md;
|
|||||||
|
|
||||||
.ReactTabs__Tab {
|
.ReactTabs__Tab {
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
padding: $padding-sm / 4 $padding-md;
|
|
||||||
height: $line-height-sm;
|
height: $line-height-sm;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
border: 1px solid transparent;
|
||||||
border-bottom: 1px solid $border-color;
|
border-bottom: 1px solid $border-color;
|
||||||
border-top: 0 !important;
|
border-top: 0 !important;
|
||||||
|
|
||||||
|
* {
|
||||||
|
color: $hl-xxl;
|
||||||
|
}
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
border-left-color: transparent;
|
border-left-color: transparent;
|
||||||
}
|
}
|
||||||
@ -35,26 +40,32 @@ $border-color: $hl-md;
|
|||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
& > * {
|
||||||
color: $hl-xxl;
|
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-left: 1px solid transparent;
|
padding-left: $padding-md / 4;
|
||||||
border-right: 1px solid transparent;
|
padding-right: $padding-md / 4;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
padding-left: $padding-md;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
padding-right: $padding-md;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ReactTabs__Tab--selected {
|
.ReactTabs__Tab--selected {
|
||||||
border: 1px solid $border-color;
|
border: 1px solid $border-color;
|
||||||
border-bottom-color: transparent;
|
border-bottom-color: transparent;
|
||||||
|
|
||||||
button {
|
* {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
border: 0 !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
& > button:hover {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,10 @@ $modal-width: 50rem;
|
|||||||
$breakpoint-md: 790px;
|
$breakpoint-md: 790px;
|
||||||
$breakpoint-sm: 580px;
|
$breakpoint-sm: 580px;
|
||||||
|
|
||||||
|
.txt-xs {
|
||||||
|
font-size: $font-size-xs;
|
||||||
|
}
|
||||||
|
|
||||||
.txt-sm {
|
.txt-sm {
|
||||||
font-size: $font-size-sm;
|
font-size: $font-size-sm;
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ h3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3 {
|
h1, h2, h3 {
|
||||||
padding-top: 1.5em;
|
padding-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
@ -40,6 +40,11 @@ hr {
|
|||||||
margin: $padding-md 0;
|
margin: $padding-md 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
color: $hl-xxl;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
.monospace {
|
.monospace {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,11 @@
|
|||||||
import * as methods from '../lib/constants';
|
import * as methods from '../lib/constants';
|
||||||
import {generateId} from './util'
|
import {generateId} from './util'
|
||||||
|
|
||||||
|
export const TYPE_WORKSPACE = 'Workspace';
|
||||||
|
export const TYPE_REQUEST_GROUP = 'RequestGroup';
|
||||||
|
export const TYPE_REQUEST = 'Request';
|
||||||
|
export const TYPE_RESPONSE = 'Response';
|
||||||
|
|
||||||
// We have to include the web version of PouchDB in app.html because
|
// We have to include the web version of PouchDB in app.html because
|
||||||
// the NodeJS version defaults to LevelDB which is hard (impossible?)
|
// the NodeJS version defaults to LevelDB which is hard (impossible?)
|
||||||
// to get working in Electron apps
|
// to get working in Electron apps
|
||||||
@ -10,19 +15,47 @@ let db = new PouchDB('insomnia.db', {adapter: 'websql'});
|
|||||||
// For browser console debugging
|
// For browser console debugging
|
||||||
global.db = db;
|
global.db = db;
|
||||||
|
|
||||||
export let changes = db.changes({
|
let changeListeners = {};
|
||||||
|
|
||||||
|
export function onChange (id, callback) {
|
||||||
|
console.log(`-- Added DB Listener ${id} -- `);
|
||||||
|
changeListeners[id] = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function offChange (id) {
|
||||||
|
console.log(`-- Removed DB Listener ${id} -- `);
|
||||||
|
delete changeListeners[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function allDocs () {
|
||||||
|
return db.allDocs({include_docs: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
db.changes({
|
||||||
since: 'now',
|
since: 'now',
|
||||||
live: true,
|
live: true,
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
return_docs: false
|
return_docs: false
|
||||||
|
}).on('change', function (res) {
|
||||||
|
Object.keys(changeListeners).map(id => changeListeners[id](res))
|
||||||
}).on('complete', function (info) {
|
}).on('complete', function (info) {
|
||||||
console.log('complete', info);
|
console.log('complete', info);
|
||||||
}).on('error', function (err) {
|
}).on('error', function (err) {
|
||||||
console.log('error', err);
|
console.log('error', err);
|
||||||
});
|
});
|
||||||
|
|
||||||
export function allDocs () {
|
/**
|
||||||
return db.allDocs({include_docs: true});
|
* Initialize the database. This should be called once on app start.
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
export function initDB () {
|
||||||
|
console.log('-- Initializing Database --');
|
||||||
|
return Promise.all([
|
||||||
|
db.createIndex({index: {fields: ['parentId']}}),
|
||||||
|
db.createIndex({index: {fields: ['type']}})
|
||||||
|
]).catch(err => {
|
||||||
|
console.error('Failed to PouchDB Indexes', err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function get (id) {
|
export function get (id) {
|
||||||
@ -40,6 +73,7 @@ export function update (doc, patch = {}) {
|
|||||||
return db.put(updatedDoc).catch(e => {
|
return db.put(updatedDoc).catch(e => {
|
||||||
if (e.status === 409) {
|
if (e.status === 409) {
|
||||||
console.warn('Retrying document update for', updatedDoc);
|
console.warn('Retrying document update for', updatedDoc);
|
||||||
|
|
||||||
get(doc._id).then(dbDoc => {
|
get(doc._id).then(dbDoc => {
|
||||||
update(dbDoc, patch);
|
update(dbDoc, patch);
|
||||||
});
|
});
|
||||||
@ -47,8 +81,20 @@ export function update (doc, patch = {}) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getChildren (doc) {
|
||||||
|
const parentId = doc._id;
|
||||||
|
return db.find({selector: {parentId}});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeChildren (doc) {
|
||||||
|
return getChildren(doc).then(res => res.docs.map(remove));
|
||||||
|
}
|
||||||
|
|
||||||
export function remove (doc) {
|
export function remove (doc) {
|
||||||
return update(doc, {_deleted: true});
|
return Promise.all([
|
||||||
|
update(doc, {_deleted: true}),
|
||||||
|
removeChildren(doc)
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ~~~~~~~~~~~~~~~~~~~ //
|
// ~~~~~~~~~~~~~~~~~~~ //
|
||||||
@ -56,7 +102,12 @@ export function remove (doc) {
|
|||||||
// ~~~~~~~~~~~~~~~~~~~ //
|
// ~~~~~~~~~~~~~~~~~~~ //
|
||||||
|
|
||||||
function modelCreate (type, idPrefix, defaults, patch = {}) {
|
function modelCreate (type, idPrefix, defaults, patch = {}) {
|
||||||
const model = Object.assign(
|
const baseDefaults = {
|
||||||
|
parentId: null
|
||||||
|
};
|
||||||
|
|
||||||
|
const doc = Object.assign(
|
||||||
|
baseDefaults,
|
||||||
defaults,
|
defaults,
|
||||||
patch,
|
patch,
|
||||||
|
|
||||||
@ -70,33 +121,30 @@ function modelCreate (type, idPrefix, defaults, patch = {}) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
update(model);
|
return update(doc).then(() => doc);
|
||||||
|
|
||||||
return model;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ~~~~~~~ //
|
// ~~~~~~~ //
|
||||||
// REQUEST //
|
// REQUEST //
|
||||||
// ~~~~~~~ //
|
// ~~~~~~~ //
|
||||||
|
|
||||||
export function requestCreate (patch = {}) {
|
export function requestCreate (patch = {}) {
|
||||||
return modelCreate('Request', 'req', {
|
return modelCreate(TYPE_REQUEST, 'req', {
|
||||||
url: '',
|
url: '',
|
||||||
name: 'New Request',
|
name: 'New Request',
|
||||||
method: methods.METHOD_GET,
|
method: methods.METHOD_GET,
|
||||||
|
activated: Date.now(),
|
||||||
body: '',
|
body: '',
|
||||||
params: [],
|
params: [],
|
||||||
contentType: 'text/plain',
|
contentType: 'text/plain',
|
||||||
headers: [],
|
headers: [],
|
||||||
authentication: {},
|
authentication: {}
|
||||||
parent: null
|
|
||||||
}, patch);
|
}, patch);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function requestCopy (originalRequest) {
|
export function requestCopy (request) {
|
||||||
const name = `${originalRequest.name} (Copy)`;
|
const name = `${request.name} (Copy)`;
|
||||||
return requestCreate(Object.assign({}, originalRequest, {name}));
|
return requestCreate(Object.assign({}, request, {name}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -105,22 +153,19 @@ export function requestCopy (originalRequest) {
|
|||||||
// ~~~~~~~~~~~~~ //
|
// ~~~~~~~~~~~~~ //
|
||||||
|
|
||||||
export function requestGroupCreate (patch = {}) {
|
export function requestGroupCreate (patch = {}) {
|
||||||
return modelCreate('RequestGroup', 'grp', {
|
return modelCreate(TYPE_REQUEST_GROUP, 'grp', {
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
name: 'New Request Group',
|
name: 'New Request Group',
|
||||||
environment: {},
|
environment: {}
|
||||||
parent: null
|
|
||||||
}, patch);
|
}, patch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ~~~~~~~~ //
|
// ~~~~~~~~ //
|
||||||
// RESPONSE //
|
// RESPONSE //
|
||||||
// ~~~~~~~~ //
|
// ~~~~~~~~ //
|
||||||
|
|
||||||
export function responseCreate (patch = {}) {
|
export function responseCreate (patch = {}) {
|
||||||
return modelCreate('Response', 'rsp', {
|
return modelCreate(TYPE_RESPONSE, 'res', {
|
||||||
requestId: null,
|
|
||||||
statusCode: 0,
|
statusCode: 0,
|
||||||
statusMessage: '',
|
statusMessage: '',
|
||||||
contentType: 'text/plain',
|
contentType: 'text/plain',
|
||||||
@ -131,32 +176,44 @@ export function responseCreate (patch = {}) {
|
|||||||
}, patch);
|
}, patch);
|
||||||
}
|
}
|
||||||
|
|
||||||
db.createIndex({
|
|
||||||
index: {fields: ['requestId']}
|
|
||||||
}).catch(err => {
|
|
||||||
console.error('Failed to create index', err);
|
|
||||||
}).then(() => {
|
|
||||||
console.log('-- Indexes Updated --');
|
|
||||||
});
|
|
||||||
|
|
||||||
export function responseGetForRequest (request) {
|
|
||||||
return db.find({
|
|
||||||
selector: {
|
|
||||||
requestId: request._id
|
|
||||||
},
|
|
||||||
sort: [{requestId: 'desc'}],
|
|
||||||
limit: 1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ~~~~~~~~~ //
|
// ~~~~~~~~~ //
|
||||||
// WORKSPACE //
|
// WORKSPACE //
|
||||||
// ~~~~~~~~~ //
|
// ~~~~~~~~~ //
|
||||||
|
|
||||||
export function workspaceCreate (patch = {}) {
|
export function workspaceCreate (patch = {}) {
|
||||||
return modelCreate('Workspace', 'wsp', {
|
return modelCreate(TYPE_WORKSPACE, 'wrk', {
|
||||||
name: 'New Request Group',
|
name: 'New Workspace',
|
||||||
|
activeRequestId: null,
|
||||||
environments: []
|
environments: []
|
||||||
}, patch);
|
}, patch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function workspaceAll () {
|
||||||
|
return db.find({
|
||||||
|
selector: {type: 'Workspace'}
|
||||||
|
}).then(res => {
|
||||||
|
if (res.docs.length) {
|
||||||
|
return res;
|
||||||
|
} else {
|
||||||
|
// No workspaces? Create first one and try again
|
||||||
|
// TODO: Replace this with UI flow maybe?
|
||||||
|
console.log('-- Creating First Workspace --');
|
||||||
|
return workspaceCreate({name: 'Insomnia'}).then(() => {
|
||||||
|
return workspaceAll();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ~~~~~~~~ //
|
||||||
|
// SETTINGS //
|
||||||
|
// ~~~~~~~~ //
|
||||||
|
|
||||||
|
// TODO: This
|
||||||
|
// export function settingsCreate (patch = {}) {
|
||||||
|
// return modelCreate('Settings', 'set', {
|
||||||
|
// editorLineWrapping: false,
|
||||||
|
// editorLineNumbers: true
|
||||||
|
// }, patch);
|
||||||
|
// }
|
||||||
|
@ -5,7 +5,7 @@ const CHARS = '23456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ'.split('')
|
|||||||
export function generateId (prefix) {
|
export function generateId (prefix) {
|
||||||
let id = `${prefix}/${Date.now()}/`;
|
let id = `${prefix}/${Date.now()}/`;
|
||||||
|
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 13; i++) {
|
||||||
id += CHARS[Math.floor(Math.random() * CHARS.length)];
|
id += CHARS[Math.floor(Math.random() * CHARS.length)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
68
app/index.js
68
app/index.js
@ -1,75 +1,31 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {render} from 'react-dom'
|
import {render} from 'react-dom'
|
||||||
import {Provider} from 'react-redux'
|
import {Provider} from 'react-redux'
|
||||||
import {bindActionCreators} from 'redux'
|
import {Tabs} from 'react-tabs'
|
||||||
|
|
||||||
import createStore from './redux/create'
|
import createStore from './redux/create'
|
||||||
import App from './containers/App'
|
import App from './containers/App'
|
||||||
|
|
||||||
import * as RequestGroupActions from './redux/modules/requestGroups'
|
|
||||||
import * as RequestActions from './redux/modules/requests'
|
|
||||||
import * as ResponseActions from './redux/modules/responses'
|
|
||||||
import * as db from './database'
|
|
||||||
|
|
||||||
// Global CSS
|
// Global CSS
|
||||||
import './css/index.scss'
|
import './css/index.scss'
|
||||||
import './css/lib/chrome/platform_app.css'
|
import './css/lib/chrome/platform_app.css'
|
||||||
import './css/lib/fontawesome/css/font-awesome.css'
|
import './css/lib/fontawesome/css/font-awesome.css'
|
||||||
|
import {initStore} from './redux/initstore'
|
||||||
|
import {initDB} from './database'
|
||||||
|
|
||||||
const store = createStore();
|
// Don't inject component styles (use our own)
|
||||||
|
Tabs.setUseDefaultStyles(false);
|
||||||
|
|
||||||
// Dispatch the initial load of data
|
export const store = createStore();
|
||||||
console.log('-- Init Insomnia --');
|
|
||||||
|
|
||||||
const actionFns = {
|
console.log('-- Loading App --');
|
||||||
RequestGroup: bindActionCreators(RequestGroupActions, store.dispatch),
|
|
||||||
Request: bindActionCreators(RequestActions, store.dispatch),
|
|
||||||
Response: bindActionCreators(ResponseActions, store.dispatch)
|
|
||||||
};
|
|
||||||
|
|
||||||
function refreshDoc (doc) {
|
|
||||||
const fns = actionFns[doc.type];
|
|
||||||
|
|
||||||
if (fns) {
|
|
||||||
fns[doc._deleted ? 'remove' : 'update'](doc);
|
|
||||||
} else if (doc.hasOwnProperty('type')) {
|
|
||||||
console.warn('Unknown change', doc.type, doc);
|
|
||||||
} else {
|
|
||||||
// Probably a design doc update or something...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function watchDB () {
|
|
||||||
console.log('-- Watching PouchDB --');
|
|
||||||
|
|
||||||
let buffer = [];
|
|
||||||
let timeout = null;
|
|
||||||
|
|
||||||
// Debounce and buffer changes if they happen in quick succession
|
|
||||||
db.changes.on('change', (response) => {
|
|
||||||
const doc = response.doc;
|
|
||||||
|
|
||||||
buffer.push(doc);
|
|
||||||
clearTimeout(timeout);
|
|
||||||
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
buffer.map(refreshDoc);
|
|
||||||
buffer = [];
|
|
||||||
}, 50);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function restoreDB() {
|
|
||||||
db.allDocs().then(response => {
|
|
||||||
response.rows.map(row => refreshDoc(row.doc));
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
watchDB();
|
|
||||||
restoreDB();
|
|
||||||
|
|
||||||
|
initDB()
|
||||||
|
.then(() => initStore(store.dispatch))
|
||||||
|
.then(() => {
|
||||||
|
console.log('-- Rendering App --');
|
||||||
render(
|
render(
|
||||||
<Provider store={store}><App /></Provider>,
|
<Provider store={store}><App /></Provider>,
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
@ -17,6 +17,7 @@ export const METHODS = [
|
|||||||
METHOD_HEAD
|
METHOD_HEAD
|
||||||
];
|
];
|
||||||
|
|
||||||
export const MODAL_REQUEST_RENAME = 'request.update.name';
|
export const MODAL_WORKSPACE_RENAME = 'workspace.update.name';
|
||||||
export const MODAL_REQUEST_GROUP_RENAME = 'requestgroup.update.name';
|
export const MODAL_REQUEST_GROUP_RENAME = 'requestgroup.update.name';
|
||||||
|
export const MODAL_REQUEST_RENAME = 'request.update.name';
|
||||||
export const MODAL_ENVIRONMENT_EDITOR = 'environment.edit';
|
export const MODAL_ENVIRONMENT_EDITOR = 'environment.edit';
|
||||||
|
@ -4,15 +4,17 @@ const TYPE_REQUEST = 'request';
|
|||||||
const TYPE_REQUEST_GROUP = 'request_group';
|
const TYPE_REQUEST_GROUP = 'request_group';
|
||||||
const FORMAT_MAP = {
|
const FORMAT_MAP = {
|
||||||
'json': 'application/json'
|
'json': 'application/json'
|
||||||
|
// TODO: Fill these out
|
||||||
};
|
};
|
||||||
|
|
||||||
function importRequestGroup (iRequestGroup, exportFormat) {
|
function importRequestGroup (iRequestGroup, parentId, exportFormat) {
|
||||||
if (exportFormat === 1) {
|
if (exportFormat === 1) {
|
||||||
const requestGroup = db.requestGroupCreate({
|
db.requestGroupCreate({
|
||||||
|
parentId,
|
||||||
|
collapsed: true,
|
||||||
name: iRequestGroup.name,
|
name: iRequestGroup.name,
|
||||||
environment: (iRequestGroup.environments || {}).base || {}
|
environment: (iRequestGroup.environments || {}).base || {}
|
||||||
});
|
}).then(requestGroup => {
|
||||||
|
|
||||||
// Sometimes (maybe all the time, I can't remember) requests will be nested
|
// Sometimes (maybe all the time, I can't remember) requests will be nested
|
||||||
if (iRequestGroup.hasOwnProperty('requests')) {
|
if (iRequestGroup.hasOwnProperty('requests')) {
|
||||||
// Let's process them oldest to newest
|
// Let's process them oldest to newest
|
||||||
@ -21,10 +23,11 @@ function importRequestGroup (iRequestGroup, exportFormat) {
|
|||||||
r => importRequest(r, requestGroup._id, exportFormat)
|
r => importRequest(r, requestGroup._id, exportFormat)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function importRequest (iRequest, parent, exportFormat) {
|
function importRequest (iRequest, parentId, exportFormat) {
|
||||||
if (exportFormat === 1) {
|
if (exportFormat === 1) {
|
||||||
let auth = {};
|
let auth = {};
|
||||||
if (iRequest.authentication.username) {
|
if (iRequest.authentication.username) {
|
||||||
@ -35,6 +38,8 @@ function importRequest (iRequest, parent, exportFormat) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
db.requestCreate({
|
db.requestCreate({
|
||||||
|
parentId,
|
||||||
|
activated: 0, // Don't activate imported requests
|
||||||
name: iRequest.name,
|
name: iRequest.name,
|
||||||
url: iRequest.url,
|
url: iRequest.url,
|
||||||
method: iRequest.method,
|
method: iRequest.method,
|
||||||
@ -42,31 +47,32 @@ function importRequest (iRequest, parent, exportFormat) {
|
|||||||
headers: iRequest.headers || [],
|
headers: iRequest.headers || [],
|
||||||
params: iRequest.params || [],
|
params: iRequest.params || [],
|
||||||
contentType: FORMAT_MAP[iRequest.__insomnia.format] || 'text/plain',
|
contentType: FORMAT_MAP[iRequest.__insomnia.format] || 'text/plain',
|
||||||
authentication: auth,
|
authentication: auth
|
||||||
parent: parent
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function (txt, callback) {
|
export default function (workspace, txt) {
|
||||||
let data;
|
let data;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(txt);
|
data = JSON.parse(txt);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return callback(new Error('Invalid Insomnia export'));
|
// TODO: Handle these errors
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!data.hasOwnProperty('_type') || !data.hasOwnProperty('items')) {
|
if (!data.hasOwnProperty('_type') || !data.hasOwnProperty('items')) {
|
||||||
return callback(new Error('Invalid Insomnia export'));
|
// TODO: Handle these errors
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
data.items.filter(i => i._type === TYPE_REQUEST_GROUP).map(
|
data.items.reverse().filter(i => i._type === TYPE_REQUEST_GROUP).map(
|
||||||
rg => importRequestGroup(rg, data.__export_format)
|
rg => importRequestGroup(rg, workspace._id, data.__export_format)
|
||||||
);
|
);
|
||||||
|
|
||||||
data.items.filter(i => i._type === TYPE_REQUEST).map(
|
data.items.reverse().filter(i => i._type === TYPE_REQUEST).map(
|
||||||
r => importRequest(r, data.__export_format)
|
r => importRequest(r, workspace._id, data.__export_format)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import networkRequest from 'request'
|
|||||||
import render from './render'
|
import render from './render'
|
||||||
import * as db from '../database'
|
import * as db from '../database'
|
||||||
|
|
||||||
function makeRequest (unrenderedRequest, callback, context = {}) {
|
function actuallySend (unrenderedRequest, callback, context = {}) {
|
||||||
// SNEAKY HACK: Render nested object by converting it to JSON then rendering
|
// SNEAKY HACK: Render nested object by converting it to JSON then rendering
|
||||||
const template = JSON.stringify(unrenderedRequest);
|
const template = JSON.stringify(unrenderedRequest);
|
||||||
const request = JSON.parse(render(template, context));
|
const request = JSON.parse(render(template, context));
|
||||||
@ -35,6 +35,7 @@ function makeRequest (unrenderedRequest, callback, context = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: this needs to account for existing URL params
|
||||||
config.url += request.params.map((p, i) => {
|
config.url += request.params.map((p, i) => {
|
||||||
const name = encodeURIComponent(p.name);
|
const name = encodeURIComponent(p.name);
|
||||||
const value = encodeURIComponent(p.value);
|
const value = encodeURIComponent(p.value);
|
||||||
@ -47,7 +48,7 @@ function makeRequest (unrenderedRequest, callback, context = {}) {
|
|||||||
console.error('Request Failed', err, response);
|
console.error('Request Failed', err, response);
|
||||||
} else {
|
} else {
|
||||||
db.responseCreate({
|
db.responseCreate({
|
||||||
requestId: request._id,
|
parentId: request._id,
|
||||||
statusCode: response.statusCode,
|
statusCode: response.statusCode,
|
||||||
statusMessage: response.statusMessage,
|
statusMessage: response.statusMessage,
|
||||||
contentType: response.headers['content-type'],
|
contentType: response.headers['content-type'],
|
||||||
@ -65,12 +66,12 @@ function makeRequest (unrenderedRequest, callback, context = {}) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function (request, callback) {
|
export function send (request, callback) {
|
||||||
if (request.parent) {
|
if (request.parentId) {
|
||||||
db.get(request.parent).then(
|
db.get(request.parentId).then(
|
||||||
requestGroup => makeRequest(request, callback, requestGroup.environment)
|
requestGroup => actuallySend(request, callback, requestGroup.environment)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
makeRequest(request, callback)
|
actuallySend(request, callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,27 +3,22 @@ import thunkMiddleware from 'redux-thunk'
|
|||||||
import createLogger from 'redux-logger'
|
import createLogger from 'redux-logger'
|
||||||
import rootReducer from './reducer'
|
import rootReducer from './reducer'
|
||||||
|
|
||||||
const loggerMiddleware = createLogger({
|
|
||||||
collapsed: true
|
|
||||||
});
|
|
||||||
|
|
||||||
export default function configureStore (initialState) {
|
export default function configureStore (initialState) {
|
||||||
const store = createStore(
|
const store = createStore(
|
||||||
rootReducer,
|
rootReducer,
|
||||||
initialState,
|
initialState,
|
||||||
applyMiddleware(
|
applyMiddleware(
|
||||||
thunkMiddleware,
|
thunkMiddleware,
|
||||||
loggerMiddleware
|
createLogger({collapsed: true})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (module.hot) {
|
if (module.hot) {
|
||||||
// Enable Webpack hot module replacement for reducers
|
|
||||||
module.hot.accept('./reducer', () => {
|
module.hot.accept('./reducer', () => {
|
||||||
const nextReducer = require('./reducer').default;
|
const nextReducer = require('./reducer.js').default;
|
||||||
store.replaceReducer(nextReducer);
|
store.replaceReducer(nextReducer);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return store
|
return store;
|
||||||
}
|
}
|
||||||
|
48
app/redux/initstore.js
Normal file
48
app/redux/initstore.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import {bindActionCreators} from 'redux'
|
||||||
|
import * as entitiesActions from './modules/entities'
|
||||||
|
import * as db from '../database'
|
||||||
|
|
||||||
|
const CHANGE_ID = 'store.listener';
|
||||||
|
|
||||||
|
export function initStore (dispatch) {
|
||||||
|
db.offChange(CHANGE_ID);
|
||||||
|
|
||||||
|
// New stuff...
|
||||||
|
const entities = bindActionCreators(entitiesActions, dispatch);
|
||||||
|
|
||||||
|
const docChanged = doc => {
|
||||||
|
if (!doc.hasOwnProperty('type')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// New stuff...
|
||||||
|
entities[doc._deleted ? 'remove' : 'update'](doc);
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('-- Restoring Store --');
|
||||||
|
|
||||||
|
const start = Date.now();
|
||||||
|
|
||||||
|
return db.workspaceAll().then(res => {
|
||||||
|
const restoreChildren = (doc) => {
|
||||||
|
docChanged(doc);
|
||||||
|
|
||||||
|
return db.getChildren(doc).then(res => {
|
||||||
|
// Done condition
|
||||||
|
if (!res.docs.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
res.docs.map(doc => restoreChildren(doc))
|
||||||
|
);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
return res.docs.map(restoreChildren)
|
||||||
|
}).then(() => {
|
||||||
|
console.log(`Restore took ${(Date.now() - start) / 1000} s`);
|
||||||
|
}).then(() => {
|
||||||
|
db.onChange(CHANGE_ID, res => docChanged(res.doc));
|
||||||
|
});
|
||||||
|
}
|
69
app/redux/modules/entities.js
Normal file
69
app/redux/modules/entities.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import {combineReducers} from 'redux'
|
||||||
|
|
||||||
|
import {TYPE_WORKSPACE, TYPE_REQUEST_GROUP, TYPE_REQUEST, TYPE_RESPONSE} from '../../database/index'
|
||||||
|
import * as workspaceFns from './workspaces'
|
||||||
|
|
||||||
|
const ENTITY_UPDATE = 'entities/update';
|
||||||
|
const ENTITY_REMOVE = 'entities/remove';
|
||||||
|
|
||||||
|
// ~~~~~~~~ //
|
||||||
|
// REDUCERS //
|
||||||
|
// ~~~~~~~~ //
|
||||||
|
|
||||||
|
function genericEntityReducer (referenceName) {
|
||||||
|
return function (state = {}, action) {
|
||||||
|
const doc = action[referenceName];
|
||||||
|
|
||||||
|
if (!doc) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
|
case ENTITY_UPDATE:
|
||||||
|
return {...state, [doc._id]: doc};
|
||||||
|
|
||||||
|
case ENTITY_REMOVE:
|
||||||
|
const newState = Object.assign({}, state);
|
||||||
|
delete newState[action[referenceName]._id];
|
||||||
|
return newState;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default combineReducers({
|
||||||
|
workspaces: genericEntityReducer('workspace'),
|
||||||
|
requestGroups: genericEntityReducer('requestGroup'),
|
||||||
|
requests: genericEntityReducer('request'),
|
||||||
|
responses: genericEntityReducer('response')
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// ~~~~~~~ //
|
||||||
|
// ACTIONS //
|
||||||
|
// ~~~~~~~ //
|
||||||
|
|
||||||
|
const updateFns = {
|
||||||
|
[TYPE_WORKSPACE]: workspace => ({type: ENTITY_UPDATE, workspace}),
|
||||||
|
[TYPE_REQUEST_GROUP]: requestGroup => ({type: ENTITY_UPDATE, requestGroup}),
|
||||||
|
[TYPE_RESPONSE]: response => ({type: ENTITY_UPDATE, response}),
|
||||||
|
[TYPE_REQUEST]: request => ({type: ENTITY_UPDATE, request})
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeFns = {
|
||||||
|
[TYPE_WORKSPACE]: workspace => ({type: ENTITY_REMOVE, workspace}),
|
||||||
|
[TYPE_REQUEST_GROUP]: requestGroup => ({type: ENTITY_REMOVE, requestGroup}),
|
||||||
|
[TYPE_RESPONSE]: response => ({type: ENTITY_UPDATE, response}),
|
||||||
|
[TYPE_REQUEST]: request => ({type: ENTITY_REMOVE, request})
|
||||||
|
};
|
||||||
|
|
||||||
|
export function update (doc) {
|
||||||
|
return updateFns[doc.type](doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function remove (doc) {
|
||||||
|
return removeFns[doc.type](doc);
|
||||||
|
}
|
@ -1,58 +1,20 @@
|
|||||||
import {combineReducers} from 'redux'
|
|
||||||
|
|
||||||
import {show} from './modals'
|
import {show} from './modals'
|
||||||
import {MODAL_ENVIRONMENT_EDITOR, MODAL_REQUEST_GROUP_RENAME} from '../../lib/constants';
|
import {MODAL_ENVIRONMENT_EDITOR, MODAL_REQUEST_GROUP_RENAME} from '../../lib/constants'
|
||||||
|
|
||||||
export const REQUEST_GROUP_UPDATE = 'requestgroups/update';
|
export const REQUEST_GROUP_TOGGLE = 'request-groups/toggle';
|
||||||
export const REQUEST_GROUP_DELETE = 'requestgroups/delete';
|
|
||||||
export const REQUEST_GROUP_TOGGLE = 'requestgroups/toggle';
|
|
||||||
|
|
||||||
// ~~~~~~~~ //
|
// ~~~~~~~~ //
|
||||||
// REDUCERS //
|
// REDUCERS //
|
||||||
// ~~~~~~~~ //
|
// ~~~~~~~~ //
|
||||||
|
|
||||||
function allReducer (state = [], action) {
|
// Nothing yet...
|
||||||
switch (action.type) {
|
|
||||||
|
|
||||||
case REQUEST_GROUP_UPDATE:
|
|
||||||
const i = state.findIndex(r => r._id === action.requestGroup._id);
|
|
||||||
|
|
||||||
if (i === -1) {
|
|
||||||
return [action.requestGroup, ...state];
|
|
||||||
} else {
|
|
||||||
return [...state.slice(0, i), action.requestGroup, ...state.slice(i + 1)]
|
|
||||||
}
|
|
||||||
|
|
||||||
case REQUEST_GROUP_TOGGLE:
|
|
||||||
return state.map(
|
|
||||||
rg => rg._id === action._id ? Object.assign({}, rg, {collapsed: !rg.collapsed}) : rg
|
|
||||||
);
|
|
||||||
|
|
||||||
case REQUEST_GROUP_DELETE:
|
|
||||||
return state.filter(rg => rg._id !== action.requestGroup._id);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default combineReducers({
|
|
||||||
all: allReducer
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// ~~~~~~~ //
|
// ~~~~~~~ //
|
||||||
// ACTIONS //
|
// ACTIONS //
|
||||||
// ~~~~~~~ //
|
// ~~~~~~~ //
|
||||||
|
|
||||||
export function update (requestGroup) {
|
|
||||||
return {type: REQUEST_GROUP_UPDATE, requestGroup};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function remove (requestGroup) {
|
|
||||||
return {type: REQUEST_GROUP_DELETE, requestGroup};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toggle (requestGroup) {
|
export function toggle (requestGroup) {
|
||||||
return {type: REQUEST_GROUP_TOGGLE, requestGroup}
|
return {type: REQUEST_GROUP_TOGGLE, requestGroup}
|
||||||
}
|
}
|
||||||
|
@ -1,86 +1,35 @@
|
|||||||
import {combineReducers} from 'redux'
|
import * as network from '../../lib/network'
|
||||||
|
|
||||||
import makeRequest from '../../lib/request'
|
|
||||||
import {loadStart, loadStop} from './global'
|
import {loadStart, loadStop} from './global'
|
||||||
import {show} from './modals'
|
import {show} from './modals'
|
||||||
import {MODAL_REQUEST_RENAME} from '../../lib/constants'
|
import {MODAL_REQUEST_RENAME} from '../../lib/constants'
|
||||||
|
|
||||||
export const REQUEST_UPDATE = 'requests/update';
|
|
||||||
export const REQUEST_DELETE = 'requests/delete';
|
|
||||||
export const REQUEST_ACTIVATE = 'requests/activate';
|
|
||||||
export const REQUEST_CHANGE_FILTER = 'requests/filter';
|
export const REQUEST_CHANGE_FILTER = 'requests/filter';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
filter: ''
|
||||||
|
};
|
||||||
|
|
||||||
// ~~~~~~~~ //
|
// ~~~~~~~~ //
|
||||||
// REDUCERS //
|
// REDUCERS //
|
||||||
// ~~~~~~~~ //
|
// ~~~~~~~~ //
|
||||||
|
|
||||||
function allReducer (state = [], action) {
|
export default function (state = initialState, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|
||||||
case REQUEST_DELETE:
|
|
||||||
return state.filter(r => r._id !== action.request._id);
|
|
||||||
|
|
||||||
case REQUEST_UPDATE:
|
|
||||||
const i = state.findIndex(r => r._id === action.request._id);
|
|
||||||
|
|
||||||
if (i === -1) {
|
|
||||||
return [action.request, ...state];
|
|
||||||
} else {
|
|
||||||
return [...state.slice(0, i), action.request, ...state.slice(i + 1)]
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function activeReducer (state = null, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
|
|
||||||
case REQUEST_ACTIVATE:
|
|
||||||
return action.request._id;
|
|
||||||
|
|
||||||
case REQUEST_DELETE:
|
|
||||||
return state === action._id ? null : state;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterReducer (state = '', action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case REQUEST_CHANGE_FILTER:
|
case REQUEST_CHANGE_FILTER:
|
||||||
return action.filter;
|
const filter = action.filter;
|
||||||
|
return Object.assign({}, state, {filter});
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default combineReducers({
|
|
||||||
all: allReducer,
|
|
||||||
filter: filterReducer,
|
|
||||||
active: activeReducer
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// ~~~~~~~ //
|
// ~~~~~~~ //
|
||||||
// ACTIONS //
|
// ACTIONS //
|
||||||
// ~~~~~~~ //
|
// ~~~~~~~ //
|
||||||
|
|
||||||
export function remove (request) {
|
|
||||||
return {type: REQUEST_DELETE, request};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function update (request) {
|
|
||||||
return {type: REQUEST_UPDATE, request};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function activate (request) {
|
|
||||||
return {type: REQUEST_ACTIVATE, request};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function changeFilter (filter) {
|
export function changeFilter (filter) {
|
||||||
return {type: REQUEST_CHANGE_FILTER, filter};
|
return {type: REQUEST_CHANGE_FILTER, filter};
|
||||||
}
|
}
|
||||||
@ -89,7 +38,7 @@ export function send (request) {
|
|||||||
return dispatch => {
|
return dispatch => {
|
||||||
dispatch(loadStart());
|
dispatch(loadStart());
|
||||||
|
|
||||||
makeRequest(request, () => {
|
network.send(request, () => {
|
||||||
dispatch(loadStop());
|
dispatch(loadStop());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,12 @@
|
|||||||
const RESPONSE_UPDATE = 'responses/update';
|
|
||||||
|
|
||||||
const initialState = {};
|
|
||||||
|
|
||||||
// ~~~~~~~~ //
|
// ~~~~~~~~ //
|
||||||
// REDUCERS //
|
// REDUCERS //
|
||||||
// ~~~~~~~~ //
|
// ~~~~~~~~ //
|
||||||
|
|
||||||
export default function (state = initialState, action) {
|
// None yet
|
||||||
switch (action.type) {
|
|
||||||
|
|
||||||
case RESPONSE_UPDATE:
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
[action.response.requestId]: action.response
|
|
||||||
});
|
|
||||||
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ~~~~~~~ //
|
// ~~~~~~~ //
|
||||||
// ACTIONS //
|
// ACTIONS //
|
||||||
// ~~~~~~~ //
|
// ~~~~~~~ //
|
||||||
|
|
||||||
export function update (response) {
|
// None yet...
|
||||||
return {type: RESPONSE_UPDATE, response};
|
|
||||||
}
|
|
||||||
|
@ -1,39 +1,26 @@
|
|||||||
import {combineReducers} from 'redux'
|
import {combineReducers} from 'redux'
|
||||||
|
import {MODAL_WORKSPACE_RENAME} from '../../lib/constants'
|
||||||
|
import {show} from './modals'
|
||||||
|
|
||||||
export const WORKSPACE_UPDATE = 'workspaces/update';
|
|
||||||
export const WORKSPACE_DELETE = 'workspaces/delete';
|
|
||||||
export const WORKSPACE_ACTIVATE = 'workspaces/activate';
|
export const WORKSPACE_ACTIVATE = 'workspaces/activate';
|
||||||
|
|
||||||
// ~~~~~~~~ //
|
// ~~~~~~~~ //
|
||||||
// REDUCERS //
|
// REDUCERS //
|
||||||
// ~~~~~~~~ //
|
// ~~~~~~~~ //
|
||||||
|
|
||||||
function allReducer (state = [], action) {
|
|
||||||
switch (action.type) {
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function activeReducer (state = null, action) {
|
function activeReducer (state = null, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
|
|
||||||
}
|
case WORKSPACE_ACTIVATE:
|
||||||
}
|
return action.workspace._id;
|
||||||
|
|
||||||
function filterReducer (state = '', action) {
|
|
||||||
switch (action.type) {
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
all: allReducer,
|
activeId: activeReducer
|
||||||
filter: filterReducer,
|
|
||||||
active: activeReducer
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -41,15 +28,11 @@ export default combineReducers({
|
|||||||
// ACTIONS //
|
// ACTIONS //
|
||||||
// ~~~~~~~ //
|
// ~~~~~~~ //
|
||||||
|
|
||||||
export function remove (request) {
|
export function activate (workspace) {
|
||||||
return {type: WORKSPACE_DELETE, request};
|
return {type: WORKSPACE_ACTIVATE, workspace};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function update (request) {
|
export function showUpdateNamePrompt (workspace) {
|
||||||
return {type: WORKSPACE_UPDATE, request};
|
const defaultValue = workspace.name;
|
||||||
|
return show(MODAL_WORKSPACE_RENAME, {defaultValue, workspace});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function activate (request) {
|
|
||||||
return {type: WORKSPACE_ACTIVATE, request};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import {combineReducers} from 'redux'
|
import {combineReducers} from 'redux'
|
||||||
|
|
||||||
import workspacesReducer from './modules/workspaces'
|
import workspaces from './modules/workspaces'
|
||||||
import requestsReducer from './modules/requests'
|
import requestGroups from './modules/requestGroups'
|
||||||
import tabsReducer from './modules/tabs'
|
import requests from './modules/requests'
|
||||||
import globalReducer from './modules/global'
|
import responses from './modules/responses'
|
||||||
import modalsReducer from './modules/modals'
|
import global from './modules/global'
|
||||||
import requestGroupsReducer from './modules/requestGroups'
|
import modals from './modules/modals'
|
||||||
import responsesReducer from './modules/responses'
|
import entities from './modules/entities'
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
workspaces: workspacesReducer,
|
workspaces,
|
||||||
requestGroups: requestGroupsReducer,
|
responses,
|
||||||
requests: requestsReducer,
|
requests,
|
||||||
responses: responsesReducer,
|
requestGroups,
|
||||||
modals: modalsReducer,
|
modals,
|
||||||
global: globalReducer,
|
global,
|
||||||
tabs: tabsReducer
|
entities
|
||||||
});
|
});
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"react-tabs": "^0.5.3",
|
"react-tabs": "^0.5.3",
|
||||||
"redux": "^3.3.1",
|
"redux": "^3.3.1",
|
||||||
"redux-logger": "^2.6.1",
|
"redux-logger": "^2.6.1",
|
||||||
|
"redux-shortcuts": "0.0.1",
|
||||||
"redux-thunk": "^2.0.1"
|
"redux-thunk": "^2.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import path from 'path'
|
|
||||||
import webpack from 'webpack'
|
import webpack from 'webpack'
|
||||||
import baseConfig from './webpack.config.base'
|
import baseConfig from './webpack.config.base'
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user