Lots and lots

This commit is contained in:
Gregory Schier 2016-03-22 22:26:27 -07:00
parent 99e147bdb0
commit 11a8a09a1d
29 changed files with 389 additions and 273 deletions

View File

@ -87,3 +87,7 @@ export function updateRequestMethod (id, method) {
export function activateRequest (id) {
return {type: types.REQUEST_ACTIVATE, id};
}
export function changeFilter (filter) {
return {type: types.REQUEST_CHANGE_FILTER, filter};
}

View File

@ -1,5 +1,5 @@
import React, {Component, PropTypes} from 'react'
import Input from './base/Input';
import Input from './base/DebouncingInput';
import Dropdown from './base/Dropdown';
import {METHODS} from '../constants/global';

View File

@ -1,56 +1,94 @@
import React, {PropTypes} from 'react'
import React, {Component, PropTypes} from 'react'
import Dropdown from './base/Dropdown'
import DebouncingInput from './base/DebouncingInput'
const Sidebar = (props) => (
<aside id="sidebar" className="pane">
<div className="grid-v">
<header className="pane__header bg-primary">
<h1>
<Dropdown right={true}>
<a href="#" className="pane__header__content">
<i className="fa fa-angle-down pull-right"></i>
{props.loading ? <i className="fa fa-refresh fa-spin pull-right"></i> : ''}
Insomnia
</a>
<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>
</ul>
</Dropdown>
</h1>
</header>
<div className="pane__body hide-scrollbars bg-dark">
<ul className="sidebar-items">
<li className="grid">
<div className="form-control col">
<input type="text" placeholder="Filter Requests"/>
class Sidebar extends Component {
onFilterChange (value) {
this.props.changeFilter(value);
}
render () {
const {
requests,
activeRequest,
activeFilter,
loading,
activateRequest,
addRequest
} = this.props;
return (
<aside className="sidebar pane">
<div className="grid-v">
<header className="pane__header bg-primary">
<h1>
<Dropdown right={true}>
<a href="#" className="pane__header__content">
<i className="fa fa-angle-down pull-right"></i>
{loading ? <i className="fa fa-refresh fa-spin pull-right"></i> : ''}
Insomnia
</a>
<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>
</ul>
</Dropdown>
</h1>
</header>
<div className="pane__body hide-scrollbars bg-dark">
<div className="stock-height form-control form-control--outlined col">
<DebouncingInput
type="text"
placeholder="Filter Requests"
debounceMillis={100}
value={activeFilter}
onChange={this.onFilterChange.bind(this)}/>
</div>
<button className="btn" onClick={(e) => props.addRequest()}>
<i className="fa fa-plus-circle"></i>
</button>
</li>
</ul>
<ul className="sidebar-items">
{props.requests.map((request) => {
const isActive = request.id === props.activeRequest.id;
return (
<li key={request.id} className={'sidebar-item ' + (isActive ? 'active': '')}>
<a href="#" onClick={() => {props.activateRequest(request.id)}}>{request.name}</a>
<ul>
<li>
<div className="sidebar__item sidebar__item--right-menu grid">
<div className="col">Request Group</div>
<button className="btn" onClick={(e) => addRequest()}>
<i className="fa fa-plus-circle"></i>
</button>
</div>
<ul>
{requests.filter(request => {
if (!activeFilter) {
return true;
}
return request.name.toLowerCase().indexOf(activeFilter.toLowerCase()) >= 0;
}).map(request => {
const isActive = request.id === activeRequest.id;
return (
<li key={request.id} className={isActive ? 'active': ''}>
<button
onClick={() => {activateRequest(request.id)}}
className="btn sidebar__item"
>
{request.name}
</button>
</li>
);
})}
</ul>
</li>
);
})}
</ul>
</div>
</div>
</aside>
);
</ul>
</div>
</div>
</aside>
)
}
}
Sidebar.propTypes = {
activateRequest: PropTypes.func.isRequired,
addRequest: PropTypes.func.isRequired,
changeFilter: PropTypes.func.isRequired,
activeFilter: PropTypes.string,
requests: PropTypes.array.isRequired,
activeRequest: PropTypes.object,
loading: PropTypes.bool.isRequired

View File

@ -2,7 +2,10 @@ import React, {Component, PropTypes} from 'react';
const DEFAULT_DEBOUNCE_MILLIS = 300;
class Input extends Component {
/**
* Input that only fire onChange() after the user stops typing
*/
class DebouncingInput extends Component {
valueChanged (e) {
if (!this.props.onChange) {
return;
@ -36,17 +39,17 @@ class Input extends Component {
className={this.props.className}
initialValue={initialValue || value}
onChange={this.valueChanged.bind(this)}
placeholder="https://google.com"
placeholder={this.props.placeholder}
/>
)
}
}
Input.propTypes = {
DebouncingInput.propTypes = {
onChange: PropTypes.func.isRequired,
initialValue: PropTypes.string,
debounceMillis: PropTypes.number,
value: PropTypes.string
};
export default Input;
export default DebouncingInput;

View File

@ -14,6 +14,10 @@ class Dropdown extends Component {
document.addEventListener('click', this._clickEvenCallback.bind(this));
}
componentWillUnmount () {
document.removeEventListener('click', this._clickEvenCallback);
}
_clickEvenCallback (e) {
if (!this.refs.container.contains(e.target)) {
e.preventDefault();

View File

@ -8,3 +8,4 @@ export const GLOBAL_STATE_RESTORED = 'GLOBAL.STATE_RESTORED';
export const REQUEST_ADD = 'REQUEST.ADD';
export const REQUEST_UPDATE = 'REQUEST.UPDATE';
export const REQUEST_ACTIVATE = 'REQUEST.ACTIVATE';
export const REQUEST_CHANGE_FILTER = 'REQUEST.CHANGE_FILTER';

View File

@ -27,46 +27,63 @@ class App extends Component {
return (
<div className="grid grid-collapse">
<section id="request" className="pane col grid-v">
<header className="pane__header bg-super-light">
<RequestUrlBar
onUrlChange={updateRequestUrl}
onMethodChange={updateRequestMethod}
request={activeRequest}/>
</header>
<div className="pane__body grid-v">
<Tabs selectedIndex={0} className="grid-v">
<TabList className="grid">
<Tab><button className="btn">Body</button></Tab>
<Tab><button className="btn">Params</button></Tab>
<Tab><button className="btn">Auth</button></Tab>
<Tab><button className="btn">Headers</button></Tab>
</TabList>
<TabPanel className="grid-v">
<RequestBodyEditor
className="grid-v"
onChange={updateRequestBody}
request={activeRequest}
options={{mode: activeRequest._mode}}/>
</TabPanel>
<TabPanel className="grid-v">Params</TabPanel>
<TabPanel className="grid-v">Basic Auth</TabPanel>
<TabPanel className="grid-v">Headers</TabPanel>
</Tabs>
<section id="request-pane" className="pane col">
<div className="grid-v">
<header className="pane__header bg-super-light">
<RequestUrlBar
onUrlChange={updateRequestUrl}
onMethodChange={updateRequestMethod}
request={activeRequest}/>
</header>
<div className="pane__body grid-v">
<Tabs selectedIndex={0} className="grid-v">
<TabList className="grid">
<Tab><button className="btn">Body</button></Tab>
<Tab><button className="btn">Params</button></Tab>
<Tab><button className="btn">Auth</button></Tab>
<Tab><button className="btn">Headers</button></Tab>
</TabList>
<TabPanel className="grid-v">
<RequestBodyEditor
className="grid-v"
onChange={updateRequestBody}
request={activeRequest}
options={{mode: activeRequest._mode}}/>
</TabPanel>
<TabPanel className="grid-v pad">Params</TabPanel>
<TabPanel className="grid-v pad">Basic Auth</TabPanel>
<TabPanel className="grid-v pad">Headers</TabPanel>
</Tabs>
</div>
</div>
</section>
<section id="response" className="pane col grid-v">
<header className="pane__header text-center bg-light">
<div className="grid">
<div className="tag success"><strong>200</strong>&nbsp;SUCCESS</div>
<div className="tag"><strong>GET</strong>&nbsp;https://google.com</div>
<section id="response-pane" className="pane col">
<div className="grid-v">
<header className="pane__header text-center bg-light">
<div className="grid">
<div className="tag success"><strong>200</strong>&nbsp;SUCCESS</div>
<div className="tag">TIME&nbsp;<strong>143ms</strong></div>
</div>
</header>
<div className="pane__body grid-v">
<Tabs selectedIndex={0}>
<TabList className="grid">
<Tab><button className="btn">Preview</button></Tab>
<Tab><button className="btn">Raw</button></Tab>
<Tab><button className="btn">Headers</button></Tab>
<Tab><button className="btn">Cookies</button></Tab>
</TabList>
<TabPanel className="grid-v">
<CodeEditor
className="grid-v"
value="{}"
options={{mode: 'application/json', readOnly: true}}/>
</TabPanel>
<TabPanel className="grid-v pad">Raw</TabPanel>
<TabPanel className="grid-v pad">Headers</TabPanel>
<TabPanel className="grid-v pad">Cookies</TabPanel>
</Tabs>
</div>
</header>
<div className="pane__body grid-v">
<CodeEditor
className="grid-v"
value="{}"
options={{mode: 'application/json', readOnly: true}}/>
</div>
</section>
</div>
@ -81,8 +98,10 @@ class App extends Component {
<div className="grid bg-dark">
<Sidebar
activateRequest={actions.activateRequest}
changeFilter={actions.changeFilter}
addRequest={actions.addRequest}
activeRequest={activeRequest}
activeFilter={requests.filter}
loading={loading}
requests={requests.all}/>
<div className="col">
@ -98,6 +117,7 @@ App.propTypes = {
activateRequest: PropTypes.func.isRequired,
updateRequestBody: PropTypes.func.isRequired,
updateRequestUrl: PropTypes.func.isRequired,
changeFilter: PropTypes.func.isRequired,
updateRequestMethod: PropTypes.func.isRequired
}).isRequired,
requests: PropTypes.shape({

View File

@ -1,3 +1,4 @@
@import '../constants/colors';
@import '../constants/dimensions';
.dropdown {
@ -20,7 +21,7 @@
display: none;
z-index: 10;
width: 180px;
border-radius: 2px;
border-radius: $radius-md;
padding: 2px 0;
margin-top: 4px;
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.15);
@ -28,9 +29,17 @@
a, button {
font-size: $font-size-md;
text-align: left;
padding: 10px $default-padding;
padding: 10px $padding-md;
width: 100%;
display: block;
&:hover {
background: $hl-xs;
}
&:active {
background: $hl-md;
}
}
}
}

View File

@ -0,0 +1,73 @@
@import '../constants/dimensions';
@import '../constants/colors';
.form-control {
outline: none;
border: 0;
height: $form-height;
input {
width: 100%;
height: 100%;
}
&.form-control--right button:last-child,
&.form-control--left input {
padding-left: $padding-md;
}
&.form-control--left button:first-child,
&.form-control--right input {
padding-right: $padding-md;
}
&.form-control--outlined {
border: 1px solid $hl-lg;
border-radius: $radius-md;
height: auto;
margin: $padding-sm;
input {
padding: $padding-sm;
}
}
}
.btn {
cursor: pointer;
text-align: center;
padding: 0 ($padding-md * 1.5);
height: $line-height-md;
&.btn--compact {
padding: 0 ($padding-md);
height: $line-height-md / 2;
}
}
.btn:hover {
background: $hl-xs;
}
.btn:active {
background: $hl-md;
}
input, button {
box-sizing: border-box;
font-size: inherit;
text-decoration: inherit;
background: none;
border: 0;
outline: 0;
margin: 0;
&::-webkit-input-placeholder {
color: $hl-xl;
}
}
button {
cursor: pointer;
width: auto;
}

View File

@ -1,4 +1,4 @@
@import 'constants/dimensions';
@import '../constants/dimensions';
.grid {
display: flex;
@ -40,12 +40,6 @@
}
}
.un-grid {
display: initial;
height: auto;
width: auto;
}
@media (max-width: $breakpoint-md) {
.grid.grid-collapse {
display: flex;

View File

@ -1,4 +1,4 @@
@import 'constants/colors';
@import '../constants/colors';
a {
text-decoration: none;

View File

@ -3,23 +3,19 @@
.pane {
.pane__body, .pane__header {
border-right: 1px solid rgba(255, 255, 255, 0.08);
border-right: 1px solid $hl-sm;
border-left: 2px solid rgba(0, 0, 0, 0.2);
}
.pane__header {
border-color: transparent;
height: $header-height;
height: $line-height-md;
.pane__header__content {
display: block;
height: $header-height;
height: $line-height-md;
box-sizing: border-box;
padding: $default-padding;
padding: $padding-md;
}
}
.pane__body {
height: 100%;
}
}

View File

@ -1,8 +0,0 @@
@import '../constants/colors';
@import '../constants/dimensions';
#request {
.url-input {
font-size: $font-size-lg;
}
}

View File

@ -1,10 +1,12 @@
@import '../constants/colors';
@import '../constants/dimensions';
::-webkit-scrollbar {
width: 8px;
width: 6px;
}
::-webkit-scrollbar {
height: 8px;
height: 6px;
}
::-webkit-scrollbar-track {
@ -12,6 +14,6 @@
}
::-webkit-scrollbar-thumb {
background: rgba(100, 100, 100, 0.35);
border-radius: 4px;
background: $hl-md;
border-radius: $radius-md;
}

View File

@ -1,33 +0,0 @@
@import '../constants/dimensions';
@import '../constants/colors';
#sidebar {
height: 100%;
.pane__body {
overflow-y: auto;
}
.pane__header, .pane__body {
border-left: 0;
}
a {
box-sizing: border-box;
display: block;
}
ul.sidebar-items {
width: $sidebar-width;
li.sidebar-item > a {
color: darken($font-dark-bg, 40%);
padding: $sidebar-item-padding;
border-left: 4px solid transparent;
}
}
.sidebar-items li.sidebar-item.active > a {
border-left-color: $bg-primary;
}
}

View File

@ -1,9 +1,17 @@
@import '../constants/colors';
@import '../constants/dimensions';
.ReactTabs {
.ReactTabs__Tab {
opacity: 0.5;
.ReactTabs__TabList {
flex-basis: $line-height-md;
}
.ReactTabs__Tab--selected {
opacity: 1;
.ReactTabs__Tab button {
height: $line-height-md;
color: $hl;
}
.ReactTabs__Tab--selected button {
color: inherit;
}
}

View File

@ -3,7 +3,7 @@
.tag {
display: inline-block;
border: 1px solid;
padding: $slim-padding;
padding: $padding-sm;
margin-right: 1em;
box-sizing: border-box;

View File

@ -4,12 +4,22 @@ $bg-light: #efefef;
$bg-super-light: #ffffff;
$bg-dark: #272824;
$hl-xxs: rgba(130, 130, 130, 0.05);
$hl-xs: rgba(130, 130, 130, 0.09);
$hl-sm: rgba(130, 130, 130, 0.15);
$hl-md: rgba(130, 130, 130, 0.25);
$hl-lg: rgba(130, 130, 130, 0.35);
$hl-xl: rgba(130, 130, 130, 0.5);
$hl: rgba(130, 130, 130, 1);
$success: #598f07;
/* Font Colors */
$font-light-bg: #444444;
$font-dark-bg: #eeeeee;
$faint-opacity: 0.5;
.success {
border-color: $success !important;
color: $success;
@ -20,14 +30,6 @@ $font-dark-bg: #eeeeee;
&, a, textarea, input, button {
color: $font-dark-bg;
}
button:hover, a:hover {
background: lighten($bg-dark, 4%);
}
button:active, a:active {
background: lighten($bg-dark, 6%);
}
}
.bg-primary {
@ -36,14 +38,6 @@ $font-dark-bg: #eeeeee;
&, a, textarea, input, button {
color: $font-dark-bg;
}
button:hover, a:hover {
background: darken($bg-primary, 4%);
}
button:active, a:active {
background: darken($bg-primary, 6%);
}
}
.bg-light {
@ -52,14 +46,6 @@ $font-dark-bg: #eeeeee;
&, a, textarea, input, button {
color: $font-light-bg;
}
button:hover, a:hover {
background: darken($bg-light, 4%);
}
button:active, a:active {
background: darken($bg-light, 6%);
}
}
.bg-super-light {
@ -67,20 +53,4 @@ $font-dark-bg: #eeeeee;
&, a, textarea, input, button {
color: $font-light-bg;
}
button:hover, a:hover {
background: darken($bg-super-light, 4%);
}
button:active, a:active {
background: darken($bg-super-light, 6%);
}
}
.bg-super-light.btn:hover {
background: darken($bg-super-light, 4%);
}
.bg-super-light.btn:active {
background: darken($bg-super-light, 6%);
}

View File

@ -1,5 +1,7 @@
$default-padding: 15px;
$slim-padding: $default-padding / 2;
/* Padding */
$padding-md: 15px;
$padding-sm: $padding-md / 2;
$padding-lg: $padding-md * 2;
/* Fonts */
$font-size: 12px;
@ -11,17 +13,26 @@ $font-size-xxl: $font-size * 1.6;
/* Breakpoints */
$breakpoint-md: 888px;
$breakpoint-sm: 555px;
/* Wrapper */
$header-height: 50px;
$line-height-md: 50px;
$line-height-sm: $line-height-md * 0.75;
/* Sidebar */
$sidebar-width: 250px;
$sidebar-item-padding: $default-padding $default-padding * 2;
$sidebar-width-sm: $sidebar-width * 0.75;
/* Elements */
$form-height: 50px;
/* Borders */
$radius-md: 2px;
.pad {
padding: $padding-md;
}
.txt-sm {
font-size: $font-size-sm;
}

View File

@ -4,20 +4,20 @@
/* Boilerplate */
@import 'reset';
@import 'grid';
@import 'base';
@import 'components/grid';
@import 'layout/base';
/* Styles */
@import 'links';
@import 'components/links';
/* Layout */
@import 'layout/pane';
@import 'layout/forms';
@import 'components/pane';
@import 'components/forms';
/* Components */
@import 'components/sidebar';
@import 'components/request';
@import 'components/response';
@import 'layout/sidebar';
@import 'layout/request';
@import 'layout/response';
@import 'components/tag';
@import 'components/tabs';
@import 'components/scrollbars';

View File

@ -1,5 +1,5 @@
@import 'constants/colors';
@import 'constants/dimensions';
@import '../constants/colors';
@import '../constants/dimensions';
html, body, #root {
display: block;
@ -36,14 +36,18 @@ h2 {
box-sizing: border-box;
}
.wide {
width: 100%;
}
.hide-scrollbars {
&::-webkit-scrollbar {
display: none;
}
}
.ReactTabs__TabList {
height: $header-height;
.stock-height {
height: $line-height-md;
}
strong {

View File

@ -1,42 +0,0 @@
@import '../constants/dimensions';
@import '../constants/colors';
.form-control {
outline: none;
border: 0;
height: $form-height;
&.form-control--right button:last-child,
&.form-control--left input {
padding-left: $default-padding;
}
&.form-control--left button:first-child,
&.form-control--right input {
padding-right: $default-padding;
}
}
.btn {
cursor: pointer;
text-align: center;
padding: $default-padding $default-padding * 1.5;
}
input, button {
box-sizing: border-box;
padding: $default-padding;
font-size: inherit;
text-decoration: inherit;
background: none;
border: 0;
outline: 0;
width: 100%;
height: 100%;
margin: 0;
}
button {
cursor: pointer;
width: auto;
}

View File

@ -0,0 +1,4 @@
@import '../constants/dimensions';
#request-pane {
}

View File

@ -1,8 +1,7 @@
@import '../constants/colors';
#response {
#response-pane {
.pane__header, .pane__body {
border-right: 0;
}
}

View File

@ -0,0 +1,60 @@
@import '../constants/dimensions';
@import '../constants/colors';
.sidebar {
height: 100%;
.pane__body {
overflow-y: auto;
width: $sidebar-width;
}
.pane__header, .pane__body {
border-left: 0;
}
ul {
li.active > .sidebar__item {
color: inherit;
cursor: default;
}
& > li > .sidebar__item {
display: flex;
align-items: center;
padding: 0 $padding-md;
color: $hl;
height: $line-height-sm;
line-height: $line-height-sm;
box-sizing: border-box;
width: 100%;
text-align: left;
&.sidebar__item--right-menu {
padding-right: 0;
}
}
& > li {
& > ul > li {
& > .sidebar__item {
padding-left: $padding-md * 2;
}
& > ul > li {
& > .sidebar__item {
padding-left: $padding-md * 3;
}
}
}
}
}
}
@media (max-width: $breakpoint-sm) {
.sidebar {
.pane__body {
overflow-y: auto;
width: $sidebar-width-sm;
}
}
}

View File

@ -2,7 +2,8 @@ import * as types from "../constants/actionTypes";
const initialState = {
all: [],
active: null
active: null,
filter: ''
};
function requestsReducer (state = [], action) {
@ -18,7 +19,6 @@ function requestsReducer (state = [], action) {
break;
}
}
return [...state, request];
case types.REQUEST_UPDATE:
return state.map(request => {
@ -26,7 +26,8 @@ function requestsReducer (state = [], action) {
return Object.assign({}, request, action.patch);
} else {
return request;
}});
}
});
default:
return state;
}
@ -43,12 +44,17 @@ export default function (state = initialState, action) {
all = requestsReducer(state.all, action);
return Object.assign({}, state, {all});
case types.REQUEST_ACTIVATE:
if (!state.all.find(r => r.id === action.id)) {
if (state.active === action.id) {
// If it's the same, do nothing
return state;
} else if (!state.all.find(r => r.id === action.id)) {
// Don't set if the request doesn't exist
return state;
} else {
return Object.assign({}, state, {active: action.id});
}
case types.REQUEST_CHANGE_FILTER:
return Object.assign({}, state, {filter: action.filter});
default:
return state
}

View File

@ -1,5 +1,3 @@
import * as types from '../constants/actionTypes';
const initialState = {
};

View File

@ -5,34 +5,29 @@ const electron = require('electron');
const app = electron.app; // Module to control application life.
const BrowserWindow = electron.BrowserWindow; // Module to create native browser window.
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
const IS_DEV = process.env.NODE_ENV === 'development';
var mainWindow = null;
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform != 'darwin') {
app.quit();
}
});
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
app.on('ready', function () {
// Create the browser window.
mainWindow = new BrowserWindow({
width: process.env.NODE_ENV === 'development' ? 1600 : 1200,
height: 800
width: IS_DEV ? 1600 : 1200,
height: 800,
minHeight: 400,
minWidth: 500
});
// and load the electron.html of the app.
mainWindow.loadURL(`file://${__dirname}/app/electron.html`);
// Open the DevTools.
if (process.env.NODE_ENV === 'development') {
if (IS_DEV) {
mainWindow.webContents.openDevTools();
}

View File

@ -8,8 +8,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-map';
base.output.path = path.join(base.output.path, '/dev');
base.output.publicPath = 'http://localhost:3000/dist/';