Updated stuff

This commit is contained in:
Gregory Schier 2016-03-21 22:01:58 -07:00
parent 56c5015fc3
commit 103d41f5fb
13 changed files with 193 additions and 106 deletions

View File

@ -37,7 +37,7 @@ function buildRequest (request) {
} }
export function addRequest (name = 'My Request') { export function addRequest (name = 'My Request') {
return (dispatch, getState) => { return (dispatch) => {
dispatch(loadStart()); dispatch(loadStart());
const request = buildRequest({name}); const request = buildRequest({name});
dispatch({type: types.REQUEST_ADD, request}); dispatch({type: types.REQUEST_ADD, request});

View File

@ -0,0 +1,30 @@
import React, {Component, PropTypes} from 'react';
import CodeEditor from './base/Editor'
class RequestBodyEditor extends Component {
shouldComponentUpdate (nextProps) {
return this.props.request !== nextProps.request;
}
render () {
const {request, onChange, className} = this.props;
return (
<CodeEditor
value={request.body}
className={className}
onChange={onChange}
options={{mode: request._mode}}
/>
)
}
}
RequestBodyEditor.propTypes = {
request: PropTypes.shape({
body: PropTypes.string.isRequired,
_mode: PropTypes.string.isRequired
}).isRequired,
onChange: PropTypes.func.isRequired
};
export default RequestBodyEditor;

View File

@ -1,53 +1,56 @@
import React, {PropTypes} from 'react' import React, {PropTypes} from 'react'
import Dropdown from '../components/Dropdown' import Dropdown from './base/Dropdown'
const Sidebar = (props) => ( const Sidebar = (props) => (
<aside id="sidebar" className="pane"> <aside id="sidebar" className="pane">
<header className="pane__header bg-primary"> <div className="grid-v">
<h1> <header className="pane__header bg-primary">
<Dropdown right={true}> <h1>
<a href="#" className="pane__header__content"> <Dropdown right={true}>
<i className="fa fa-angle-down pull-right"></i> <a href="#" className="pane__header__content">
{props.loading ? <i className="fa fa-refresh fa-spin pull-right"></i> : ''} <i className="fa fa-angle-down pull-right"></i>
Insomnia {props.loading ? <i className="fa fa-refresh fa-spin pull-right"></i> : ''}
</a> Insomnia
<ul className="bg-super-light"> </a>
<li><button>hello</button></li> <ul className="bg-super-light">
<li><button>hello</button></li> <li><button>hello</button></li>
<li><button>hello</button></li> <li><button>hello</button></li>
<li><button>hello</button></li> <li><button>hello</button></li>
<li><button>hello</button></li> <li><button>hello</button></li>
</ul> <li><button>hello</button></li>
</Dropdown> </ul>
</h1> </Dropdown>
</header> </h1>
<div className="pane__body grid-v hide-scrollbars bg-dark"> </header>
<ul className="sidebar-items"> <div className="pane__body hide-scrollbars bg-dark">
<li className="grid"> <ul className="sidebar-items">
<div className="form-control col"> <li className="grid">
<input type="text" placeholder="Filter Requests"/> <div className="form-control col">
</div> <input type="text" placeholder="Filter Requests"/>
<button className="btn" onClick={(e) => props.addRequest()}> </div>
<i className="fa fa-plus-circle"></i> <button className="btn" onClick={(e) => props.addRequest()}>
</button> <i className="fa fa-plus-circle"></i>
</li> </button>
</ul> </li>
<ul className="sidebar-items row"> </ul>
{props.requests.map((request) => { <ul className="sidebar-items">
const isActive = request.id === props.activeRequest.id; {props.requests.map((request) => {
return ( const isActive = request.id === props.activeRequest.id;
<li key={request.id} className={'sidebar-item ' + (isActive ? 'active': '')}> return (
<a href="#" onClick={() => {props.activateRequest(request.id)}}>{request.name}</a> <li key={request.id} className={'sidebar-item ' + (isActive ? 'active': '')}>
</li> <a href="#" onClick={() => {props.activateRequest(request.id)}}>{request.name}</a>
); </li>
})} );
</ul> })}
</ul>
</div>
</div> </div>
</aside> </aside>
); );
Sidebar.propTypes = { Sidebar.propTypes = {
activateRequest: PropTypes.func.isRequired, activateRequest: PropTypes.func.isRequired,
addRequest: PropTypes.func.isRequired,
requests: PropTypes.array.isRequired, requests: PropTypes.array.isRequired,
activeRequest: PropTypes.object, activeRequest: PropTypes.object,
loading: PropTypes.bool.isRequired loading: PropTypes.bool.isRequired

View File

@ -1,6 +1,6 @@
import React, {Component, PropTypes} from 'react' import React, {Component, PropTypes} from 'react'
import Input from '../components/Input'; import Input from './base/Input';
import Dropdown from '../components/Dropdown'; import Dropdown from './base/Dropdown';
import {METHODS} from '../constants/global'; import {METHODS} from '../constants/global';
class UrlInput extends Component { class UrlInput extends Component {

View File

@ -3,34 +3,39 @@ import React, {Component, PropTypes} from 'react';
class Dropdown extends Component { class Dropdown extends Component {
constructor () { constructor () {
super(); super();
this.state = {open: false}; this.state = {
open: false
};
} }
componentDidMount () { componentDidMount () {
// Capture clicks outside the component and close the dropdown // Capture clicks outside the component and close the dropdown
// TODO: Remove this listener when component unmounts // TODO: Remove this listener when component unmounts
document.addEventListener('click', (e) => { document.addEventListener('click', this._clickEvenCallback.bind(this));
if (!this.refs.container.contains(e.target)) {
e.preventDefault();
this.setState({open: false});
}
});
} }
handleClick (e) { _clickEvenCallback (e) {
if (!this.refs.container.contains(e.target)) {
e.preventDefault();
this.setState({open: false});
}
}
_handleClick (e) {
e.preventDefault(); e.preventDefault();
this.setState({open: !this.state.open}); this.setState({open: !this.state.open});
} }
render () { render () {
const {initialValue, value} = this.props; const classes = ['dropdown'];
this.state.open && classes.push('dropdown--open');
this.props.right && classes.push('dropdown--right');
return ( return (
<div ref="container" <div ref="container"
className={'dropdown ' + className={classes.join(' ')}
(this.state.open ? 'dropdown--open ' : ' ') + onClick={this._handleClick.bind(this)}>
(this.props.right ? 'dropdown--right ' : ' ')
}
onClick={this.handleClick.bind(this)}>
{this.props.children} {this.props.children}
</div> </div>
) )

View File

@ -3,7 +3,7 @@ import {getDOMNode} from 'react-dom';
import CodeMirror from 'codemirror'; import CodeMirror from 'codemirror';
// Modes // Modes
import 'codemirror/mode/css/css'; import '../../../node_modules/codemirror/mode/css/css';
import 'codemirror/mode/htmlmixed/htmlmixed'; import 'codemirror/mode/htmlmixed/htmlmixed';
import 'codemirror/mode/javascript/javascript'; import 'codemirror/mode/javascript/javascript';
@ -20,6 +20,17 @@ import 'codemirror/addon/fold/xml-fold';
import 'codemirror/addon/fold/foldgutter'; import 'codemirror/addon/fold/foldgutter';
import 'codemirror/addon/fold/foldgutter.css'; import 'codemirror/addon/fold/foldgutter.css';
// TODO: Figure out how to lint (json-lint doesn't build in webpack environment)
// import 'codemirror/addon/lint/lint';
// import 'codemirror/addon/lint/json-lint';
// import 'codemirror/addon/lint/html-lint';
// import 'codemirror/addon/lint/lint.css';
// import * as jsonlint from 'jsonlint';
// import * as htmlhint from 'htmlhint';
// window.jsonlint = jsonlint;
// window.htmlhint = htmlhint;
// CSS Themes // CSS Themes
import 'codemirror/theme/material.css' import 'codemirror/theme/material.css'
import 'codemirror/theme/railscasts.css' import 'codemirror/theme/railscasts.css'
@ -32,7 +43,7 @@ import 'codemirror/theme/seti.css'
import 'codemirror/theme/monokai.css' import 'codemirror/theme/monokai.css'
// App styles // App styles
import '../css/components/editor.scss'; import '../../css/components/editor.scss';
const DEFAULT_DEBOUNCE_MILLIS = 500; const DEFAULT_DEBOUNCE_MILLIS = 500;
@ -40,7 +51,12 @@ const BASE_CODEMIRROR_OPTIONS = {
theme: 'monokai', theme: 'monokai',
lineNumbers: true, lineNumbers: true,
foldGutter: true, foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], // lint: true,
gutters: [
'CodeMirror-linenumbers',
'CodeMirror-foldgutter',
'CodeMirror-lint-markers'
],
cursorScrollMargin: 80, cursorScrollMargin: 80,
extraKeys: { extraKeys: {
"Ctrl-Q": function (cm) { "Ctrl-Q": function (cm) {
@ -60,13 +76,11 @@ class Editor extends Component {
var textareaNode = this.refs.textarea; var textareaNode = this.refs.textarea;
this.codeMirror = CodeMirror.fromTextArea(textareaNode, BASE_CODEMIRROR_OPTIONS); this.codeMirror = CodeMirror.fromTextArea(textareaNode, BASE_CODEMIRROR_OPTIONS);
this.codeMirror.on('change', this.codemirrorValueChanged.bind(this)); this.codeMirror.on('change', this._codemirrorValueChanged.bind(this));
this.codeMirror.on('focus', this.focusChanged.bind(this, true)); this.codeMirror.on('paste', this._codemirrorValueChanged.bind(this));
this.codeMirror.on('blur', this.focusChanged.bind(this, false));
this.codeMirror.on('paste', this.codemirrorValueChanged.bind(this));
this._currentCodemirrorValue = this.props.defaultValue || this.props.value || ''; this._currentCodemirrorValue = this.props.defaultValue || this.props.value || '';
this.codemirrorSetValue(this._currentCodemirrorValue); this._codemirrorSetValue(this._currentCodemirrorValue);
this.codemirrorSetOptions(this.props.options); this._codemirrorSetOptions(this.props.options);
} }
componentWillUnmount () { componentWillUnmount () {
@ -77,35 +91,48 @@ class Editor extends Component {
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
if (this.codeMirror && nextProps.value !== undefined && this._currentCodemirrorValue !== nextProps.value) { // Don't update if no CodeMirror instance
this.codemirrorSetValue(nextProps.value); if (!this.codeMirror) {
return;
} }
// Don't update if no value passed
if (nextProps.value === undefined) {
return;
}
// Don't update if same value passed again
if (this._currentCodemirrorValue === nextProps.value) {
return;
}
// Set the new value
this._codemirrorSetValue(nextProps.value);
// Reset any options that may have changed // Reset any options that may have changed
this.codemirrorSetOptions(nextProps.options); this._codemirrorSetOptions(nextProps.options);
} }
/**
* Focus the cursor to the editor
*/
focus () { focus () {
if (this.codeMirror) { if (this.codeMirror) {
this.codeMirror.focus(); this.codeMirror.focus();
} }
} }
focusChanged (focused) {
this.setState({isFocused: focused});
this.props.onFocusChange && this.props.onFocusChange(focused);
}
/** /**
* Set options on the CodeMirror editor while also sanitizing them * Sets options on the CodeMirror editor while also sanitizing them
* @param options * @param options
*/ */
codemirrorSetOptions (options) { _codemirrorSetOptions (options) {
if (options.mode === 'json') { if (options.mode === 'json') {
options.mode = 'application/json'; options.mode = 'application/json';
} }
if (options.mode === 'application/json') { if (options.mode === 'application/json') {
// ld+json looks better because keys are a different color
options.mode = 'application/ld+json'; options.mode = 'application/ld+json';
} }
@ -118,7 +145,7 @@ class Editor extends Component {
* Wrapper function to add extra behaviour to our onChange event * Wrapper function to add extra behaviour to our onChange event
* @param doc CodeMirror document * @param doc CodeMirror document
*/ */
codemirrorValueChanged (doc) { _codemirrorValueChanged (doc) {
// Update our cached value // Update our cached value
var newValue = doc.getValue(); var newValue = doc.getValue();
this._currentCodemirrorValue = newValue; this._currentCodemirrorValue = newValue;
@ -138,10 +165,10 @@ class Editor extends Component {
} }
/** /**
* Set the CodeMirror value without triggering the onChange event * Sets the CodeMirror value without triggering the onChange event
* @param code the code to set in the editor * @param code the code to set in the editor
*/ */
codemirrorSetValue (code) { _codemirrorSetValue (code) {
this._ignoreNextChange = true; this._ignoreNextChange = true;
this.codeMirror.setValue(code); this.codeMirror.setValue(code);
} }
@ -149,10 +176,12 @@ class Editor extends Component {
render () { render () {
return ( return (
<div className={`editor ${this.props.className || ''}`}> <div className={`editor ${this.props.className || ''}`}>
<textarea name={this.props.path} <textarea
ref='textarea' name={this.props.path}
defaultValue={this.props.value} ref='textarea'
autoComplete='off'></textarea> defaultValue={this.props.value}
autoComplete='off'>
</textarea>
</div> </div>
); );
} }

View File

@ -3,7 +3,8 @@ import {connect} from 'react-redux'
import {bindActionCreators} from 'redux' import {bindActionCreators} from 'redux'
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs'; import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
import CodeEditor from '../components/CodeEditor' import CodeEditor from '../components/base/Editor'
import RequestBodyEditor from '../components/RequestBodyEditor'
import UrlInput from '../components/UrlInput' import UrlInput from '../components/UrlInput'
import Sidebar from '../components/Sidebar' import Sidebar from '../components/Sidebar'
@ -14,8 +15,7 @@ import * as GlobalActions from '../actions/global'
Tabs.setUseDefaultStyles(false); Tabs.setUseDefaultStyles(false);
class App extends Component { class App extends Component {
renderPageBody() { renderPageBody (actions, activeRequest) {
const {actions, activeRequest} = this.props;
if (!activeRequest) { if (!activeRequest) {
return <div></div>; return <div></div>;
@ -24,15 +24,16 @@ class App extends Component {
const updateRequestBody = actions.updateRequestBody.bind(null, activeRequest.id); const updateRequestBody = actions.updateRequestBody.bind(null, activeRequest.id);
const updateRequestUrl = actions.updateRequestUrl.bind(null, activeRequest.id); const updateRequestUrl = actions.updateRequestUrl.bind(null, activeRequest.id);
const updateRequestMethod = actions.updateRequestMethod.bind(null, activeRequest.id); const updateRequestMethod = actions.updateRequestMethod.bind(null, activeRequest.id);
return ( return (
<div className="grid grid-collapse"> <div className="grid grid-collapse">
<section id="request" className="pane col grid-v"> <section id="request" className="pane col grid-v">
<header className="pane__header bg-super-light"> <header className="pane__header bg-super-light">
<UrlInput onUrlChange={updateRequestUrl} <UrlInput
onMethodChange={updateRequestMethod} onUrlChange={updateRequestUrl}
method={activeRequest.method} onMethodChange={updateRequestMethod}
urlValue={activeRequest.url}/> method={activeRequest.method}
urlValue={activeRequest.url}/>
</header> </header>
<div className="pane__body grid-v"> <div className="pane__body grid-v">
<Tabs selectedIndex={0} className="grid-v"> <Tabs selectedIndex={0} className="grid-v">
@ -43,10 +44,11 @@ class App extends Component {
<Tab><button className="btn">Headers</button></Tab> <Tab><button className="btn">Headers</button></Tab>
</TabList> </TabList>
<TabPanel className="grid-v"> <TabPanel className="grid-v">
<CodeEditor value={activeRequest.body} <RequestBodyEditor
className="grid-v" className="grid-v"
onChange={updateRequestBody} onChange={updateRequestBody}
options={{mode: activeRequest._mode}}/> request={activeRequest}
options={{mode: activeRequest._mode}}/>
</TabPanel> </TabPanel>
<TabPanel className="grid-v">Params</TabPanel> <TabPanel className="grid-v">Params</TabPanel>
<TabPanel className="grid-v">Basic Auth</TabPanel> <TabPanel className="grid-v">Basic Auth</TabPanel>
@ -57,8 +59,8 @@ class App extends Component {
<section id="response" className="pane col grid-v"> <section id="response" className="pane col grid-v">
<header className="pane__header text-center bg-light"> <header className="pane__header text-center bg-light">
<div className="pane__header__content"> <div className="pane__header__content">
<div className="tag success"><strong>200</strong> SUCCESS</div> <div className="tag success"><strong>200</strong>&nbsp;SUCCESS</div>
<div className="tag"><strong>GET</strong> https://google.com</div> <div className="tag"><strong>GET</strong>&nbsp;https://google.com</div>
</div> </div>
</header> </header>
<div className="pane__body grid-v"> <div className="pane__body grid-v">
@ -71,19 +73,21 @@ class App extends Component {
</div> </div>
) )
} }
render () { render () {
const {actions, loading, activeRequest, allRequests} = this.props; const {actions, loading, requests} = this.props;
const activeRequest = requests.all.find(r => r.id === requests.active);
return ( return (
<div className="grid bg-dark"> <div className="grid bg-dark">
<Sidebar <Sidebar
activateRequest={actions.activateRequest} activateRequest={actions.activateRequest}
addRequest={actions.addRequest} addRequest={actions.addRequest}
loading={loading}
activeRequest={activeRequest} activeRequest={activeRequest}
requests={allRequests}/> loading={loading}
requests={requests.all}/>
<div className="col"> <div className="col">
{this.renderPageBody()} {this.renderPageBody(actions, activeRequest)}
</div> </div>
</div> </div>
) )
@ -91,16 +95,23 @@ class App extends Component {
} }
App.propTypes = { App.propTypes = {
allRequests: PropTypes.array.isRequired, actions: PropTypes.shape({
activeRequest: PropTypes.object, activateRequest: PropTypes.func.isRequired,
updateRequestBody: PropTypes.func.isRequired,
updateRequestUrl: PropTypes.func.isRequired,
updateRequestMethod: PropTypes.func.isRequired
}).isRequired,
requests: PropTypes.shape({
all: PropTypes.array.isRequired,
active: PropTypes.string // "required" but can be null
}).isRequired,
loading: PropTypes.bool.isRequired loading: PropTypes.bool.isRequired
}; };
function mapStateToProps (state) { function mapStateToProps (state) {
return { return {
actions: state.actions, actions: state.actions,
allRequests: state.requests.all, requests: state.requests,
activeRequest: state.requests.all.find(r => r.id === state.requests.active),
loading: state.loading loading: state.loading
}; };
} }

View File

@ -32,6 +32,9 @@
box-sizing: border-box; box-sizing: border-box;
margin-bottom: 0; margin-bottom: 0;
padding-bottom: 0; padding-bottom: 0;
/* Normal Overrides */
cursor: text; // Show text cursor everywhere (not just in .Codemirror-lines)
} }
.CodeMirror-lines { .CodeMirror-lines {

View File

@ -37,8 +37,8 @@ export default function (state = initialState, action) {
switch (action.type) { switch (action.type) {
case types.REQUEST_ADD: case types.REQUEST_ADD:
all = requestsReducer(state.all, action); all = requestsReducer(state.all, action);
return Object.assign({}, state, {all, active});
active = action.request.id; active = action.request.id;
return Object.assign({}, state, {all, active});
case types.REQUEST_UPDATE: case types.REQUEST_UPDATE:
all = requestsReducer(state.all, action); all = requestsReducer(state.all, action);
return Object.assign({}, state, {all}); return Object.assign({}, state, {all});

View File

@ -10,6 +10,8 @@
"dependencies": { "dependencies": {
"classnames": "^2.2.3", "classnames": "^2.2.3",
"codemirror": "^5.12.0", "codemirror": "^5.12.0",
"htmlhint": "^0.9.12",
"jsonlint": "^1.6.2",
"jsonschema": "^1.1.0", "jsonschema": "^1.1.0",
"react": "^0.14.7", "react": "^0.14.7",
"react-dom": "^0.14.7", "react-dom": "^0.14.7",

View File

@ -2,6 +2,8 @@ var path = require('path');
var webpack = require('webpack'); var webpack = require('webpack');
module.exports = { module.exports = {
target: 'web',
devtool: 'source-map',
context: path.join(__dirname, '../app'), context: path.join(__dirname, '../app'),
entry: [ entry: [
'./index.js', './index.js',

View File

@ -7,6 +7,8 @@ base.entry = [
'webpack/hot/only-dev-server' 'webpack/hot/only-dev-server'
].concat(base.entry); ].concat(base.entry);
base.devtool = 'eval'; // Fastest form of source maps
base.debug = true; base.debug = true;
base.devtool = 'inline-source-maps'; base.devtool = 'inline-source-maps';
base.output.path = path.join(base.output.path, '/dev'); base.output.path = path.join(base.output.path, '/dev');