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') {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(loadStart());
const request = buildRequest({name});
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,8 +1,9 @@
import React, {PropTypes} from 'react'
import Dropdown from '../components/Dropdown'
import Dropdown from './base/Dropdown'
const Sidebar = (props) => (
<aside id="sidebar" className="pane">
<div className="grid-v">
<header className="pane__header bg-primary">
<h1>
<Dropdown right={true}>
@ -21,7 +22,7 @@ const Sidebar = (props) => (
</Dropdown>
</h1>
</header>
<div className="pane__body grid-v hide-scrollbars bg-dark">
<div className="pane__body hide-scrollbars bg-dark">
<ul className="sidebar-items">
<li className="grid">
<div className="form-control col">
@ -32,7 +33,7 @@ const Sidebar = (props) => (
</button>
</li>
</ul>
<ul className="sidebar-items row">
<ul className="sidebar-items">
{props.requests.map((request) => {
const isActive = request.id === props.activeRequest.id;
return (
@ -43,11 +44,13 @@ const Sidebar = (props) => (
})}
</ul>
</div>
</div>
</aside>
);
Sidebar.propTypes = {
activateRequest: PropTypes.func.isRequired,
addRequest: PropTypes.func.isRequired,
requests: PropTypes.array.isRequired,
activeRequest: PropTypes.object,
loading: PropTypes.bool.isRequired

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import {getDOMNode} from 'react-dom';
import CodeMirror from 'codemirror';
// Modes
import 'codemirror/mode/css/css';
import '../../../node_modules/codemirror/mode/css/css';
import 'codemirror/mode/htmlmixed/htmlmixed';
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.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
import 'codemirror/theme/material.css'
import 'codemirror/theme/railscasts.css'
@ -32,7 +43,7 @@ import 'codemirror/theme/seti.css'
import 'codemirror/theme/monokai.css'
// App styles
import '../css/components/editor.scss';
import '../../css/components/editor.scss';
const DEFAULT_DEBOUNCE_MILLIS = 500;
@ -40,7 +51,12 @@ const BASE_CODEMIRROR_OPTIONS = {
theme: 'monokai',
lineNumbers: true,
foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
// lint: true,
gutters: [
'CodeMirror-linenumbers',
'CodeMirror-foldgutter',
'CodeMirror-lint-markers'
],
cursorScrollMargin: 80,
extraKeys: {
"Ctrl-Q": function (cm) {
@ -60,13 +76,11 @@ class Editor extends Component {
var textareaNode = this.refs.textarea;
this.codeMirror = CodeMirror.fromTextArea(textareaNode, BASE_CODEMIRROR_OPTIONS);
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.codeMirror.on('paste', this.codemirrorValueChanged.bind(this));
this.codeMirror.on('change', this._codemirrorValueChanged.bind(this));
this.codeMirror.on('paste', this._codemirrorValueChanged.bind(this));
this._currentCodemirrorValue = this.props.defaultValue || this.props.value || '';
this.codemirrorSetValue(this._currentCodemirrorValue);
this.codemirrorSetOptions(this.props.options);
this._codemirrorSetValue(this._currentCodemirrorValue);
this._codemirrorSetOptions(this.props.options);
}
componentWillUnmount () {
@ -77,35 +91,48 @@ class Editor extends Component {
}
componentWillReceiveProps (nextProps) {
if (this.codeMirror && nextProps.value !== undefined && this._currentCodemirrorValue !== nextProps.value) {
this.codemirrorSetValue(nextProps.value);
// Don't update if no CodeMirror instance
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
this.codemirrorSetOptions(nextProps.options);
this._codemirrorSetOptions(nextProps.options);
}
/**
* Focus the cursor to the editor
*/
focus () {
if (this.codeMirror) {
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
*/
codemirrorSetOptions (options) {
_codemirrorSetOptions (options) {
if (options.mode === 'json') {
options.mode = 'application/json';
}
if (options.mode === 'application/json') {
// ld+json looks better because keys are a different color
options.mode = 'application/ld+json';
}
@ -118,7 +145,7 @@ class Editor extends Component {
* Wrapper function to add extra behaviour to our onChange event
* @param doc CodeMirror document
*/
codemirrorValueChanged (doc) {
_codemirrorValueChanged (doc) {
// Update our cached value
var newValue = doc.getValue();
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
*/
codemirrorSetValue (code) {
_codemirrorSetValue (code) {
this._ignoreNextChange = true;
this.codeMirror.setValue(code);
}
@ -149,10 +176,12 @@ class Editor extends Component {
render () {
return (
<div className={`editor ${this.props.className || ''}`}>
<textarea name={this.props.path}
<textarea
name={this.props.path}
ref='textarea'
defaultValue={this.props.value}
autoComplete='off'></textarea>
autoComplete='off'>
</textarea>
</div>
);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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