Refactored modals a bit

This commit is contained in:
Gregory Schier 2016-04-14 22:23:54 -07:00
parent 949dbff04b
commit 8a4c4fcd4f
16 changed files with 177 additions and 148 deletions

View File

@ -3,9 +3,6 @@ jest.unmock('../global');
jest.unmock('../../constants/global'); jest.unmock('../../constants/global');
jest.unmock('../../constants/actionTypes'); jest.unmock('../../constants/actionTypes');
jest.unmock('../../validators/requestGroup'); jest.unmock('../../validators/requestGroup');
jest.unmock('jsonschema');
jest.unmock('redux-thunk');
jest.unmock('redux-mock-store');
describe('RequestGroup Actions', () => { describe('RequestGroup Actions', () => {
it('should add valid group', () => { it('should add valid group', () => {

View File

@ -5,10 +5,6 @@ jest.unmock('../../constants/actionTypes');
jest.unmock('../../validators/request'); jest.unmock('../../validators/request');
jest.unmock('../../reducers/requests'); jest.unmock('../../reducers/requests');
jest.unmock('../../reducers/global'); jest.unmock('../../reducers/global');
jest.unmock('jsonschema');
jest.unmock('nunjucks');
jest.unmock('redux-thunk');
jest.unmock('redux-mock-store');
// Jest seems to barf when this isn't here // Jest seems to barf when this isn't here
jest.unmock('request'); jest.unmock('request');

View File

@ -1,6 +1,6 @@
import * as types from '../constants/actionTypes' import * as types from '../constants/actionTypes'
import * as methods from '../constants/global' import * as methods from '../constants/global'
import makeRequest from '../lib/request.electron' import makeRequest from '../lib/request'
import {loadStart, loadStop} from './global' import {loadStart, loadStop} from './global'
import {showModal} from './modals' import {showModal} from './modals'
import {REQUEST_RENAME} from '../constants/modals' import {REQUEST_RENAME} from '../constants/modals'
@ -93,7 +93,7 @@ export function sendRequest (request) {
if (err) { if (err) {
console.error(err); console.error(err);
} }
dispatch(setResponse(request.id, response)); dispatch(setResponse(request.id, response));
dispatch(loadStop()); dispatch(loadStop());
}); });

View File

@ -1,36 +1,6 @@
import React, {Component, PropTypes} from 'react'; import React, {Component, PropTypes} from 'react'
import classnames from 'classnames'; import classnames from 'classnames'
import * as ModalActions from '../../actions/modals'
const ModalHeader = (props) => (
<div className="modal__header bg-light">
<div className="grid">
<div className="grid__cell pad">
<div className={props.className}>
{props.children}
</div>
</div>
<div className="grid--v">
<button className="btn btn--compact txt-lg" data-close-modal="true">
<i className="fa fa-times"></i>
</button>
</div>
</div>
</div>
);
const ModalBody = (props) => (
<div className={classnames('modal__body', 'grid__cell', 'scrollable', props.className)}>
{props.children}
</div>
);
const ModalFooter = (props) => (
<div className="modal__footer">
<div className={props.className}>
{props.children}
</div>
</div>
);
class Modal extends Component { class Modal extends Component {
_handleClick (e) { _handleClick (e) {
@ -70,7 +40,7 @@ class Modal extends Component {
} }
componentDidMount () { componentDidMount () {
this.refs.modal.focus(); this.refs.modal && this.refs.modal.focus();
} }
render () { render () {
@ -80,7 +50,8 @@ class Modal extends Component {
className={classnames('modal', 'grid', 'grid--center', this.props.className)} className={classnames('modal', 'grid', 'grid--center', this.props.className)}
onKeyDown={this._keyDown.bind(this)} onKeyDown={this._keyDown.bind(this)}
onClick={this._handleClick.bind(this)}> onClick={this._handleClick.bind(this)}>
<div className={classnames('modal__content', 'grid--v', 'bg-super-light', {tall: this.props.tall})}> <div
className={classnames('modal__content', 'grid--v', 'bg-super-light', {tall: this.props.tall})}>
{this.props.children} {this.props.children}
</div> </div>
</div> </div>
@ -93,4 +64,5 @@ Modal.propTypes = {
tall: PropTypes.bool tall: PropTypes.bool
}; };
export {Modal, ModalHeader, ModalBody, ModalFooter}; export default Modal;

View File

@ -0,0 +1,10 @@
import React from 'react';
import classnames from 'classnames';
const ModalBody = (props) => (
<div className={classnames('modal__body', 'grid__cell', 'scrollable', props.className)}>
{props.children}
</div>
);
export default ModalBody;

View File

@ -0,0 +1,11 @@
import React from 'react';
const ModalFooter = (props) => (
<div className="modal__footer">
<div className={props.className}>
{props.children}
</div>
</div>
);
export default ModalFooter;

View File

@ -0,0 +1,20 @@
import React from 'react';
const ModalHeader = (props) => (
<div className="modal__header bg-light">
<div className="grid">
<div className="grid__cell pad">
<div className={props.className}>
{props.children}
</div>
</div>
<div className="grid--v">
<button className="btn btn--compact txt-lg" data-close-modal="true">
<i className="fa fa-times"></i>
</button>
</div>
</div>
</div>
);
export default ModalHeader;

View File

@ -1,5 +1,8 @@
import React, {Component, PropTypes} from 'react'; import React, {Component, PropTypes} from 'react'
import {Modal, ModalHeader, ModalBody, ModalFooter} from './Modal' import Modal from './Modal'
import ModalBody from './ModalBody'
import ModalHeader from './ModalHeader'
import ModalFooter from './ModalFooter'
class PromptModal extends Component { class PromptModal extends Component {
_onSubmit (e) { _onSubmit (e) {
@ -29,9 +32,9 @@ class PromptModal extends Component {
} }
render () { render () {
const {onClose, submitName, headerName} = this.props; const {onClose, submitName, headerName, ...extraProps} = this.props;
return ( return (
<Modal ref="modal" onClose={onClose}> <Modal ref="modal" onClose={onClose} {...extraProps}>
<ModalHeader>{headerName}</ModalHeader> <ModalHeader>{headerName}</ModalHeader>
<ModalBody className="wide"> <ModalBody className="wide">
<form onSubmit={this._onSubmit.bind(this)} className="wide pad"> <form onSubmit={this._onSubmit.bind(this)} className="wide pad">

View File

@ -1,44 +0,0 @@
import React, {Component, PropTypes} from 'react'
import {Modal, ModalHeader, ModalBody, ModalFooter} from '../base/Modal'
import Editor from '../base/Editor'
import KeyValueEditor from '../base/KeyValueEditor'
import * as modalIds from '../../constants/modals'
class EnvironmentEditModal extends Component {
render () {
const editorOptions = {
mode: 'application/json',
placeholder: '{ "array": [1, 2, 3, 4] }',
theme: 'neat'
};
return (
<Modal {...this.props}>
<ModalHeader>Environment Variables</ModalHeader>
<ModalBody className="grid--v wide pad">
<div>
<KeyValueEditor onChange={() => {}}
pairs={[{name: 'foo', value: 'hello'}]}
namePlaceholder="BASE_URL"
valuePlaceholder="https://api.insomnia.com/v1"/>
</div>
{/*
<h3>Hello</h3>
<Editor value={undefined} options={editorOptions}/>
*/}
</ModalBody>
<ModalFooter className="text-right">
<button className="btn">Done</button>
</ModalFooter>
</Modal>
);
}
}
EnvironmentEditModal.propTypes = {};
EnvironmentEditModal.defaultProps = {
id: modalIds.ENVIRONMENT_EDITOR
};
export default EnvironmentEditModal;

View File

@ -7,14 +7,10 @@ import * as RequestGroupActions from '../../actions/requestGroups'
import * as RequestActions from '../../actions/requests' import * as RequestActions from '../../actions/requests'
import * as modalIds from '../../constants/modals' import * as modalIds from '../../constants/modals'
import PromptModal from '../base/PromptModal' import PromptModal from '../base/PromptModal'
import EnvironmentEditModal from './EnvironmentEditModal'
class Modals extends Component { class Prompts extends Component {
constructor (props) { constructor (props) {
super(props); super(props);
this._modals = [
EnvironmentEditModal
];
this._prompts = {}; this._prompts = {};
this._prompts[modalIds.REQUEST_RENAME] = { this._prompts[modalIds.REQUEST_RENAME] = {
header: 'Rename Request', header: 'Rename Request',
@ -37,53 +33,33 @@ class Modals extends Component {
const {modals, actions} = this.props; const {modals, actions} = this.props;
return ( return (
<div> <div ref="prompts">
<div ref="prompts"> {Object.keys(this._prompts).map(id => {
{Object.keys(this._prompts).map(id => { const promptDef = this._prompts[id];
const promptDef = this._prompts[id]; const modal = modals.find(m => m.id === id);
const modal = modals.find(m => m.id === id);
if (!modal) { if (!modal) {
return null; return null;
} }
return ( return (
<PromptModal <PromptModal
key={id} key={id}
headerName={promptDef.header} id={id}
submitName={promptDef.submit} headerName={promptDef.header}
defaultValue={modal.data.defaultValue} submitName={promptDef.submit}
onClose={() => actions.hideModal(modal.id)} defaultValue={modal.data.defaultValue}
onSubmit={value => promptDef.onSubmit(modal, value)} onClose={() => actions.hideModal(modal.id)}
/> onSubmit={value => promptDef.onSubmit(modal, value)}
) />
})} )
</div> })}
<div ref="modals">
{this._modals.map(c => {
const id = c.defaultProps.id;
const isVisible = modals.find(m => m.id === id);
const modal = React.createElement(c, {
key: id,
onClose: () => actions.hideModal(id)
});
return isVisible ? modal : null;
})}
</div>
<div ref="toasts">
{/*<div className="toast grid">
<div className="toast__message">Request deleted</div>
<button className="btn toast__action">
Undo
</button>
</div> */}
</div>
</div> </div>
); );
} }
} }
Modals.propTypes = { Prompts.propTypes = {
actions: PropTypes.shape({ actions: PropTypes.shape({
hideModal: PropTypes.func.isRequired, hideModal: PropTypes.func.isRequired,
updateRequestGroup: PropTypes.func.isRequired, updateRequestGroup: PropTypes.func.isRequired,
@ -113,5 +89,5 @@ function mapDispatchToProps (dispatch) {
export default connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(Modals); )(Prompts);

View File

@ -0,0 +1,65 @@
import React, {Component, PropTypes} from 'react'
import Modal from '../base/Modal'
import ModalBody from '../base/ModalBody'
import ModalHeader from '../base/ModalHeader'
import ModalFooter from '../base/ModalFooter'
import Editor from '../base/Editor'
import KeyValueEditor from '../base/KeyValueEditor'
import * as modalIds from '../../constants/modals'
class RequestGroupEnvironmentEditModal extends Component {
constructor (props) {
super(props);
this.state = {
pairs: []
}
}
_saveChanges () {
this.props.onChange(this.state.pairs);
}
_keyValueChange (pairs) {
this.setState({pairs});
}
render () {
const editorOptions = {
mode: 'application/json',
placeholder: '{ "array": [1, 2, 3, 4] }',
theme: 'neat'
};
return (
<Modal {...this.props}>
<ModalHeader>Environment Variables</ModalHeader>
<ModalBody className="grid--v wide pad">
<div>
<KeyValueEditor onChange={this._keyValueChange.bind(this)}
pairs={this.state.pairs}
namePlaceholder="BASE_URL"
valuePlaceholder="https://api.insomnia.com/v1"/>
</div>
{/*
<h3>Hello</h3>
<Editor value={undefined} options={editorOptions}/>
*/}
</ModalBody>
<ModalFooter className="text-right">
<button className="btn" onClick={this._saveChanges.bind(this)}>Done</button>
</ModalFooter>
</Modal>
);
}
}
RequestGroupEnvironmentEditModal.propTypes = {
// requestGroup: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired
};
RequestGroupEnvironmentEditModal.defaultProps = {
id: modalIds.ENVIRONMENT_EDITOR
};
export default RequestGroupEnvironmentEditModal;

View File

@ -4,17 +4,19 @@ import {bindActionCreators} from 'redux'
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs' import {Tab, Tabs, TabList, TabPanel} from 'react-tabs'
import Editor from '../components/base/Editor' import Editor from '../components/base/Editor'
import Modals from '../components/modals/ModalContainer' import Prompts from '../components/modals/Prompts'
import KeyValueEditor from '../components/base/KeyValueEditor' import KeyValueEditor from '../components/base/KeyValueEditor'
import RequestBodyEditor from '../components/RequestBodyEditor' import RequestBodyEditor from '../components/RequestBodyEditor'
import RequestAuthEditor from '../components/RequestAuthEditor' import RequestAuthEditor from '../components/RequestAuthEditor'
import RequestUrlBar from '../components/RequestUrlBar' import RequestUrlBar from '../components/RequestUrlBar'
import Sidebar from '../components/Sidebar' import Sidebar from '../components/Sidebar'
import RequestGroupEnvironmentEditModal from '../components/modals/RequestGroupEnvironmentEditModal'
import * as GlobalActions from '../actions/global' import * as GlobalActions from '../actions/global'
import * as RequestGroupActions from '../actions/requestGroups' import * as RequestGroupActions from '../actions/requestGroups'
import * as RequestActions from '../actions/requests' import * as RequestActions from '../actions/requests'
import * as ResponseActions from '../actions/responses' import * as ResponseActions from '../actions/responses'
import * as ModalActions from '../actions/modals'
// Don't inject component styles (use our own) // Don't inject component styles (use our own)
Tabs.setUseDefaultStyles(false); Tabs.setUseDefaultStyles(false);
@ -144,13 +146,19 @@ class App extends Component {
} }
render () { render () {
const {actions, requests, responses, requestGroups, tabs} = this.props; const {actions, requests, responses, requestGroups, tabs, modals} = this.props;
const activeRequest = requests.all.find(r => r.id === requests.active); const activeRequest = requests.all.find(r => r.id === requests.active);
const activeResponse = responses[activeRequest && activeRequest.id]; const activeResponse = responses[activeRequest && activeRequest.id];
return ( return (
<div className="grid bg-super-dark tall"> <div className="grid bg-super-dark tall">
<Modals /> <Prompts />
{!modals.find(m => m.id === RequestGroupEnvironmentEditModal.defaultProps.id) ? null : (
<RequestGroupEnvironmentEditModal
onClose={() => actions.hideModal(RequestGroupEnvironmentEditModal.defaultProps.id)}
onChange={v => console.log(v)}
/>
)}
<Sidebar <Sidebar
activateRequest={actions.activateRequest} activateRequest={actions.activateRequest}
changeFilter={actions.changeFilter} changeFilter={actions.changeFilter}
@ -192,7 +200,8 @@ App.propTypes = {
active: PropTypes.string // "required" but can be null active: PropTypes.string // "required" but can be null
}).isRequired, }).isRequired,
responses: PropTypes.object.isRequired, responses: PropTypes.object.isRequired,
tabs: PropTypes.object.isRequired tabs: PropTypes.object.isRequired,
modals: PropTypes.array.isRequired
}; };
function mapStateToProps (state) { function mapStateToProps (state) {
@ -201,7 +210,8 @@ function mapStateToProps (state) {
requests: state.requests, requests: state.requests,
requestGroups: state.requestGroups, requestGroups: state.requestGroups,
responses: state.responses, responses: state.responses,
tabs: state.tabs tabs: state.tabs,
modals: state.modals
}; };
} }
@ -210,6 +220,7 @@ function mapDispatchToProps (dispatch) {
actions: Object.assign( actions: Object.assign(
{}, {},
bindActionCreators(GlobalActions, dispatch), bindActionCreators(GlobalActions, dispatch),
bindActionCreators(ModalActions, dispatch),
bindActionCreators(RequestGroupActions, dispatch), bindActionCreators(RequestGroupActions, dispatch),
bindActionCreators(RequestActions, dispatch), bindActionCreators(RequestActions, dispatch),
bindActionCreators(ResponseActions, dispatch) bindActionCreators(ResponseActions, dispatch)

View File

@ -1,7 +1,7 @@
import nunjucks from 'nunjucks' import nunjucks from 'nunjucks'
nunjucks.configure({ autoescape: false }); nunjucks.configure({autoescape: false});
export default function (template, context) { export default function (template, context = {}) {
return nunjucks.renderString(template, context); return nunjucks.renderString(template, context);
} }

View File

@ -1,6 +1,5 @@
jest.unmock('../request'); jest.unmock('../request');
jest.unmock('../../constants/global'); jest.unmock('../../constants/global');
jest.unmock('jsonschema');
import validate from '../request'; import validate from '../request';

View File

@ -61,9 +61,21 @@
}, },
"jest": { "jest": {
"unmockedModulePathPatterns": [ "unmockedModulePathPatterns": [
"<rootDir>/node_modules/redux-thunk",
"<rootDir>/node_modules/redux-mock-store",
"<rootDir>/node_modules/nunjucks",
"<rootDir>/node_modules/jsonschema",
"<rootDir>/node_modules/react", "<rootDir>/node_modules/react",
"<rootDir>/node_modules/react-dom", "<rootDir>/node_modules/react-dom",
"<rootDir>/node_modules/react-addons-test-utils" "<rootDir>/node_modules/react-addons-test-utils"
],
"testFileExtensions": [
"test.js"
],
"moduleFileExtensions": [
"js",
"electron.js",
"chrome.js"
] ]
} }
} }

View File

@ -37,7 +37,8 @@ base.plugins = [
base.target = webpackTargetElectronRenderer(base); base.target = webpackTargetElectronRenderer(base);
base.resolve.extensions.push('electron.js'); base.resolve.extensions.push('.electron.js');
console.log(base);
module.exports = base; module.exports = base;