diff options
author | Maximilian Hils <git@maximilianhils.com> | 2016-07-21 01:14:55 -0700 |
---|---|---|
committer | Maximilian Hils <git@maximilianhils.com> | 2016-07-21 01:14:55 -0700 |
commit | 8a3a21bba1e6706295cc22e1b3a876a7a86cb705 (patch) | |
tree | 9408324d45850fd4def75c60cf3537a12f632217 /web/src/js | |
parent | 427fffbcb82ba16dd65a4fee4000a05215e859b8 (diff) | |
download | mitmproxy-8a3a21bba1e6706295cc22e1b3a876a7a86cb705.tar.gz mitmproxy-8a3a21bba1e6706295cc22e1b3a876a7a86cb705.tar.bz2 mitmproxy-8a3a21bba1e6706295cc22e1b3a876a7a86cb705.zip |
web: fix ValueEditor, clean up code
Diffstat (limited to 'web/src/js')
24 files changed, 638 insertions, 733 deletions
diff --git a/web/src/js/components/ContentView.jsx b/web/src/js/components/ContentView.jsx index 766de26f..f7eafc89 100644 --- a/web/src/js/components/ContentView.jsx +++ b/web/src/js/components/ContentView.jsx @@ -5,7 +5,7 @@ import * as ContentViews from './ContentView/ContentViews' import * as MetaViews from './ContentView/MetaViews' import ContentLoader from './ContentView/ContentLoader' import ViewSelector from './ContentView/ViewSelector' -import { setContentView, setDisplayLarge, setModifiedFlowContent } from '../ducks/ui' +import { setContentView, displayLarge, updateEdit } from '../ducks/ui/flow' import CodeEditor from './common/CodeEditor' ContentView.propTypes = { @@ -30,7 +30,7 @@ function ContentView(props) { } if (!displayLarge && ContentView.isContentTooLarge(message)) { - return <MetaViews.ContentTooLarge {...props} onClick={() => setDisplayLarge(true)}/> + return <MetaViews.ContentTooLarge {...props} onClick={displayLarge}/> } const View = ContentViews[contentView] @@ -80,13 +80,13 @@ function ContentView(props) { export default connect( state => ({ - contentView: state.ui.contentView, - displayLarge: state.ui.displayLarge, - isFlowEditorOpen : state.ui.isFlowEditorOpen + contentView: state.ui.flow.contentView, + displayLarge: state.ui.flow.displayLarge, + isFlowEditorOpen : !!state.ui.flow.modifiedFlow // FIXME }), { selectView: setContentView, - setDisplayLarge, - setModifiedFlowContent + displayLarge, + updateEdit, } )(ContentView) diff --git a/web/src/js/components/FlowView.jsx b/web/src/js/components/FlowView.jsx index 5ba472a5..a80dc040 100644 --- a/web/src/js/components/FlowView.jsx +++ b/web/src/js/components/FlowView.jsx @@ -7,7 +7,7 @@ import { Request, Response, ErrorView as Error } from './FlowView/Messages' import Details from './FlowView/Details' import Prompt from './Prompt' -import { setPrompt, selectTab } from '../ducks/ui' +import { selectTab } from '../ducks/ui/flow' export default class FlowView extends Component { @@ -90,9 +90,9 @@ export default class FlowView extends Component { export default connect( state => ({ promptOpen: state.ui.promptOpen, + tab: state.ui.flow.tab }), { - setPrompt, selectTab, } )(FlowView) diff --git a/web/src/js/components/FlowView/FlowEditorButton.jsx b/web/src/js/components/FlowView/FlowEditorButton.jsx deleted file mode 100644 index 3d0d1d16..00000000 --- a/web/src/js/components/FlowView/FlowEditorButton.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { PropTypes, Component } from 'react' -import { connect } from 'react-redux' - -import {closeFlowEditor} from '../../ducks/ui.js' -import {openFlowEditor} from '../../ducks/ui.js' - -FlowEditorButton.propTypes = { - isFlowEditorOpen: PropTypes.bool.isRequired, - content: PropTypes.string.isRequired, - onContentChange: PropTypes.func.isRequired -} - -function FlowEditorButton ({ isFlowEditorOpen, closeFlowEditor, openFlowEditor, onContentChange, content }) { - return ( - <div className="edit-flow-container"> - {isFlowEditorOpen ? - <a className="edit-flow" onClick={() => {onContentChange(content); closeFlowEditor()}}> - <i className="fa fa-check"/> - </a> - : - <a className="edit-flow" onClick={() => openFlowEditor()}> - <i className="fa fa-pencil"/> - </a> - } - </div> - ) -} - -export default connect( - state => ({ - isFlowEditorOpen: state.ui.isFlowEditorOpen, - content: state.ui.modifiedFlow.content - }), - { - closeFlowEditor, - openFlowEditor - - } -)(FlowEditorButton) diff --git a/web/src/js/components/FlowView/Headers.jsx b/web/src/js/components/FlowView/Headers.jsx index 880eeda1..706dd404 100644 --- a/web/src/js/components/FlowView/Headers.jsx +++ b/web/src/js/components/FlowView/Headers.jsx @@ -1,12 +1,21 @@ import React, { Component, PropTypes } from 'react' import ReactDOM from 'react-dom' -import ValueEditor from '../ValueEditor' -import { Key } from '../../utils.js' +import ValueEditor from '../ValueEditor/ValueEditor' +import { Key } from '../../utils' class HeaderEditor extends Component { + constructor(props) { + super(props) + this.onKeyDown = this.onKeyDown.bind(this) + } + render() { - return <ValueEditor ref="input" {...this.props} onKeyDown={this.onKeyDown} inline/> + let { onTab, ...props } = this.props + return <ValueEditor + {...props} + onKeyDown={this.onKeyDown} + /> } focus() { @@ -21,6 +30,7 @@ class HeaderEditor extends Component { this.props.onRemove(e) } break + case Key.ENTER: case Key.TAB: if (!e.shiftKey) { this.props.onTab(e) @@ -66,7 +76,12 @@ export default class Headers extends Component { onTab(row, col, e) { const headers = this.props.message.headers - if (row !== headers.length - 1 || col !== 1) { + if (col === 0) { + this._nextSel = `${row}-value` + return + } + if (row !== headers.length - 1) { + this._nextSel = `${row + 1}-key` return } @@ -96,33 +111,35 @@ export default class Headers extends Component { } render() { - const { message } = this.props + const { message, readonly } = this.props return ( <table className="header-table"> <tbody> - {message.headers.map((header, i) => ( - <tr key={i}> - <td className="header-name"> - <HeaderEditor - ref={`${i}-key`} - content={header[0]} - onDone={val => this.onChange(i, 0, val)} - onRemove={event => this.onRemove(i, 0, event)} - onTab={event => this.onTab(i, 0, event)} - />: - </td> - <td className="header-value"> - <HeaderEditor - ref={`${i}-value`} - content={header[1]} - onDone={val => this.onChange(i, 1, val)} - onRemove={event => this.onRemove(i, 1, event)} - onTab={event => this.onTab(i, 1, event)} - /> - </td> - </tr> - ))} + {message.headers.map((header, i) => ( + <tr key={i}> + <td className="header-name"> + <HeaderEditor + ref={`${i}-key`} + content={header[0]} + readonly={readonly} + onDone={val => this.onChange(i, 0, val)} + onRemove={event => this.onRemove(i, 0, event)} + onTab={event => this.onTab(i, 0, event)} + />: + </td> + <td className="header-value"> + <HeaderEditor + ref={`${i}-value`} + content={header[1]} + readonly={readonly} + onDone={val => this.onChange(i, 1, val)} + onRemove={event => this.onRemove(i, 1, event)} + onTab={event => this.onTab(i, 1, event)} + /> + </td> + </tr> + ))} </tbody> </table> ) diff --git a/web/src/js/components/FlowView/Messages.jsx b/web/src/js/components/FlowView/Messages.jsx index 2f03c712..133b2883 100644 --- a/web/src/js/components/FlowView/Messages.jsx +++ b/web/src/js/components/FlowView/Messages.jsx @@ -1,172 +1,180 @@ import React, { Component, PropTypes } from 'react' -import _ from 'lodash' - -import { RequestUtils, isValidHttpVersion, parseUrl, parseHttpVersion } from '../../flow/utils.js' -import { Key, formatTimeStamp } from '../../utils.js' -import ContentView from '../ContentView' -import ValueEditor from '../ValueEditor' -import Headers from './Headers' -import * as flowActions from '../../ducks/flows' -import FlowEditorButton from './FlowEditorButton' +import { connect } from 'react-redux' -class RequestLine extends Component { +import { RequestUtils, isValidHttpVersion, parseUrl } from '../../flow/utils.js' +import { formatTimeStamp } from '../../utils.js' +import ContentView from '../ContentView' +import ValidateEditor from '../ValueEditor/ValidateEditor' +import ValueEditor from '../ValueEditor/ValueEditor' - render() { - const { flow, updateFlow } = this.props +import Headers from './Headers' +import { startEdit, updateEdit } from '../../ducks/ui/flow' +import ToggleEdit from './ToggleEdit' - return ( - <div className="first-line request-line"> +function RequestLine({ flow, readonly, updateFlow }) { + return ( + <div className="first-line request-line"> + <div> <ValueEditor - ref="method" content={flow.request.method} + readonly={readonly} onDone={method => updateFlow({ request: { method } })} - inline /> - <ValueEditor - ref="url" + <ValidateEditor content={RequestUtils.pretty_url(flow.request)} - onDone={url => updateFlow({ request: Object.assign({ path: '' }, parseUrl(url)) })} + readonly={readonly} + onDone={url => updateFlow({ request: {path: '', ...parseUrl(url)}})} isValid={url => !!parseUrl(url).host} - inline /> - <ValueEditor - ref="httpVersion" + <ValidateEditor content={flow.request.http_version} - onDone={ver => updateFlow({ request: { http_version: parseHttpVersion(ver) } })} + readonly={readonly} + onDone={http_version => updateFlow({ request: { http_version } })} isValid={isValidHttpVersion} - inline /> </div> - ) - } + </div> + ) } -class ResponseLine extends Component { - - render() { - const { flow, updateFlow } = this.props +function ResponseLine({ flow, readonly, updateFlow }) { + return ( + <div className="first-line response-line"> + <ValidateEditor + content={flow.response.http_version} + readonly={readonly} + onDone={nextVer => updateFlow({ response: { http_version: nextVer } })} + isValid={isValidHttpVersion} + /> + + <ValidateEditor + content={flow.response.status_code + ''} + readonly={readonly} + onDone={code => updateFlow({ response: { code: parseInt(code) } })} + isValid={code => /^\d+$/.test(code)} + /> + + <ValueEditor + content={flow.response.reason} + readonly={readonly} + onDone={msg => updateFlow({ response: { msg } })} + /> + </div> + ) +} - return ( - <div className="first-line response-line"> - <ValueEditor - ref="httpVersion" - content={flow.response.http_version} - onDone={nextVer => updateFlow({ response: { http_version: parseHttpVersion(nextVer) } })} - isValid={isValidHttpVersion} - inline - /> - - <ValueEditor - ref="code" - content={flow.response.status_code + ''} - onDone={code => updateFlow({ response: { code: parseInt(code) } })} - isValid={code => /^\d+$/.test(code)} - inline - /> - - <ValueEditor - ref="msg" - content={flow.response.reason} - onDone={msg => updateFlow({ response: { msg } })} - inline - /> - </div> - ) +const Message = connect( + state => ({ + flow: state.ui.flow.modifiedFlow || state.flows.byId[state.flows.selected[0]], + isEdit: !!state.ui.flow.modifiedFlow, + }), + { + updateFlow: updateEdit, } -} +) export class Request extends Component { - render() { - const { flow, updateFlow } = this.props - let onContentChange = content => flowActions.updateContent(this.props.flow, content, "request") + render() { + const { flow, isEdit, updateFlow } = this.props return ( <section className="request"> - <FlowEditorButton onContentChange={onContentChange}/> - <RequestLine ref="requestLine" flow={flow} updateFlow={updateFlow} /> + <ToggleEdit/> + <RequestLine + flow={flow} + readonly={!isEdit} + updateFlow={updateFlow}/> <Headers - ref="headers" message={flow.request} + readonly={!isEdit} onChange={headers => updateFlow({ request: { headers } })} /> <hr/> - <ContentView flow={flow} - onContentChange={onContentChange} - message={flow.request} - /> + <ContentView flow={flow} message={flow.request}/> </section> ) } + edit(k) { - switch (k) { - case 'm': - this.refs.requestLine.refs.method.focus() - break - case 'u': - this.refs.requestLine.refs.url.focus() - break - case 'v': - this.refs.requestLine.refs.httpVersion.focus() - break - case 'h': - this.refs.headers.edit() - break - default: - throw new Error(`Unimplemented: ${k}`) - } + throw "unimplemented" + /* + switch (k) { + case 'm': + this.refs.requestLine.refs.method.focus() + break + case 'u': + this.refs.requestLine.refs.url.focus() + break + case 'v': + this.refs.requestLine.refs.httpVersion.focus() + break + case 'h': + this.refs.headers.edit() + break + default: + throw new Error(`Unimplemented: ${k}`) + } + */ } + } -export class Response extends Component { +Request = Message(Request) +export class Response extends Component { render() { - const { flow, updateFlow } = this.props - let onContentChange = content => flowActions.updateContent(this.props.flow, content, "response") + const { flow, isEdit, updateFlow } = this.props return ( <section className="response"> - <FlowEditorButton onContentChange={onContentChange}/> - <ResponseLine ref="responseLine" flow={flow} updateFlow={updateFlow} /> + <ToggleEdit/> + <ResponseLine + flow={flow} + readonly={!isEdit} + updateFlow={updateFlow}/> <Headers - ref="headers" message={flow.response} + readonly={!isEdit} onChange={headers => updateFlow({ response: { headers } })} /> <hr/> - <ContentView flow={flow} - onContentChange={onContentChange} - message={flow.response} - /> + <ContentView flow={flow} message={flow.response}/> </section> ) } edit(k) { - switch (k) { - case 'c': - this.refs.responseLine.refs.status_code.focus() - break - case 'm': - this.refs.responseLine.refs.msg.focus() - break - case 'v': - this.refs.responseLine.refs.httpVersion.focus() - break - case 'h': - this.refs.headers.edit() - break - default: - throw new Error(`'Unimplemented: ${k}`) - } + throw "unimplemented" + /* + switch (k) { + case 'c': + this.refs.responseLine.refs.status_code.focus() + break + case 'm': + this.refs.responseLine.refs.msg.focus() + break + case 'v': + this.refs.responseLine.refs.httpVersion.focus() + break + case 'h': + this.refs.headers.edit() + break + default: + throw new Error(`'Unimplemented: ${k}`) + } + */ } } +Response = Message(Response) + + ErrorView.propTypes = { flow: PropTypes.object.isRequired, } diff --git a/web/src/js/components/FlowView/ToggleEdit.jsx b/web/src/js/components/FlowView/ToggleEdit.jsx new file mode 100644 index 00000000..0c8cbbd8 --- /dev/null +++ b/web/src/js/components/FlowView/ToggleEdit.jsx @@ -0,0 +1,38 @@ +import React, { PropTypes, Component } from 'react' +import { connect } from 'react-redux' + +import { startEdit, stopEdit } from '../../ducks/ui/flow' + +ToggleEdit.propTypes = { + isEdit: PropTypes.bool.isRequired, + flow: PropTypes.object.isRequired, + startEdit: PropTypes.func.isRequired, + stopEdit: PropTypes.func.isRequired, +} + +function ToggleEdit({ isEdit, startEdit, stopEdit, flow }) { + return ( + <div className="edit-flow-container"> + {isEdit ? + <a className="edit-flow" onClick={() => stopEdit(flow)}> + <i className="fa fa-check"/> + </a> + : + <a className="edit-flow" onClick={() => startEdit(flow)}> + <i className="fa fa-pencil"/> + </a> + } + </div> + ) +} + +export default connect( + state => ({ + isEdit: !!state.ui.flow.modifiedFlow, + flow: state.ui.flow.modifiedFlow || state.flows.byId[state.flows.selected[0]] + }), + { + startEdit, + stopEdit, + } +)(ToggleEdit) diff --git a/web/src/js/components/Footer.jsx b/web/src/js/components/Footer.jsx index 82d6d8a1..2bda70e1 100644 --- a/web/src/js/components/Footer.jsx +++ b/web/src/js/components/Footer.jsx @@ -48,6 +48,6 @@ function Footer({ settings }) { export default connect( state => ({ - settings: state.settings.settings, + settings: state.settings, }) )(Footer) diff --git a/web/src/js/components/Header.jsx b/web/src/js/components/Header.jsx index 5de885ae..702786e6 100644 --- a/web/src/js/components/Header.jsx +++ b/web/src/js/components/Header.jsx @@ -6,7 +6,7 @@ import ViewMenu from './Header/ViewMenu' import OptionMenu from './Header/OptionMenu' import FileMenu from './Header/FileMenu' import FlowMenu from './Header/FlowMenu' -import {setActiveMenu} from '../ducks/ui.js' +import {setActiveMenu} from '../ducks/ui/header' class Header extends Component { static entries = [MainMenu, ViewMenu, OptionMenu] @@ -17,7 +17,7 @@ class Header extends Component { } render() { - const { query, selectedFlowId, activeMenu} = this.props + const { selectedFlowId, activeMenu} = this.props let entries = [...Header.entries] if(selectedFlowId) @@ -39,10 +39,7 @@ class Header extends Component { ))} </nav> <div className="menu"> - <Active - ref="active" - query={query} - /> + <Active/> </div> </header> ) @@ -52,13 +49,9 @@ class Header extends Component { export default connect( state => ({ selectedFlowId: state.flows.selected[0], - activeMenu: state.ui.activeMenu, + activeMenu: state.ui.header.activeMenu, }), { setActiveMenu, - }, - null, - { - withRef: true, } )(Header) diff --git a/web/src/js/components/Header/MainMenu.jsx b/web/src/js/components/Header/MainMenu.jsx index 27a4be60..7236d31f 100644 --- a/web/src/js/components/Header/MainMenu.jsx +++ b/web/src/js/components/Header/MainMenu.jsx @@ -1,92 +1,50 @@ import React, { Component, PropTypes } from 'react' import { connect } from 'react-redux' import FilterInput from './FilterInput' -import { Query } from '../../actions.js' import { update as updateSettings } from '../../ducks/settings' -import { updateQuery, setSelectedInput } from '../../ducks/ui' +import { updateFilter, updateHighlight } from '../../ducks/flowView' -class MainMenu extends Component { +MainMenu.title = "Start" - static title = 'Start' - static route = 'flows' - - static propTypes = { - query: PropTypes.object.isRequired, - settings: PropTypes.object.isRequired, - updateSettings: PropTypes.func.isRequired, - updateQuery: PropTypes.func.isRequired, - } - - constructor(props, context) { - super(props, context) - this.onSearchChange = this.onSearchChange.bind(this) - this.onHighlightChange = this.onHighlightChange.bind(this) - } - - componentWillReceiveProps(nextProps) { - if(this.refs[nextProps.selectedInput]) { - this.refs[nextProps.selectedInput].select() - } - this.props.setSelectedInput(undefined) - } - - onSearchChange(val) { - this.props.updateQuery({ [Query.SEARCH]: val }) - } - - onHighlightChange(val) { - this.props.updateQuery({ [Query.HIGHLIGHT]: val }) - } - - render() { - const { query, settings, updateSettings } = this.props - - return ( - <div> - <div className="menu-row"> - <FilterInput - ref="search" - placeholder="Search" - type="search" - color="black" - value={query[Query.SEARCH] || ''} - onChange={this.onSearchChange} - /> - <FilterInput - ref="highlight" - placeholder="Highlight" - type="tag" - color="hsl(48, 100%, 50%)" - value={query[Query.HIGHLIGHT] || ''} - onChange={this.onHighlightChange} - /> - <FilterInput - ref="intercept" - placeholder="Intercept" - type="pause" - color="hsl(208, 56%, 53%)" - value={settings.intercept || ''} - onChange={intercept => updateSettings({ intercept })} - /> - </div> - <div className="clearfix"></div> +export default function MainMenu() { + return ( + <div> + <div className="menu-row"> + <FlowFilterInput/> + <HighlightInput/> + <InterceptInput/> </div> - ) - } + <div className="clearfix"></div> + </div> + ) } -export default connect( +const InterceptInput = connect( + state => ({ + value: state.settings.intercept || '', + placeholder: 'Intercept', + type: 'pause', + color: 'hsl(208, 56%, 53%)' + }), + { onChange: intercept => updateSettings({ intercept }) } +)(FilterInput); + +const FlowFilterInput = connect( + state => ({ + value: state.flowView.filter || '', + placeholder: 'Search', + type: 'search', + color: 'black' + }), + { onChange: updateFilter } +)(FilterInput); + +const HighlightInput = connect( state => ({ - settings: state.settings.settings, - selectedInput: state.ui.selectedInput + value: state.flowView.highlight || '', + placeholder: 'Highlight', + type: 'tag', + color: 'hsl(48, 100%, 50%)' }), - { - updateSettings, - updateQuery, - setSelectedInput - }, - null, - { - withRef: true, - } -)(MainMenu); + { onChange: updateHighlight } +)(FilterInput); diff --git a/web/src/js/components/Header/OptionMenu.jsx b/web/src/js/components/Header/OptionMenu.jsx index 4a487cc9..a338fed0 100644 --- a/web/src/js/components/Header/OptionMenu.jsx +++ b/web/src/js/components/Header/OptionMenu.jsx @@ -8,53 +8,52 @@ OptionMenu.title = 'Options' OptionMenu.propTypes = { settings: PropTypes.object.isRequired, - onSettingsChange: PropTypes.func.isRequired, + updateSettings: PropTypes.func.isRequired, } -function OptionMenu({ settings, onSettingsChange }) { - // @todo use settings.map +function OptionMenu({ settings, updateSettings }) { return ( <div> <div className="menu-row"> <ToggleButton text="showhost" checked={settings.showhost} - onToggle={() => onSettingsChange({ showhost: !settings.showhost })} + onToggle={() => updateSettings({ showhost: !settings.showhost })} /> <ToggleButton text="no_upstream_cert" checked={settings.no_upstream_cert} - onToggle={() => onSettingsChange({ no_upstream_cert: !settings.no_upstream_cert })} + onToggle={() => updateSettings({ no_upstream_cert: !settings.no_upstream_cert })} /> <ToggleButton text="rawtcp" checked={settings.rawtcp} - onToggle={() => onSettingsChange({ rawtcp: !settings.rawtcp })} + onToggle={() => updateSettings({ rawtcp: !settings.rawtcp })} /> <ToggleButton text="http2" checked={settings.http2} - onToggle={() => onSettingsChange({ http2: !settings.http2 })} + onToggle={() => updateSettings({ http2: !settings.http2 })} /> <ToggleButton text="anticache" checked={settings.anticache} - onToggle={() => onSettingsChange({ anticache: !settings.anticache })} + onToggle={() => updateSettings({ anticache: !settings.anticache })} /> <ToggleButton text="anticomp" checked={settings.anticomp} - onToggle={() => onSettingsChange({ anticomp: !settings.anticomp })} + onToggle={() => updateSettings({ anticomp: !settings.anticomp })} /> <ToggleInputButton name="stickyauth" placeholder="Sticky auth filter" checked={!!settings.stickyauth} txt={settings.stickyauth || ''} - onToggleChanged={txt => onSettingsChange({ stickyauth: !settings.stickyauth ? txt : null })} + onToggleChanged={txt => updateSettings({ stickyauth: !settings.stickyauth ? txt : null })} /> <ToggleInputButton name="stickycookie" placeholder="Sticky cookie filter" checked={!!settings.stickycookie} txt={settings.stickycookie || ''} - onToggleChanged={txt => onSettingsChange({ stickycookie: !settings.stickycookie ? txt : null })} + onToggleChanged={txt => updateSettings({ stickycookie: !settings.stickycookie ? txt : null })} /> <ToggleInputButton name="stream" placeholder="stream..." checked={!!settings.stream} txt={settings.stream || ''} inputType="number" - onToggleChanged={txt => onSettingsChange({ stream: !settings.stream ? txt : null })} + onToggleChanged={txt => updateSettings({ stream: !settings.stream ? txt : null })} /> </div> <div className="clearfix"/> @@ -64,9 +63,9 @@ function OptionMenu({ settings, onSettingsChange }) { export default connect( state => ({ - settings: state.settings.settings, + settings: state.settings, }), { - onSettingsChange: updateSettings, + updateSettings, } )(OptionMenu) diff --git a/web/src/js/components/MainView.jsx b/web/src/js/components/MainView.jsx index b0bbf70e..d7d1ebeb 100644 --- a/web/src/js/components/MainView.jsx +++ b/web/src/js/components/MainView.jsx @@ -1,6 +1,5 @@ import React, { Component, PropTypes } from 'react' import { connect } from 'react-redux' -import { Query } from '../actions.js' import Splitter from './common/Splitter' import FlowTable from './FlowTable' import FlowView from './FlowView' @@ -14,19 +13,6 @@ class MainView extends Component { sort: PropTypes.object, } - /** - * @todo move to actions - * @todo replace with mapStateToProps - */ - componentWillReceiveProps(nextProps) { - if (nextProps.location.query[Query.SEARCH] !== nextProps.filter) { - this.props.updateFilter(nextProps.location.query[Query.SEARCH], false) - } - if (nextProps.location.query[Query.HIGHLIGHT] !== nextProps.highlight) { - this.props.updateHighlight(nextProps.location.query[Query.HIGHLIGHT], false) - } - } - render() { const { flows, selectedFlow, highlight } = this.props return ( @@ -66,9 +52,5 @@ export default connect( updateFilter, updateHighlight, updateFlow: flowsActions.update, - }, - undefined, - { - withRef: true } )(MainView) diff --git a/web/src/js/components/ProxyApp.jsx b/web/src/js/components/ProxyApp.jsx index 2962fc2b..f8a6e262 100644 --- a/web/src/js/components/ProxyApp.jsx +++ b/web/src/js/components/ProxyApp.jsx @@ -1,14 +1,11 @@ import React, { Component, PropTypes } from 'react' -import ReactDOM from 'react-dom' -import _ from 'lodash' import { connect } from 'react-redux' import { init as appInit, destruct as appDestruct } from '../ducks/app' -import { onKeyDown } from '../ducks/ui' +import { onKeyDown } from '../ducks/ui/keyboard' import Header from './Header' import EventLog from './EventLog' import Footer from './Footer' -import { Key } from '../utils.js' class ProxyAppMain extends Component { @@ -27,6 +24,15 @@ class ProxyAppMain extends Component { } componentWillReceiveProps(nextProps) { + /* + FIXME: improve react-router -> redux integration. + if (nextProps.location.query[Query.SEARCH] !== nextProps.filter) { + this.props.updateFilter(nextProps.location.query[Query.SEARCH], false) + } + if (nextProps.location.query[Query.HIGHLIGHT] !== nextProps.highlight) { + this.props.updateHighlight(nextProps.location.query[Query.HIGHLIGHT], false) + } + */ if (nextProps.query === this.props.query && nextProps.selectedFlowId === this.props.selectedFlowId && nextProps.panel === this.props.panel) { return } @@ -35,13 +41,14 @@ class ProxyAppMain extends Component { } else { this.context.router.replace({ pathname: '/flows', query: nextProps.query }) } + } render() { const { showEventLog, location, children, query } = this.props return ( <div id="container" tabIndex="0"> - <Header ref="header" query={query} /> + <Header/> {React.cloneElement( children, { ref: 'view', location, query } @@ -58,8 +65,8 @@ class ProxyAppMain extends Component { export default connect( state => ({ showEventLog: state.eventLog.visible, - query: state.ui.query, - panel: state.ui.panel, + query: state.flowView.filter, + panel: state.ui.flow.tab, selectedFlowId: state.flows.selected[0] }), { diff --git a/web/src/js/components/ValueEditor.jsx b/web/src/js/components/ValueEditor.jsx deleted file mode 100755 index 5f1bf2dc..00000000 --- a/web/src/js/components/ValueEditor.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import React, { Component, PropTypes } from 'react' -import ReactDOM from 'react-dom' -import ValidateEditor from './ValueEditor/ValidateEditor' - -export default class ValueEditor extends Component { - - static propTypes = { - content: PropTypes.string.isRequired, - onDone: PropTypes.func.isRequired, - inline: PropTypes.bool, - } - - render() { - var tag = this.props.inline ? 'span' : 'div' - return ( - <ValidateEditor - {...this.props} - tag={tag} - /> - ) - } - - focus() { - ReactDOM.findDOMNode(this).focus(); - } -} diff --git a/web/src/js/components/ValueEditor/ValidateEditor.jsx b/web/src/js/components/ValueEditor/ValidateEditor.jsx index 2f362986..7415c1b8 100755 --- a/web/src/js/components/ValueEditor/ValidateEditor.jsx +++ b/web/src/js/components/ValueEditor/ValidateEditor.jsx @@ -1,57 +1,57 @@ import React, { Component, PropTypes } from 'react' -import ReactDOM from 'react-dom' -import EditorBase from './EditorBase' +import ValueEditor from './ValueEditor' +import classnames from 'classnames' + export default class ValidateEditor extends Component { static propTypes = { content: PropTypes.string.isRequired, + readonly: PropTypes.bool, onDone: PropTypes.func.isRequired, - onInput: PropTypes.func, - isValid: PropTypes.func, className: PropTypes.string, + isValid: PropTypes.func.isRequired, } constructor(props) { super(props) - this.state = { currentContent: props.content } + this.state = { valid: props.isValid(props.content) } this.onInput = this.onInput.bind(this) this.onDone = this.onDone.bind(this) } componentWillReceiveProps(nextProps) { - this.setState({ currentContent: nextProps.content }) + this.setState({ valid: nextProps.isValid(nextProps.content) }) } - onInput(currentContent) { - this.setState({ currentContent }) - this.props.onInput && this.props.onInput(currentContent) + onInput(content) { + this.setState({ valid: this.props.isValid(content) }) } onDone(content) { - if (this.props.isValid && !this.props.isValid(content)) { - this.refs.editor.reset() + if (!this.props.isValid(content)) { + this.editor.reset() content = this.props.content } this.props.onDone(content) } render() { - let className = this.props.className || '' - if (this.props.isValid) { - if (this.props.isValid(this.state.currentContent)) { - className += ' has-success' - } else { - className += ' has-warning' + let className = classnames( + this.props.className, + { + 'has-success': this.state.valid, + 'has-warning': !this.state.valid } - } + ) return ( - <EditorBase - {...this.props} - ref="editor" - className={className} + <ValueEditor + content={this.props.content} + readonly={this.props.readonly} onDone={this.onDone} onInput={this.onInput} + className={className} + ref={(e) => this.editor = e} /> ) } diff --git a/web/src/js/components/ValueEditor/EditorBase.jsx b/web/src/js/components/ValueEditor/ValueEditor.jsx index aa09dad5..dd9c2cde 100755..100644 --- a/web/src/js/components/ValueEditor/EditorBase.jsx +++ b/web/src/js/components/ValueEditor/ValueEditor.jsx @@ -1,61 +1,66 @@ import React, { Component, PropTypes } from 'react' -import ReactDOM from 'react-dom' -import {Key} from '../../utils.js' +import _ from "lodash" +import classnames from 'classnames' -export default class EditorBase extends Component { +import { Key } from '../../utils' + +export default class ValueEditor extends Component { static propTypes = { content: PropTypes.string.isRequired, + readonly: PropTypes.bool, onDone: PropTypes.func.isRequired, - contentToHtml: PropTypes.func, - nodeToContent: PropTypes.func, - onStop: PropTypes.func, - submitOnEnter: PropTypes.bool, className: PropTypes.string, - tag: PropTypes.string, + onInput: PropTypes.func, + onKeyDown: PropTypes.func, } static defaultProps = { - contentToHtml: content => _.escape(content), - nodeToContent: node => node.textContent, - submitOnEnter: true, - className: '', - tag: 'div', - onStop: _.noop, - onMouseDown: _.noop, - onBlur: _.noop, - onInput: _.noop, + onInput: () => {}, + onKeyDown: () => {}, } constructor(props) { super(props) - this.state = {editable: false} + this.state = { editable: false } this.onPaste = this.onPaste.bind(this) this.onMouseDown = this.onMouseDown.bind(this) this.onMouseUp = this.onMouseUp.bind(this) this.onFocus = this.onFocus.bind(this) this.onClick = this.onClick.bind(this) - this.stop = this.stop.bind(this) + this.blur = this.blur.bind(this) this.onBlur = this.onBlur.bind(this) this.reset = this.reset.bind(this) this.onKeyDown = this.onKeyDown.bind(this) this.onInput = this.onInput.bind(this) } - stop() { + blur() { // a stop would cause a blur as a side-effect. // but a blur event must trigger a stop as well. // to fix this, make stop = blur and do the actual stop in the onBlur handler. - ReactDOM.findDOMNode(this).blur() - this.props.onStop() + this.input.blur() + } + + reset() { + this.input.innerHTML = _.escape(this.props.content) } render() { + let className = classnames( + 'inline-input', + { + 'readonly': this.props.readonly, + 'editable': !this.props.readonly + }, + this.props.className + ) return ( - <this.props.tag - tabIndex="0" - className={`inline-input ${this.props.className}`} + <div + ref={input => this.input = input} + tabIndex={!this.props.readonly && "0"} + className={className} contentEditable={this.state.editable || undefined} onFocus={this.onFocus} onMouseDown={this.onMouseDown} @@ -64,8 +69,8 @@ export default class EditorBase extends Component { onKeyDown={this.onKeyDown} onInput={this.onInput} onPaste={this.onPaste} - dangerouslySetInnerHTML={{ __html: this.props.contentToHtml(this.props.content) }} - /> + dangerouslySetInnerHTML={{ __html: _.escape(this.props.content) }} + ></div> ) } @@ -78,7 +83,6 @@ export default class EditorBase extends Component { onMouseDown(e) { this._mouseDown = true window.addEventListener('mouseup', this.onMouseUp) - this.props.onMouseDown(e) } onMouseUp() { @@ -94,7 +98,7 @@ export default class EditorBase extends Component { } onFocus(e) { - if (this._mouseDown || this._ignore_events || this.state.editable) { + if (this._mouseDown || this._ignore_events || this.state.editable || this.props.readonly) { return } @@ -114,31 +118,29 @@ export default class EditorBase extends Component { range = document.caretRangeFromPoint(e.clientX, e.clientY) } else { range = document.createRange() - range.selectNodeContents(ReactDOM.findDOMNode(this)) + range.selectNodeContents(this.input) } this._ignore_events = true this.setState({ editable: true }, () => { - const node = ReactDOM.findDOMNode(this) - node.blur() - node.focus() + this.input.blur() + this.input.focus() this._ignore_events = false + range.selectNodeContents(this.input) + sel.removeAllRanges(); + sel.addRange(range); }) } onBlur(e) { - if (this._ignore_events) { + if (this._ignore_events || this.props.readonly) { return } window.getSelection().removeAllRanges() //make sure that selection is cleared on blur this.setState({ editable: false }) - this.props.onDone(this.props.nodeToContent(ReactDOM.findDOMNode(this))) - this.props.onBlur(e) + this.props.onDone(this.input.textContent) } - reset() { - ReactDOM.findDOMNode(this).innerHTML = this.props.contentToHtml(this.props.content) - } onKeyDown(e) { e.stopPropagation() @@ -146,20 +148,21 @@ export default class EditorBase extends Component { case Key.ESC: e.preventDefault() this.reset() - this.stop() + this.blur() break case Key.ENTER: - if (this.props.submitOnEnter && !e.shiftKey) { + if (!e.shiftKey) { e.preventDefault() - this.stop() + this.blur() } break default: break } + this.props.onKeyDown(e) } onInput() { - this.props.onInput(this.props.nodeToContent(ReactDOM.findDOMNode(this))) + this.props.onInput(this.input.textContent) } } diff --git a/web/src/js/ducks/flows.js b/web/src/js/ducks/flows.js index ffb7ac87..f18e48e6 100644 --- a/web/src/js/ducks/flows.js +++ b/web/src/js/ducks/flows.js @@ -67,56 +67,49 @@ export default function reduce(state = defaultState, action) { * @public */ export function accept(flow) { - fetchApi(`/flows/${flow.id}/accept`, { method: 'POST' }) - return { type: REQUEST_ACTION } + return dispatch => fetchApi(`/flows/${flow.id}/accept`, { method: 'POST' }) } /** * @public */ export function acceptAll() { - fetchApi('/flows/accept', { method: 'POST' }) - return { type: REQUEST_ACTION } + return dispatch => fetchApi('/flows/accept', { method: 'POST' }) } /** * @public */ export function remove(flow) { - fetchApi(`/flows/${flow.id}`, { method: 'DELETE' }) - return { type: REQUEST_ACTION } + return dispatch => fetchApi(`/flows/${flow.id}`, { method: 'DELETE' }) } /** * @public */ export function duplicate(flow) { - fetchApi(`/flows/${flow.id}/duplicate`, { method: 'POST' }) - return { type: REQUEST_ACTION } + return dispatch => fetchApi(`/flows/${flow.id}/duplicate`, { method: 'POST' }) } /** * @public */ export function replay(flow) { - fetchApi(`/flows/${flow.id}/replay`, { method: 'POST' }) - return { type: REQUEST_ACTION } + return dispatch => fetchApi(`/flows/${flow.id}/replay`, { method: 'POST' }) } /** * @public */ export function revert(flow) { - fetchApi(`/flows/${flow.id}/revert`, { method: 'POST' }) - return { type: REQUEST_ACTION } + return dispatch => fetchApi(`/flows/${flow.id}/revert`, { method: 'POST' }) } /** * @public */ export function update(flow, data) { - fetchApi.put(`/flows/${flow.id}`, data) - return { type: REQUEST_ACTION } + return dispatch => fetchApi.put(`/flows/${flow.id}`, data) } export function updateContent(flow, file, type) { @@ -124,8 +117,7 @@ export function updateContent(flow, file, type) { if (typeof file !== File) file = new Blob([file], {type: 'plain/text'}) body.append('file', file) - fetchApi(`/flows/${flow.id}/${type}/content`, {method: 'post', body} ) - return { type: REQUEST_ACTION } + return dispatch => fetchApi(`/flows/${flow.id}/${type}/content`, {method: 'post', body} ) } @@ -133,8 +125,7 @@ export function updateContent(flow, file, type) { * @public */ export function clear() { - fetchApi('/clear', { method: 'POST' }) - return { type: REQUEST_ACTION } + return dispatch => fetchApi('/clear', { method: 'POST' }) } /** @@ -151,8 +142,7 @@ export function download() { export function upload(file) { const body = new FormData() body.append('file', file) - fetchApi('/flows/dump', { method: 'post', body }) - return { type: REQUEST_ACTION } + return dispatch => fetchApi('/flows/dump', { method: 'post', body }) } diff --git a/web/src/js/ducks/index.js b/web/src/js/ducks/index.js index 16193530..b90b24ff 100644 --- a/web/src/js/ducks/index.js +++ b/web/src/js/ducks/index.js @@ -4,7 +4,7 @@ import websocket from './websocket' import flows from './flows' import flowView from './flowView' import settings from './settings' -import ui from './ui' +import ui from './ui/index' import msgQueue from './msgQueue' export default combineReducers({ diff --git a/web/src/js/ducks/settings.js b/web/src/js/ducks/settings.js index 7ad97b87..6b21baec 100644 --- a/web/src/js/ducks/settings.js +++ b/web/src/js/ducks/settings.js @@ -11,22 +11,19 @@ export const REQUEST_UPDATE = 'REQUEST_UPDATE' export const UNKNOWN_CMD = 'SETTINGS_UNKNOWN_CMD' const defaultState = { - settings: {}, + } export default function reducer(state = defaultState, action) { switch (action.type) { case RECEIVE: - return { - ...state, - settings: action.settings, - } + return action.settings case UPDATE: return { ...state, - settings: { ...state.settings, ...action.settings }, + ...action.settings, } default: diff --git a/web/src/js/ducks/ui.js b/web/src/js/ducks/ui.js deleted file mode 100644 index ccd17eb6..00000000 --- a/web/src/js/ducks/ui.js +++ /dev/null @@ -1,292 +0,0 @@ -import { selectRelative as selectFlowRelative } from './flowView' -import { Key } from '../utils.js' -import * as flowsActions from './flows' - -export const SET_ACTIVE_MENU = 'UI_SET_ACTIVE_MENU' -export const SET_CONTENT_VIEW = 'UI_SET_CONTENT_VIEW' -export const SET_SELECTED_INPUT = 'UI_SET_SELECTED_INPUT' -export const UPDATE_QUERY = 'UI_UPDATE_QUERY' -export const SELECT_TAB = 'UI_SELECT_TAB' -export const SET_PROMPT = 'UI_SET_PROMPT' -export const SET_DISPLAY_LARGE = 'UI_SET_DISPLAY_LARGE' -export const OPEN_FLOW_EDITOR= 'UI_OPEN_FLOW_EDITOR' -export const CLOSE_FLOW_EDITOR = 'UI_CLOSE_FLOW_EDITOR' -export const SET_MODIFIED_FLOW_CONTENT = 'UI_SET_MODIFIED_FLOW' - -const defaultState = { - activeMenu: 'Start', - isFlowSelected: false, - selectedInput: null, - displayLarge: false, - promptOpen: false, - contentView: 'ViewAuto', - query: {}, - panel: 'request', - modifiedFlow: {headers: "", content: ""}, - isFlowEditorOpen: false -} - -export default function reducer(state = defaultState, action) { - switch (action.type) { - - case SET_ACTIVE_MENU: - return { - ...state, - activeMenu: action.activeMenu, - } - - case flowsActions.SELECT: - let s = {...state, isFlowEditorOpen: false} - if (action.flowIds.length && !state.isFlowSelected) { - return { - ...s, - displayLarge: false, - activeMenu: 'Flow', - isFlowSelected: true, - } - } - - if (!action.flowIds.length && state.isFlowSelected) { - let activeMenu = state.activeMenu - if (activeMenu == 'Flow') { - activeMenu = 'Start' - } - return { - ...s, - activeMenu, - isFlowSelected: false, - } - } - - return { - ...s, - displayLarge: false, - } - - case SET_CONTENT_VIEW: - return { - ...state, - contentView: action.contentView, - } - - case SET_SELECTED_INPUT: - return { - ...state, - selectedInput: action.input - } - - case UPDATE_QUERY: - return { - ...state, - query: { ...state.query, ...action.query } - } - - case SELECT_TAB: - return { - ...state, - isFlowEditorOpen: false, - panel: action.panel - } - - case SET_PROMPT: - return { - ...state, - promptOpen: action.open, - } - - case SET_DISPLAY_LARGE: - return { - ...state, - displayLarge: action.displayLarge, - } - case OPEN_FLOW_EDITOR: - return { - ...state, - isFlowEditorOpen: true - } - case CLOSE_FLOW_EDITOR: - return { - ...state, - isFlowEditorOpen: false - } - case SET_MODIFIED_FLOW_CONTENT: - return{ - ...state, - modifiedFlow: {...state.modifiedFlow, content: action.content} - } - default: - return state - } -} - -export function setActiveMenu(activeMenu) { - return { type: SET_ACTIVE_MENU, activeMenu } -} - -export function setContentView(contentView) { - return { type: SET_CONTENT_VIEW, contentView } -} - -export function setSelectedInput(input) { - return { type: SET_SELECTED_INPUT, input } -} - -export function updateQuery(query) { - return { type: UPDATE_QUERY, query } -} - -export function selectTab(panel) { - return { type: SELECT_TAB, panel } -} - -export function setPrompt(open) { - return { type: SET_PROMPT, open } -} - -export function setDisplayLarge(displayLarge) { - return { type: SET_DISPLAY_LARGE, displayLarge } -} - -export function openFlowEditor(){ - return { type: OPEN_FLOW_EDITOR } -} - -export function closeFlowEditor(){ - return { type: CLOSE_FLOW_EDITOR } -} - -export function setModifiedFlowContent(content) { - return { type: SET_MODIFIED_FLOW_CONTENT, content } -} - -export function onKeyDown(e) { - if (e.ctrlKey) { - return () => { - } - } - var key = e.keyCode - var shiftKey = e.shiftKey - e.preventDefault() - return (dispatch, getState) => { - - const flow = getState().flows.byId[getState().flows.selected[0]] - - switch (key) { - - case Key.I: - dispatch(setSelectedInput('intercept')) - break - - case Key.L: - dispatch(setSelectedInput('search')) - break - - case Key.H: - dispatch(setSelectedInput('highlight')) - break - - case Key.K: - case Key.UP: - dispatch(selectFlowRelative(-1)) - break - - case Key.J: - case Key.DOWN: - dispatch(selectFlowRelative(+1)) - break - - case Key.SPACE: - case Key.PAGE_DOWN: - dispatch(selectFlowRelative(+10)) - break - - case Key.PAGE_UP: - dispatch(selectFlowRelative(-10)) - break - - case Key.END: - dispatch(selectFlowRelative(+1e10)) - break - - case Key.HOME: - dispatch(selectFlowRelative(-1e10)) - break - - case Key.ESC: - dispatch(flowsActions.select(null)) - break - - case Key.LEFT: - { - let tabs = ['request', 'response', 'error'].filter(k => flow[k]).concat(['details']), - currentTab = getState().ui.panel, - nextTab = tabs[(tabs.indexOf(currentTab) - 1 + tabs.length) % tabs.length] - dispatch(selectTab(nextTab)) - break - } - - case Key.TAB: - case Key.RIGHT: - { - let tabs = ['request', 'response', 'error'].filter(k => flow[k]).concat(['details']), - currentTab = getState().ui.panel, - nextTab = tabs[(tabs.indexOf(currentTab) + 1) % tabs.length] - dispatch(selectTab(nextTab)) - break - } - - case Key.C: - if (shiftKey) { - dispatch(flowsActions.clear()) - } - break - - case Key.D: - { - if (!flow) { - return - } - if (shiftKey) { - dispatch(flowsActions.duplicate(flow)) - } else { - dispatch(flowsActions.remove(flow)) - } - break - } - - case Key.A: - { - if (shiftKey) { - dispatch(flowsActions.acceptAll()) - } else if (flow && flow.intercepted) { - dispatch(flowsActions.accept(flow)) - } - break - } - - case Key.R: - { - if (!shiftKey && flow) { - dispatch(flowsActions.replay(flow)) - } - break - } - - case Key.V: - { - if (!shiftKey && flow && flow.modified) { - dispatch(flowsActions.revert(flow)) - } - break - } - - case Key.E: - dispatch(setPrompt(true)) - break - - default: - return () => { - } - } - } -} diff --git a/web/src/js/ducks/ui/flow.js b/web/src/js/ducks/ui/flow.js new file mode 100644 index 00000000..b1fe535f --- /dev/null +++ b/web/src/js/ducks/ui/flow.js @@ -0,0 +1,97 @@ +import * as flowsActions from '../flows' +import _ from 'lodash' + +export const SET_CONTENT_VIEW = 'UI_FLOWVIEW_SET_CONTENT_VIEW', + DISPLAY_LARGE = 'UI_FLOWVIEW_DISPLAY_LARGE', + SET_TAB = "UI_FLOWVIEW_SET_TAB", + START_EDIT = 'UI_FLOWVIEW_START_EDIT', + UPDATE_EDIT = 'UI_FLOWVIEW_UPDATE_EDIT', + STOP_EDIT = 'UI_FLOWVIEW_STOP_EDIT' + + +const defaultState = { + displayLarge: false, + modifiedFlow: false, + contentView: 'ViewAuto', + tab: 'request', +} + +export default function reducer(state = defaultState, action) { + switch (action.type) { + + case START_EDIT: + return { + ...state, + modifiedFlow: action.flow + } + + case UPDATE_EDIT: + return { + ...state, + modifiedFlow: _.merge({}, state.modifiedFlow, action.update) + } + + case STOP_EDIT: + return { + ...state, + modifiedFlow: false + } + + case flowsActions.SELECT: + return { + ...state, + modifiedFlow: false, + displayLarge: false, + } + + case SET_TAB: + return { + ...state, + tab: action.tab, + displayLarge: false, + } + + case SET_CONTENT_VIEW: + return { + ...state, + contentView: action.contentView, + } + + case DISPLAY_LARGE: + return { + ...state, + displayLarge: true, + } + default: + return state + } +} + +export function setContentView(contentView) { + return { type: SET_CONTENT_VIEW, contentView } +} + +export function displayLarge() { + return { type: DISPLAY_LARGE } +} + +export function selectTab(tab) { + return { type: SET_TAB, tab } +} + +export function startEdit(flow) { + return { type: START_EDIT, flow } +} + +export function updateEdit(update) { + return { type: UPDATE_EDIT, update } +} + +export function stopEdit(flow) { + return (dispatch) => { + dispatch(flowsActions.update(flow, flow)).then(() => { + dispatch(flowsActions.updateFlow(flow)) + dispatch({ type: STOP_EDIT }) + }) + } +} diff --git a/web/src/js/ducks/ui/header.js b/web/src/js/ducks/ui/header.js new file mode 100644 index 00000000..25dfe602 --- /dev/null +++ b/web/src/js/ducks/ui/header.js @@ -0,0 +1,50 @@ +import * as flowsActions from '../flows' + +export const SET_ACTIVE_MENU = 'UI_SET_ACTIVE_MENU' + + +const defaultState = { + activeMenu: 'Start', + isFlowSelected: false, +} + +export default function reducer(state = defaultState, action) { + switch (action.type) { + + case SET_ACTIVE_MENU: + return { + ...state, + activeMenu: action.activeMenu, + } + + case flowsActions.SELECT: + // First Select + if (action.flowIds.length && !state.isFlowSelected) { + return { + ...state, + activeMenu: 'Flow', + isFlowSelected: true, + } + } + + // Deselect + if (!action.flowIds.length && state.isFlowSelected) { + let activeMenu = state.activeMenu + if (activeMenu == 'Flow') { + activeMenu = 'Start' + } + return { + ...state, + activeMenu, + isFlowSelected: false, + } + } + return state + default: + return state + } +} + +export function setActiveMenu(activeMenu) { + return { type: SET_ACTIVE_MENU, activeMenu } +} diff --git a/web/src/js/ducks/ui/index.js b/web/src/js/ducks/ui/index.js new file mode 100644 index 00000000..f3c5f59e --- /dev/null +++ b/web/src/js/ducks/ui/index.js @@ -0,0 +1,8 @@ +import { combineReducers } from 'redux' +import flow from './flow' +import header from './header' + +export default combineReducers({ + flow, + header, +}) diff --git a/web/src/js/ducks/ui/keyboard.js b/web/src/js/ducks/ui/keyboard.js new file mode 100644 index 00000000..10c69853 --- /dev/null +++ b/web/src/js/ducks/ui/keyboard.js @@ -0,0 +1,122 @@ +import { Key } from '../../utils' +import { selectRelative as selectFlowRelative } from '../flowView' +import { selectTab } from './flow' +import * as flowsActions from '../flows' + + +export function onKeyDown(e) { + console.debug("onKeyDown", e) + if (e.ctrlKey) { + return () => { + } + } + var key = e.keyCode + var shiftKey = e.shiftKey + e.preventDefault() + return (dispatch, getState) => { + + const flow = getState().flows.byId[getState().flows.selected[0]] + + switch (key) { + case Key.K: + case Key.UP: + dispatch(selectFlowRelative(-1)) + break + + case Key.J: + case Key.DOWN: + dispatch(selectFlowRelative(+1)) + break + + case Key.SPACE: + case Key.PAGE_DOWN: + dispatch(selectFlowRelative(+10)) + break + + case Key.PAGE_UP: + dispatch(selectFlowRelative(-10)) + break + + case Key.END: + dispatch(selectFlowRelative(+1e10)) + break + + case Key.HOME: + dispatch(selectFlowRelative(-1e10)) + break + + case Key.ESC: + dispatch(flowsActions.select(null)) + break + + case Key.LEFT: + { + if(!flow) break + let tabs = ['request', 'response', 'error'].filter(k => flow[k]).concat(['details']), + currentTab = getState().ui.flow.tab, + nextTab = tabs[(tabs.indexOf(currentTab) - 1 + tabs.length) % tabs.length] + dispatch(selectTab(nextTab)) + break + } + + case Key.TAB: + case Key.RIGHT: + { + if(!flow) break + let tabs = ['request', 'response', 'error'].filter(k => flow[k]).concat(['details']), + currentTab = getState().ui.flow.tab, + nextTab = tabs[(tabs.indexOf(currentTab) + 1) % tabs.length] + dispatch(selectTab(nextTab)) + break + } + + case Key.C: + if (shiftKey) { + dispatch(flowsActions.clear()) + } + break + + case Key.D: + { + if (!flow) { + return + } + if (shiftKey) { + dispatch(flowsActions.duplicate(flow)) + } else { + dispatch(flowsActions.remove(flow)) + } + break + } + + case Key.A: + { + if (shiftKey) { + dispatch(flowsActions.acceptAll()) + } else if (flow && flow.intercepted) { + dispatch(flowsActions.accept(flow)) + } + break + } + + case Key.R: + { + if (!shiftKey && flow) { + dispatch(flowsActions.replay(flow)) + } + break + } + + case Key.V: + { + if (!shiftKey && flow && flow.modified) { + dispatch(flowsActions.revert(flow)) + } + break + } + + default: + return + } + } +} diff --git a/web/src/js/flow/utils.js b/web/src/js/flow/utils.js index 1f9f3d07..d24f984c 100644 --- a/web/src/js/flow/utils.js +++ b/web/src/js/flow/utils.js @@ -107,10 +107,3 @@ var isValidHttpVersion_regex = /^HTTP\/\d+(\.\d+)*$/i; export var isValidHttpVersion = function (httpVersion) { return isValidHttpVersion_regex.test(httpVersion); }; - -export var parseHttpVersion = function (httpVersion) { - httpVersion = httpVersion.replace("HTTP/", "").split("."); - return _.map(httpVersion, function (x) { - return parseInt(x); - }); -}; |