mirror of
https://github.com/Kong/insomnia
synced 2024-11-07 14:19:58 +00:00
A bunch
This commit is contained in:
parent
3ebb3ed00d
commit
1452c39b67
22
app/__tests__/main.test.js
Normal file
22
app/__tests__/main.test.js
Normal file
@ -0,0 +1,22 @@
|
||||
// import {expect, unmock, it, describe} from 'jest';
|
||||
|
||||
jest.unmock('../components/Sidebar');
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
import Sidebar from '../components/Sidebar';
|
||||
|
||||
describe('Sidebar', () => {
|
||||
it('changes the text after click', () => {
|
||||
// Render a checkbox with label in the document
|
||||
const sidebar = TestUtils.renderIntoDocument(
|
||||
<div><Sidebar /></div>
|
||||
);
|
||||
|
||||
const sidebarNode = ReactDOM.findDOMNode(sidebar);
|
||||
|
||||
// Verify that it's Off by default
|
||||
expect(sidebarNode.textContent).toEqual('Off');
|
||||
});
|
||||
});
|
23
app/actions/global.js
Normal file
23
app/actions/global.js
Normal file
@ -0,0 +1,23 @@
|
||||
import * as types from '../constants/actionTypes';
|
||||
import {LOCALSTORAGE_KEY} from "../constants/global";
|
||||
|
||||
export function restoreState () {
|
||||
return (dispatch) => {
|
||||
setTimeout(() => {
|
||||
let state = undefined;
|
||||
try {
|
||||
state = JSON.parse(localStorage[LOCALSTORAGE_KEY]);
|
||||
} catch (e) { }
|
||||
|
||||
dispatch({type: types.GLOBAL_STATE_RESTORED, state});
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
export function loadStart () {
|
||||
return {type: types.GLOBAL_LOAD_START};
|
||||
}
|
||||
|
||||
export function loadStop () {
|
||||
return {type: types.GLOBAL_LOAD_STOP};
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
import * as types from '../constants/ActionTypes'
|
||||
|
||||
export function addTodo (text) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(loadStart());
|
||||
|
||||
setTimeout(() => {
|
||||
let id = Date.now();
|
||||
let completed = false;
|
||||
localStorage['todos'] = JSON.stringify(
|
||||
[...JSON.parse(localStorage['todos'] || '[]'), {
|
||||
text: text,
|
||||
id: id,
|
||||
completed: completed
|
||||
}]
|
||||
);
|
||||
dispatch({type: types.ADD_TODO, text, id, completed});
|
||||
dispatch(loadStop());
|
||||
}, 300);
|
||||
};
|
||||
}
|
||||
|
||||
export function loadTodos () {
|
||||
return (dispatch) => {
|
||||
dispatch(loadStart());
|
||||
setTimeout(() => {
|
||||
let todos = JSON.parse(localStorage['todos'] || '[]');
|
||||
dispatch({type: types.LOAD_TODOS, todos})
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
export function loadStart () {
|
||||
return {type: types.LOAD_START};
|
||||
}
|
||||
|
||||
export function loadStop () {
|
||||
return {type: types.LOAD_STOP};
|
||||
}
|
||||
|
||||
export function deleteTodo (id) {
|
||||
return (dispatch) => {
|
||||
dispatch(loadStart());
|
||||
setTimeout(() => {
|
||||
|
||||
localStorage['todos'] = JSON.stringify(
|
||||
JSON.parse(localStorage['todos'] || '[]').filter(t => t.id !== id)
|
||||
);
|
||||
dispatch({type: types.DELETE_TODO, id});
|
||||
dispatch(loadStop());
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
export function editTodo (id, text) {
|
||||
return {type: types.EDIT_TODO, id, text};
|
||||
}
|
||||
|
||||
export function completeTodo (id) {
|
||||
return {type: types.COMPLETE_TODO, id};
|
||||
}
|
||||
|
||||
export function completeAll () {
|
||||
return {type: types.COMPLETE_ALL};
|
||||
}
|
||||
|
||||
export function clearCompleted () {
|
||||
return {type: types.CLEAR_COMPLETED};
|
||||
}
|
62
app/actions/requests.js
Normal file
62
app/actions/requests.js
Normal file
@ -0,0 +1,62 @@
|
||||
import * as types from '../constants/actionTypes'
|
||||
import * as methods from '../constants/global'
|
||||
import {loadStart} from "./global";
|
||||
import {loadStop} from "./global";
|
||||
|
||||
function defaultRequest () {
|
||||
return {
|
||||
id: null,
|
||||
_mode: 'json',
|
||||
created: 0,
|
||||
modified: 0,
|
||||
name: '',
|
||||
method: methods.METHOD_GET,
|
||||
body: '',
|
||||
params: [],
|
||||
headers: [],
|
||||
authentication: {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new request from a subset of fields
|
||||
* @param request values to override defaults with
|
||||
* @returns {*}
|
||||
*/
|
||||
function buildRequest (request) {
|
||||
// Build the required fields
|
||||
const id = request.id || `req_${Date.now()}`;
|
||||
const created = request.created || Date.now();
|
||||
const modified = request.modified || Date.now();
|
||||
|
||||
// Create the request
|
||||
return Object.assign({}, defaultRequest(), request, {
|
||||
id, created, modified
|
||||
});
|
||||
}
|
||||
|
||||
export function addRequest (name = 'My Request') {
|
||||
return (dispatch) => {
|
||||
dispatch(loadStart());
|
||||
const request = buildRequest({name});
|
||||
|
||||
setTimeout(() => {
|
||||
dispatch({type: types.REQUEST_ADD, request});
|
||||
dispatch(loadStop());
|
||||
}, 500);
|
||||
};
|
||||
}
|
||||
|
||||
export function updateRequest (requestPatch) {
|
||||
return (dispatch) => {
|
||||
dispatch(loadStart());
|
||||
|
||||
const newRequest = Object.assign({}, requestPatch, {modified: Date.now()});
|
||||
const request = buildRequest(newRequest);
|
||||
|
||||
setTimeout(() => {
|
||||
dispatch({type: types.REQUEST_UPDATE, request});
|
||||
dispatch(loadStop());
|
||||
}, 500);
|
||||
};
|
||||
}
|
40
app/components/App.js
Normal file
40
app/components/App.js
Normal file
@ -0,0 +1,40 @@
|
||||
import React, {PropTypes, Component} from 'react'
|
||||
import Sidebar from './Sidebar'
|
||||
import RequestPane from './RequestPane'
|
||||
import ResponsePane from './ResponsePane'
|
||||
|
||||
class App extends Component {
|
||||
renderEditor () {
|
||||
const {updateRequest, requests} = this.props;
|
||||
return (
|
||||
<div className="grid">
|
||||
<RequestPane
|
||||
updateRequest={updateRequest}
|
||||
request={requests.active}/>
|
||||
<ResponsePane request={requests.active}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const {addRequest, loading, requests} = this.props;
|
||||
return (
|
||||
<div className="grid bg-dark">
|
||||
<Sidebar
|
||||
addRequest={addRequest}
|
||||
loading={loading}
|
||||
requests={requests}/>
|
||||
{requests.active ? this.renderEditor() : <div></div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
App.propTypes = {
|
||||
addRequest: PropTypes.func.isRequired,
|
||||
updateRequest: PropTypes.func.isRequired,
|
||||
requests: PropTypes.object.isRequired,
|
||||
loading: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default App;
|
@ -50,32 +50,20 @@ class Editor extends Component {
|
||||
|
||||
componentDidMount () {
|
||||
var textareaNode = this.refs.textarea;
|
||||
let options = Object.assign({
|
||||
theme: 'monokai',
|
||||
scrollPastEnd: true,
|
||||
foldGutter: true,
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||
cursorScrollMargin: 60,
|
||||
extraKeys: {
|
||||
"Ctrl-Q": function (cm) {
|
||||
cm.foldCode(cm.getCursor());
|
||||
}
|
||||
},
|
||||
scrollbarStyle: 'overlay'
|
||||
}, this.props.options || {});
|
||||
|
||||
if (options.mode === 'application/json') {
|
||||
options.mode = 'application/ld+json';
|
||||
}
|
||||
|
||||
this.codeMirror = CodeMirror.fromTextArea(textareaNode, options);
|
||||
this.codeMirror = CodeMirror.fromTextArea(textareaNode);
|
||||
this.codeMirror.on('change', this.codemirrorValueChanged.bind(this));
|
||||
this.codeMirror.on('focus', this.focusChanged.bind(this, true));
|
||||
this.codeMirror.on('blur', this.focusChanged.bind(this, false));
|
||||
this._currentCodemirrorValue = this.props.defaultValue || this.props.value || '';
|
||||
this.codemirrorSetOptions(this.props.options);
|
||||
this.codeMirror.setValue(this._currentCodemirrorValue);
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
this.codemirrorSetOptions(this.props.options);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
// todo: is there a lighter-weight way to remove the cm instance?
|
||||
if (this.codeMirror) {
|
||||
@ -96,10 +84,6 @@ class Editor extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
getCodeMirror () {
|
||||
return this.codeMirror;
|
||||
}
|
||||
|
||||
focus () {
|
||||
if (this.codeMirror) {
|
||||
this.codeMirror.focus();
|
||||
@ -113,18 +97,51 @@ class Editor extends Component {
|
||||
this.props.onFocusChange && this.props.onFocusChange(focused);
|
||||
}
|
||||
|
||||
codemirrorValueChanged (doc, change) {
|
||||
codemirrorSetOptions (options) {
|
||||
options = Object.assign({
|
||||
theme: 'monokai',
|
||||
scrollPastEnd: true,
|
||||
foldGutter: true,
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||
cursorScrollMargin: 60,
|
||||
extraKeys: {
|
||||
"Ctrl-Q": function (cm) {
|
||||
cm.foldCode(cm.getCursor());
|
||||
}
|
||||
},
|
||||
scrollbarStyle: 'overlay'
|
||||
}, options || {});
|
||||
|
||||
if (options.mode === 'json') {
|
||||
options.mode = 'application/json';
|
||||
}
|
||||
|
||||
if (options.mode === 'application/json') {
|
||||
options.mode = 'application/ld+json';
|
||||
}
|
||||
|
||||
Object.keys(options).map(key => {
|
||||
this.codeMirror.setOption(key, options[key]);
|
||||
});
|
||||
}
|
||||
|
||||
codemirrorValueChanged (doc) {
|
||||
clearTimeout(this._timeout);
|
||||
this._timeout = setTimeout(() => {
|
||||
var newValue = doc.getValue();
|
||||
this._currentCodemirrorValue = newValue;
|
||||
this.props.onChange && this.props.onChange(newValue);
|
||||
}, (this.props.debounceMillis || 0));
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className={`editor ${this.props.className || ''}`}>
|
||||
<textarea name={this.props.path}
|
||||
ref='textarea'
|
||||
defaultValue={this.props.value}
|
||||
autoComplete='off'></textarea>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -135,7 +152,8 @@ Editor.propTypes = {
|
||||
options: PropTypes.object,
|
||||
path: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
className: PropTypes.any
|
||||
className: PropTypes.any,
|
||||
debounceMillis: PropTypes.number
|
||||
};
|
||||
|
||||
export default Editor;
|
||||
|
@ -1,9 +1,21 @@
|
||||
import React from 'react'
|
||||
import React, {Component, PropTypes} from 'react'
|
||||
import Editor from '../components/Editor'
|
||||
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
|
||||
|
||||
const RequestPane = (props) => (
|
||||
// Don't inject component styles (use our own)
|
||||
Tabs.setUseDefaultStyles(false);
|
||||
|
||||
class RequestPane extends Component {
|
||||
shouldComponentUpdate (nextProps, nextState) {
|
||||
return nextProps.request !== this.props.request
|
||||
}
|
||||
|
||||
render () {
|
||||
const {request, updateRequest} = this.props;
|
||||
|
||||
return (
|
||||
<section id="request" className="pane col grid-v">
|
||||
<header className="pane-header pane-header-no-padding bg-super-light">
|
||||
<header className="pane__header bg-super-light">
|
||||
<div className="form-control url-input">
|
||||
<div className="grid">
|
||||
<button className="btn method-dropdown">
|
||||
@ -16,21 +28,41 @@ const RequestPane = (props) => (
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div className="pane-body grid-v">
|
||||
<div className="bg-dark pane-tabs">
|
||||
{['Query Params', 'Body', 'Headers', 'Basic Auth'].map((name =>
|
||||
<button key={name} className={'btn ' + (name === 'Body' ? 'bg-dark' : 'bg-dark')}>
|
||||
{name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<Editor value={localStorage['json']}
|
||||
onChange={(v) => {localStorage['json'] = v}}
|
||||
options={{mode: 'application/json', lineNumbers: true}}/>
|
||||
<div className="pane__body">
|
||||
<Tabs selectedIndex={1} className="grid-v">
|
||||
<TabList className="grid pane__header">
|
||||
<Tab>
|
||||
<button className="btn">Params</button>
|
||||
</Tab>
|
||||
<Tab>
|
||||
<button className="btn">Body</button>
|
||||
</Tab>
|
||||
<Tab>
|
||||
<button className="btn">Basic Auth</button>
|
||||
</Tab>
|
||||
<Tab>
|
||||
<button className="btn">Headers</button>
|
||||
</Tab>
|
||||
</TabList>
|
||||
<TabPanel className="col">Params</TabPanel>
|
||||
<TabPanel className="col">
|
||||
<Editor value={request.body}
|
||||
onChange={(body) => updateRequest(Object.assign({}, request, {body}) )}
|
||||
debounceMillis={500}
|
||||
options={{mode: request._mode, lineNumbers: true}}/>
|
||||
</TabPanel>
|
||||
<TabPanel className="col">Basic Auth</TabPanel>
|
||||
<TabPanel className="col">Headers</TabPanel>
|
||||
</Tabs>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RequestPane.propTypes = {};
|
||||
RequestPane.propTypes = {
|
||||
updateRequest: PropTypes.func.isRequired,
|
||||
request: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default RequestPane;
|
||||
|
@ -1,20 +1,22 @@
|
||||
import React from 'react'
|
||||
import React, {PropTypes} from 'react'
|
||||
import Editor from '../components/Editor'
|
||||
|
||||
const ResponsePane = (props) => (
|
||||
<section id="response" className="pane col grid-v">
|
||||
<header className="pane-header header-no-padding text-center bg-super-light">
|
||||
<div>
|
||||
<header className="pane__header text-center bg-super-light">
|
||||
<div className="pane__header__content">
|
||||
<div className="tag success"><strong>200</strong> SUCCESS</div>
|
||||
<div className="tag"><strong>GET</strong> https://google.com</div>
|
||||
</div>
|
||||
</header>
|
||||
<div className="pane-body">
|
||||
<Editor value={'{}'} options={{mode: 'application/json', lineNumbers: true}}></Editor>
|
||||
<div className="pane__body">
|
||||
<Editor value={'{}'} options={{mode: 'application/json'}}></Editor>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
ResponsePane.propTypes = {};
|
||||
ResponsePane.propTypes = {
|
||||
request: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default ResponsePane;
|
||||
|
@ -1,22 +1,43 @@
|
||||
import React from 'react'
|
||||
import React, {PropTypes} from 'react'
|
||||
|
||||
const Sidebar = (props) => (
|
||||
<aside id="sidebar" className="pane">
|
||||
<header className="pane-header pane-header-clickable bg-primary">
|
||||
<h2><a href="#">Insomnia</a></h2>
|
||||
<header className="pane__header bg-primary">
|
||||
<h2>
|
||||
<a href="#" className="pane__header__content">
|
||||
{props.loading ? <i className="fa fa-refresh fa-spin pull-right"></i> : ''}
|
||||
Insomnia
|
||||
</a>
|
||||
</h2>
|
||||
</header>
|
||||
<div className="pane-body">
|
||||
<div className="pane__body">
|
||||
<ul className="sidebar-items">
|
||||
{[0, 1, 2, 3, 4].map((i) => {
|
||||
return <li key={i} className={'sidebar-item ' + (i === 0 ? 'active': '')}>
|
||||
<a href="#">Item 1</a>
|
||||
</li>;
|
||||
<li className="grid">
|
||||
<div className="form-control col">
|
||||
<input type="text" placeholder="Filter Requests"/>
|
||||
</div>
|
||||
<button className="btn" onClick={(e) => props.addRequest()}>
|
||||
<i className="fa fa-plus-circle"></i>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<ul className="sidebar-items">
|
||||
{props.requests.all.map((request) => {
|
||||
const isActive = request.id === props.requests.active.id;
|
||||
return (
|
||||
<li key={request.id} className={'sidebar-item ' + (isActive ? 'active': '')}>
|
||||
<a href="#">{request.name}</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
|
||||
Sidebar.propTypes = {};
|
||||
Sidebar.propTypes = {
|
||||
requests: PropTypes.object.isRequired,
|
||||
loading: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
|
@ -1 +1,10 @@
|
||||
export const ADD_REQUEST = 'ADD_REQUEST';
|
||||
// Global
|
||||
export const GLOBAL_LOAD_START = 'GLOBAL.LOAD_START';
|
||||
export const GLOBAL_LOAD_STOP = 'GLOBAL.LOAD_STOP';
|
||||
export const GLOBAL_STATE_SAVED = 'GLOBAL.STATE_SAVED';
|
||||
export const GLOBAL_STATE_RESTORED = 'GLOBAL.STATE_RESTORED';
|
||||
|
||||
// Requests
|
||||
export const REQUEST_ADD = 'REQUEST.ADD';
|
||||
export const REQUEST_UPDATE = 'REQUEST.UPDATE';
|
||||
export const REQUESTS_LOADED = 'REQUESTS.LOADED';
|
||||
|
11
app/constants/global.js
Normal file
11
app/constants/global.js
Normal file
@ -0,0 +1,11 @@
|
||||
// Global
|
||||
export const LOCALSTORAGE_DEBOUNCE_MILLIS = 1000;
|
||||
export const LOCALSTORAGE_KEY = 'insomnia';
|
||||
|
||||
// HTTP Methods
|
||||
export const METHOD_GET = 'GET';
|
||||
export const METHOD_PUT = 'PUT';
|
||||
export const METHOD_POST = 'POST';
|
||||
export const METHOD_DELETE = 'DELETE';
|
||||
export const METHOD_OPTIONS = 'OPTIONS';
|
||||
export const METHOD_HEAD = 'HEAD';
|
@ -1,42 +0,0 @@
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import Sidebar from '../components/Sidebar'
|
||||
import RequestPane from '../components/RequestPane'
|
||||
import ResponsePane from '../components/ResponsePane'
|
||||
|
||||
class App extends Component {
|
||||
render () {
|
||||
//const { global, todos, actions } = this.props;
|
||||
return (
|
||||
<div className="grid bg-dark">
|
||||
<Sidebar />
|
||||
<RequestPane />
|
||||
<ResponsePane />
|
||||
</div>
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
App.propTypes = {
|
||||
//todos: PropTypes.array.isRequired,
|
||||
//global: PropTypes.object.isRequired,
|
||||
//actions: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
//todos: state.todos,
|
||||
//global: state.global
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
//actions: bindActionCreators(TodoActions, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(App)
|
45
app/containers/AppWrapper.js
Normal file
45
app/containers/AppWrapper.js
Normal file
@ -0,0 +1,45 @@
|
||||
import React, {Component, PropTypes} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import {bindActionCreators} from 'redux'
|
||||
import App from '../components/App.js';
|
||||
|
||||
import * as RequestActions from '../actions/requests'
|
||||
import * as GlobalActions from '../actions/global'
|
||||
|
||||
class AppWrapper extends Component {
|
||||
componentDidMount() {
|
||||
this.props.actions.restoreState();
|
||||
}
|
||||
render() {
|
||||
const {actions, requests, loading, initialized} = this.props;
|
||||
if (!initialized) {
|
||||
return <div>Loading...</div>
|
||||
} else {
|
||||
return <App
|
||||
addRequest={actions.addRequest}
|
||||
updateRequest={actions.updateRequest}
|
||||
requests={requests}
|
||||
loading={loading}/>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
actions: Object.assign(
|
||||
{},
|
||||
bindActionCreators(GlobalActions, dispatch),
|
||||
bindActionCreators(RequestActions, dispatch)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(AppWrapper);
|
||||
|
@ -25,6 +25,10 @@ h2 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pull-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
@ -1,26 +1,31 @@
|
||||
@import '../constants/colors';
|
||||
|
||||
.CodeMirror {
|
||||
.editor {
|
||||
height: 100%;
|
||||
|
||||
.CodeMirror {
|
||||
height: 100% !important;
|
||||
width: 100%;
|
||||
font-size: 13px !important;
|
||||
font-size: 12px !important;
|
||||
font-family: "Source Code Pro", monospace;
|
||||
padding: 5px 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.CodeMirror,
|
||||
.cm-s-seti.CodeMirror, // Hack because seti theme is dumb
|
||||
.CodeMirror-gutters,
|
||||
.CodeMirror-scrollbar-filler,
|
||||
.CodeMirror-gutter-filler {
|
||||
.CodeMirror,
|
||||
.cm-s-seti.CodeMirror, // Hack because seti theme is dumb
|
||||
.CodeMirror-gutters,
|
||||
.CodeMirror-scrollbar-filler,
|
||||
.CodeMirror-gutter-filler {
|
||||
background-color: $bg-dark !important;
|
||||
}
|
||||
}
|
||||
|
||||
.CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-vertical div {
|
||||
.CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-vertical div {
|
||||
background: lighten($bg-dark, 10%);
|
||||
}
|
||||
|
||||
.CodeMirror-linenumber, .CodeMirror-guttermarker-subtle {
|
||||
color: #555555 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.CodeMirror-linenumber, .CodeMirror-guttermarker-subtle {
|
||||
color: #555555 !important;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
@import '../constants/colors';
|
||||
|
||||
#response {
|
||||
.pane-header {
|
||||
.pane__header {
|
||||
|
||||
.tag { // HACK: Find some way to center this vertically
|
||||
margin-top: -4px;
|
||||
|
9
app/css/components/tabs.scss
Normal file
9
app/css/components/tabs.scss
Normal file
@ -0,0 +1,9 @@
|
||||
.ReactTabs {
|
||||
.ReactTabs__Tab {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.ReactTabs__Tab--selected {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
@ -28,6 +28,14 @@ $font-dark-bg: #dddddd;
|
||||
&, a, textarea, input, .btn {
|
||||
color: $font-dark-bg;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: lighten($bg-dark, 4%);
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
background: lighten($bg-dark, 6%);
|
||||
}
|
||||
}
|
||||
|
||||
.bg-light {
|
||||
|
@ -13,6 +13,7 @@
|
||||
}
|
||||
|
||||
.col {
|
||||
display: block;
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@ -33,7 +34,14 @@
|
||||
}
|
||||
|
||||
.row {
|
||||
height: 100%;
|
||||
display: block;
|
||||
flex: 1 1 auto;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.un-grid {
|
||||
display: initial;
|
||||
height: auto;
|
||||
width: auto;
|
||||
}
|
||||
|
@ -19,3 +19,4 @@
|
||||
@import 'components/request';
|
||||
@import 'components/response';
|
||||
@import 'components/tag';
|
||||
@import 'components/tabs';
|
||||
|
@ -2,7 +2,6 @@
|
||||
@import '../constants/colors';
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
outline: none;
|
||||
border: 0;
|
||||
height: $form-height;
|
||||
|
@ -2,29 +2,22 @@
|
||||
@import '../constants/dimensions';
|
||||
|
||||
.pane {
|
||||
.pane-header {
|
||||
.pane__header {
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
border-right: 2px solid rgba(0,0,0,0.05);
|
||||
border-right: 2px solid rgba(0, 0, 0, 0.04);
|
||||
height: $header-height;
|
||||
|
||||
&.pane-header-clickable a, &:not(.pane-header-clickable) > *:first-child {
|
||||
.pane__header__content {
|
||||
display: block;
|
||||
height: $header-height;
|
||||
box-sizing: border-box;
|
||||
padding: $default-padding;
|
||||
}
|
||||
|
||||
&:not(.pane-header_clickable).pane-header-no-padding > *:first-child {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.pane-header-clickable.header-no-padding a {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.pane-body {
|
||||
.pane__body {
|
||||
height: 100%;
|
||||
border-right: 2px solid rgba(0,0,0,0.1);
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import React from 'react'
|
||||
import { render } from 'react-dom'
|
||||
import { Provider } from 'react-redux'
|
||||
import App from './containers/App'
|
||||
import {render} from 'react-dom'
|
||||
import {Provider} from 'react-redux'
|
||||
import configureStore from './stores/configureStore'
|
||||
import AppWrapper from './containers/AppWrapper'
|
||||
|
||||
|
||||
// Global CSS
|
||||
import './css/index.scss'
|
||||
@ -12,6 +13,6 @@ import './css/lib/fontawesome/css/font-awesome.css'
|
||||
const store = configureStore();
|
||||
|
||||
render(
|
||||
<Provider store={store}><App /></Provider>,
|
||||
<Provider store={store}><AppWrapper /></Provider>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
13
app/middleware/localstorage.js
Normal file
13
app/middleware/localstorage.js
Normal file
@ -0,0 +1,13 @@
|
||||
import {LOCALSTORAGE_DEBOUNCE_MILLIS} from "../constants/global";
|
||||
|
||||
let timeout = null;
|
||||
export default (store) => next => action => {
|
||||
let result = next(action);
|
||||
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
localStorage['insomnia'] = JSON.stringify(store.getState(), null, 2);
|
||||
}, LOCALSTORAGE_DEBOUNCE_MILLIS);
|
||||
|
||||
return result;
|
||||
};
|
29
app/reducers/global.js
Normal file
29
app/reducers/global.js
Normal file
@ -0,0 +1,29 @@
|
||||
import * as types from '../constants/actionTypes';
|
||||
import requestsReducer from './requests'
|
||||
import settingsReducer from './settings'
|
||||
|
||||
const initialState = {
|
||||
initialized: false,
|
||||
loading: false
|
||||
};
|
||||
|
||||
export default function (state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case types.GLOBAL_STATE_SAVED:
|
||||
return state;
|
||||
case types.GLOBAL_STATE_RESTORED:
|
||||
return Object.assign({}, state, action.state, {initialized: true});
|
||||
case types.GLOBAL_LOAD_START:
|
||||
return Object.assign({}, state, {loading: true});
|
||||
case types.GLOBAL_LOAD_STOP:
|
||||
return Object.assign({}, state, {loading: false});
|
||||
default:
|
||||
// Send everything else to the child reducers
|
||||
const requests = requestsReducer(state.requests, action);
|
||||
const settings = settingsReducer(state.settings, action);
|
||||
return Object.assign({}, state, {
|
||||
settings,
|
||||
requests
|
||||
});
|
||||
}
|
||||
};
|
@ -1,8 +0,0 @@
|
||||
import { combineReducers } from 'redux'
|
||||
import requests from './requests'
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
requests
|
||||
});
|
||||
|
||||
export default rootReducer;
|
@ -1,22 +1,33 @@
|
||||
import * as types from '../constants/ActionTypes';
|
||||
import * as types from "../constants/actionTypes";
|
||||
|
||||
function request (state, action) {
|
||||
const initialState = {
|
||||
all: [],
|
||||
active: null
|
||||
};
|
||||
|
||||
function requestsReducer (state = [], action) {
|
||||
switch (action.type) {
|
||||
case types.ADD_REQUEST:
|
||||
return {
|
||||
foo: 'bar'
|
||||
};
|
||||
|
||||
case types.REQUEST_ADD:
|
||||
return [...state, action.request];
|
||||
case types.REQUEST_UPDATE:
|
||||
return state.map(request => request.id === action.request.id ? action.request : request);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default function requests (state = [], action) {
|
||||
export default function (state = initialState, action) {
|
||||
let all, active;
|
||||
switch (action.type) {
|
||||
case types.ADD_REQUEST:
|
||||
return [...state, request(undefined, action)];
|
||||
case types.REQUEST_ADD:
|
||||
all = requestsReducer(state.all, action);
|
||||
active = state.active || action.request;
|
||||
return Object.assign({}, state, {all, active});
|
||||
case types.REQUEST_UPDATE:
|
||||
all = requestsReducer(state.all, action);
|
||||
active = state.active.id === action.request.id ? action.request : state.active;
|
||||
return Object.assign({}, state, {all, active});
|
||||
default:
|
||||
return state;
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
11
app/reducers/settings.js
Normal file
11
app/reducers/settings.js
Normal file
@ -0,0 +1,11 @@
|
||||
import * as types from '../constants/actionTypes';
|
||||
|
||||
const initialState = {
|
||||
};
|
||||
|
||||
export default function (state = initialState, action) {
|
||||
switch (action.type) {
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
@ -1,9 +1,12 @@
|
||||
import { createStore, applyMiddleware } from 'redux'
|
||||
import {createStore, applyMiddleware} from 'redux'
|
||||
import thunkMiddleware from 'redux-thunk'
|
||||
import createLogger from 'redux-logger'
|
||||
import rootReducer from '../reducers'
|
||||
import rootReducer from '../reducers/global'
|
||||
import localStorageMiddleware from '../middleware/localstorage'
|
||||
|
||||
const loggerMiddleware = createLogger();
|
||||
const loggerMiddleware = createLogger({
|
||||
collapsed: true
|
||||
});
|
||||
|
||||
export default function configureStore (initialState) {
|
||||
const store = createStore(
|
||||
@ -11,14 +14,15 @@ export default function configureStore (initialState) {
|
||||
initialState,
|
||||
applyMiddleware(
|
||||
thunkMiddleware,
|
||||
localStorageMiddleware,
|
||||
loggerMiddleware
|
||||
)
|
||||
);
|
||||
|
||||
if (module.hot) {
|
||||
// Enable Webpack hot module replacement for reducers
|
||||
module.hot.accept('../reducers', () => {
|
||||
const nextReducer = require('../reducers').default;
|
||||
module.hot.accept('../reducers/global', () => {
|
||||
const nextReducer = require('../reducers/global').default;
|
||||
store.replaceReducer(nextReducer);
|
||||
})
|
||||
}
|
||||
|
15
package.json
15
package.json
@ -7,12 +7,12 @@
|
||||
"url": "git@bitbucket.org:gschier/insomnia.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-polyfill": "^6.7.2",
|
||||
"classnames": "^2.2.3",
|
||||
"codemirror": "^5.12.0",
|
||||
"react": "^0.14.7",
|
||||
"react-dom": "^0.14.7",
|
||||
"react-redux": "^4.4.1",
|
||||
"react-tabs": "^0.5.3",
|
||||
"redux": "^3.3.1",
|
||||
"redux-logger": "^2.6.1",
|
||||
"redux-thunk": "^2.0.1",
|
||||
@ -20,12 +20,17 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.7.2",
|
||||
"babel-jest": "^9.0.3",
|
||||
"babel-loader": "^6.2.4",
|
||||
"babel-polyfill": "^6.7.2",
|
||||
"babel-preset-es2015": "^6.6.0",
|
||||
"babel-preset-react": "^6.5.0",
|
||||
"css-loader": "^0.23.1",
|
||||
"file-loader": "^0.8.5",
|
||||
"jest": "^0.1.40",
|
||||
"jest-cli": "^0.9.2",
|
||||
"node-sass": "^3.4.2",
|
||||
"react-addons-test-utils": "^0.14.7",
|
||||
"react-hot-loader": "^1.3.0",
|
||||
"sass-loader": "^3.2.0",
|
||||
"style-loader": "^0.13.0",
|
||||
@ -34,6 +39,14 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"test": "jest",
|
||||
"build": "webpack --config webpack/prod.config.js # --optimize-minimize"
|
||||
},
|
||||
"jest": {
|
||||
"unmockedModulePathPatterns": [
|
||||
"<rootDir>/node_modules/react",
|
||||
"<rootDir>/node_modules/react-dom",
|
||||
"<rootDir>/node_modules/react-addons-test-utils"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user