aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaximilian Hils <git@maximilianhils.com>2016-07-21 01:14:55 -0700
committerMaximilian Hils <git@maximilianhils.com>2016-07-21 01:14:55 -0700
commit8a3a21bba1e6706295cc22e1b3a876a7a86cb705 (patch)
tree9408324d45850fd4def75c60cf3537a12f632217
parent427fffbcb82ba16dd65a4fee4000a05215e859b8 (diff)
downloadmitmproxy-8a3a21bba1e6706295cc22e1b3a876a7a86cb705.tar.gz
mitmproxy-8a3a21bba1e6706295cc22e1b3a876a7a86cb705.tar.bz2
mitmproxy-8a3a21bba1e6706295cc22e1b3a876a7a86cb705.zip
web: fix ValueEditor, clean up code
-rw-r--r--web/src/css/codemirror.less10
-rw-r--r--web/src/css/flowdetail.less18
-rw-r--r--web/src/css/flowview.less5
-rw-r--r--web/src/js/components/ContentView.jsx14
-rw-r--r--web/src/js/components/FlowView.jsx4
-rw-r--r--web/src/js/components/FlowView/FlowEditorButton.jsx39
-rw-r--r--web/src/js/components/FlowView/Headers.jsx71
-rw-r--r--web/src/js/components/FlowView/Messages.jsx226
-rw-r--r--web/src/js/components/FlowView/ToggleEdit.jsx38
-rw-r--r--web/src/js/components/Footer.jsx2
-rw-r--r--web/src/js/components/Header.jsx15
-rw-r--r--web/src/js/components/Header/MainMenu.jsx120
-rw-r--r--web/src/js/components/Header/OptionMenu.jsx27
-rw-r--r--web/src/js/components/MainView.jsx18
-rw-r--r--web/src/js/components/ProxyApp.jsx21
-rwxr-xr-xweb/src/js/components/ValueEditor.jsx26
-rwxr-xr-xweb/src/js/components/ValueEditor/ValidateEditor.jsx44
-rw-r--r--[-rwxr-xr-x]web/src/js/components/ValueEditor/ValueEditor.jsx (renamed from web/src/js/components/ValueEditor/EditorBase.jsx)89
-rw-r--r--web/src/js/ducks/flows.js30
-rw-r--r--web/src/js/ducks/index.js2
-rw-r--r--web/src/js/ducks/settings.js9
-rw-r--r--web/src/js/ducks/ui.js292
-rw-r--r--web/src/js/ducks/ui/flow.js97
-rw-r--r--web/src/js/ducks/ui/header.js50
-rw-r--r--web/src/js/ducks/ui/index.js8
-rw-r--r--web/src/js/ducks/ui/keyboard.js122
-rw-r--r--web/src/js/flow/utils.js7
27 files changed, 657 insertions, 747 deletions
diff --git a/web/src/css/codemirror.less b/web/src/css/codemirror.less
index 6504db50..f88ea8b1 100644
--- a/web/src/css/codemirror.less
+++ b/web/src/css/codemirror.less
@@ -1,9 +1,7 @@
-.ReactCodeMirror {
- border: 1px solid #ccc;
-}
-
-.CodeMirror{
+.CodeMirror {
+ border: 1px solid #ccc;
height: auto !important;
- max-height: 1000px !important;
+ max-height: 2048px !important;
}
+
@import (inline) "../../node_modules/codemirror/lib/codemirror.css";
diff --git a/web/src/css/flowdetail.less b/web/src/css/flowdetail.less
index 43078eff..35857729 100644
--- a/web/src/css/flowdetail.less
+++ b/web/src/css/flowdetail.less
@@ -26,14 +26,14 @@
word-break: break-all;
max-height: 100px;
overflow-y: auto;
+
+ .inline-input.editable {
+ border-color: rgba(255,255,255,0.5);
+ }
}
.request-line {
margin-bottom: 2px;
}
- /*.request .response-line,
- .response .request-line {
- opacity: 0.7;
- }*/
hr {
margin: 0 0 5px;
@@ -42,8 +42,14 @@
}
.inline-input {
- margin: 0 -5px;
- padding: 0 5px;
+ display: inline;
+ margin: 0 -3px;
+ padding: 0 3px;
+
+ border: solid transparent 1px;
+ &.editable {
+ border-color: #ccc;
+ }
&[contenteditable] {
diff --git a/web/src/css/flowview.less b/web/src/css/flowview.less
index 419739a4..1827db5c 100644
--- a/web/src/css/flowview.less
+++ b/web/src/css/flowview.less
@@ -13,15 +13,16 @@
}
.edit-flow {
+ cursor: pointer;
position: absolute;
- right: 0px;
+ right: 0;
top: 5px;
height: 40px;
width: 40px;
border-radius: 20px;
z-index: 10000;
- background-color: white;
+ background-color: rgba(255, 255, 255, 0.7);
border: solid 2px rgba(248, 145, 59, 0.7);
text-align: center;
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
/>
&nbsp;
- <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
/>
&nbsp;
- <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}
+ />
+ &nbsp;
+ <ValidateEditor
+ content={flow.response.status_code + ''}
+ readonly={readonly}
+ onDone={code => updateFlow({ response: { code: parseInt(code) } })}
+ isValid={code => /^\d+$/.test(code)}
+ />
+ &nbsp;
+ <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
- />
- &nbsp;
- <ValueEditor
- ref="code"
- content={flow.response.status_code + ''}
- onDone={code => updateFlow({ response: { code: parseInt(code) } })}
- isValid={code => /^\d+$/.test(code)}
- inline
- />
- &nbsp;
- <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);
- });
-};