mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-23 00:01:36 +00:00
web: fix ValueEditor, clean up code
This commit is contained in:
parent
427fffbcb8
commit
8a3a21bba1
@ -1,9 +1,7 @@
|
|||||||
.ReactCodeMirror {
|
.CodeMirror {
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
|
height: auto !important;
|
||||||
|
max-height: 2048px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror{
|
|
||||||
height: auto !important;
|
|
||||||
max-height: 1000px !important;
|
|
||||||
}
|
|
||||||
@import (inline) "../../node_modules/codemirror/lib/codemirror.css";
|
@import (inline) "../../node_modules/codemirror/lib/codemirror.css";
|
||||||
|
@ -26,14 +26,14 @@
|
|||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
max-height: 100px;
|
max-height: 100px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.inline-input.editable {
|
||||||
|
border-color: rgba(255,255,255,0.5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.request-line {
|
.request-line {
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
/*.request .response-line,
|
|
||||||
.response .request-line {
|
|
||||||
opacity: 0.7;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
margin: 0 0 5px;
|
margin: 0 0 5px;
|
||||||
@ -42,8 +42,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.inline-input {
|
.inline-input {
|
||||||
margin: 0 -5px;
|
display: inline;
|
||||||
padding: 0 5px;
|
margin: 0 -3px;
|
||||||
|
padding: 0 3px;
|
||||||
|
|
||||||
|
border: solid transparent 1px;
|
||||||
|
&.editable {
|
||||||
|
border-color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
&[contenteditable] {
|
&[contenteditable] {
|
||||||
|
|
||||||
|
@ -13,15 +13,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.edit-flow {
|
.edit-flow {
|
||||||
|
cursor: pointer;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0px;
|
right: 0;
|
||||||
top: 5px;
|
top: 5px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
|
|
||||||
background-color: white;
|
background-color: rgba(255, 255, 255, 0.7);
|
||||||
border: solid 2px rgba(248, 145, 59, 0.7);
|
border: solid 2px rgba(248, 145, 59, 0.7);
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -5,7 +5,7 @@ import * as ContentViews from './ContentView/ContentViews'
|
|||||||
import * as MetaViews from './ContentView/MetaViews'
|
import * as MetaViews from './ContentView/MetaViews'
|
||||||
import ContentLoader from './ContentView/ContentLoader'
|
import ContentLoader from './ContentView/ContentLoader'
|
||||||
import ViewSelector from './ContentView/ViewSelector'
|
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'
|
import CodeEditor from './common/CodeEditor'
|
||||||
|
|
||||||
ContentView.propTypes = {
|
ContentView.propTypes = {
|
||||||
@ -30,7 +30,7 @@ function ContentView(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!displayLarge && ContentView.isContentTooLarge(message)) {
|
if (!displayLarge && ContentView.isContentTooLarge(message)) {
|
||||||
return <MetaViews.ContentTooLarge {...props} onClick={() => setDisplayLarge(true)}/>
|
return <MetaViews.ContentTooLarge {...props} onClick={displayLarge}/>
|
||||||
}
|
}
|
||||||
|
|
||||||
const View = ContentViews[contentView]
|
const View = ContentViews[contentView]
|
||||||
@ -80,13 +80,13 @@ function ContentView(props) {
|
|||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
state => ({
|
state => ({
|
||||||
contentView: state.ui.contentView,
|
contentView: state.ui.flow.contentView,
|
||||||
displayLarge: state.ui.displayLarge,
|
displayLarge: state.ui.flow.displayLarge,
|
||||||
isFlowEditorOpen : state.ui.isFlowEditorOpen
|
isFlowEditorOpen : !!state.ui.flow.modifiedFlow // FIXME
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
selectView: setContentView,
|
selectView: setContentView,
|
||||||
setDisplayLarge,
|
displayLarge,
|
||||||
setModifiedFlowContent
|
updateEdit,
|
||||||
}
|
}
|
||||||
)(ContentView)
|
)(ContentView)
|
||||||
|
@ -7,7 +7,7 @@ import { Request, Response, ErrorView as Error } from './FlowView/Messages'
|
|||||||
import Details from './FlowView/Details'
|
import Details from './FlowView/Details'
|
||||||
import Prompt from './Prompt'
|
import Prompt from './Prompt'
|
||||||
|
|
||||||
import { setPrompt, selectTab } from '../ducks/ui'
|
import { selectTab } from '../ducks/ui/flow'
|
||||||
|
|
||||||
export default class FlowView extends Component {
|
export default class FlowView extends Component {
|
||||||
|
|
||||||
@ -90,9 +90,9 @@ export default class FlowView extends Component {
|
|||||||
export default connect(
|
export default connect(
|
||||||
state => ({
|
state => ({
|
||||||
promptOpen: state.ui.promptOpen,
|
promptOpen: state.ui.promptOpen,
|
||||||
|
tab: state.ui.flow.tab
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
setPrompt,
|
|
||||||
selectTab,
|
selectTab,
|
||||||
}
|
}
|
||||||
)(FlowView)
|
)(FlowView)
|
||||||
|
@ -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)
|
|
@ -1,12 +1,21 @@
|
|||||||
import React, { Component, PropTypes } from 'react'
|
import React, { Component, PropTypes } from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import ValueEditor from '../ValueEditor'
|
import ValueEditor from '../ValueEditor/ValueEditor'
|
||||||
import { Key } from '../../utils.js'
|
import { Key } from '../../utils'
|
||||||
|
|
||||||
class HeaderEditor extends Component {
|
class HeaderEditor extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.onKeyDown = this.onKeyDown.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <ValueEditor ref="input" {...this.props} onKeyDown={this.onKeyDown} inline/>
|
let { onTab, ...props } = this.props
|
||||||
|
return <ValueEditor
|
||||||
|
{...props}
|
||||||
|
onKeyDown={this.onKeyDown}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
@ -21,6 +30,7 @@ class HeaderEditor extends Component {
|
|||||||
this.props.onRemove(e)
|
this.props.onRemove(e)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
case Key.ENTER:
|
||||||
case Key.TAB:
|
case Key.TAB:
|
||||||
if (!e.shiftKey) {
|
if (!e.shiftKey) {
|
||||||
this.props.onTab(e)
|
this.props.onTab(e)
|
||||||
@ -66,7 +76,12 @@ export default class Headers extends Component {
|
|||||||
onTab(row, col, e) {
|
onTab(row, col, e) {
|
||||||
const headers = this.props.message.headers
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,33 +111,35 @@ export default class Headers extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { message } = this.props
|
const { message, readonly } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<table className="header-table">
|
<table className="header-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
{message.headers.map((header, i) => (
|
{message.headers.map((header, i) => (
|
||||||
<tr key={i}>
|
<tr key={i}>
|
||||||
<td className="header-name">
|
<td className="header-name">
|
||||||
<HeaderEditor
|
<HeaderEditor
|
||||||
ref={`${i}-key`}
|
ref={`${i}-key`}
|
||||||
content={header[0]}
|
content={header[0]}
|
||||||
onDone={val => this.onChange(i, 0, val)}
|
readonly={readonly}
|
||||||
onRemove={event => this.onRemove(i, 0, event)}
|
onDone={val => this.onChange(i, 0, val)}
|
||||||
onTab={event => this.onTab(i, 0, event)}
|
onRemove={event => this.onRemove(i, 0, event)}
|
||||||
/>:
|
onTab={event => this.onTab(i, 0, event)}
|
||||||
</td>
|
/>:
|
||||||
<td className="header-value">
|
</td>
|
||||||
<HeaderEditor
|
<td className="header-value">
|
||||||
ref={`${i}-value`}
|
<HeaderEditor
|
||||||
content={header[1]}
|
ref={`${i}-value`}
|
||||||
onDone={val => this.onChange(i, 1, val)}
|
content={header[1]}
|
||||||
onRemove={event => this.onRemove(i, 1, event)}
|
readonly={readonly}
|
||||||
onTab={event => this.onTab(i, 1, event)}
|
onDone={val => this.onChange(i, 1, val)}
|
||||||
/>
|
onRemove={event => this.onRemove(i, 1, event)}
|
||||||
</td>
|
onTab={event => this.onTab(i, 1, event)}
|
||||||
</tr>
|
/>
|
||||||
))}
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
)
|
)
|
||||||
|
@ -1,172 +1,180 @@
|
|||||||
import React, { Component, PropTypes } from 'react'
|
import React, { Component, PropTypes } from 'react'
|
||||||
import _ from 'lodash'
|
import { connect } from 'react-redux'
|
||||||
|
|
||||||
import { RequestUtils, isValidHttpVersion, parseUrl, parseHttpVersion } from '../../flow/utils.js'
|
|
||||||
import { Key, formatTimeStamp } from '../../utils.js'
|
import { RequestUtils, isValidHttpVersion, parseUrl } from '../../flow/utils.js'
|
||||||
|
import { formatTimeStamp } from '../../utils.js'
|
||||||
import ContentView from '../ContentView'
|
import ContentView from '../ContentView'
|
||||||
import ValueEditor from '../ValueEditor'
|
import ValidateEditor from '../ValueEditor/ValidateEditor'
|
||||||
|
import ValueEditor from '../ValueEditor/ValueEditor'
|
||||||
|
|
||||||
import Headers from './Headers'
|
import Headers from './Headers'
|
||||||
import * as flowActions from '../../ducks/flows'
|
import { startEdit, updateEdit } from '../../ducks/ui/flow'
|
||||||
import FlowEditorButton from './FlowEditorButton'
|
import ToggleEdit from './ToggleEdit'
|
||||||
|
|
||||||
|
function RequestLine({ flow, readonly, updateFlow }) {
|
||||||
class RequestLine extends Component {
|
return (
|
||||||
|
<div className="first-line request-line">
|
||||||
render() {
|
<div>
|
||||||
const { flow, updateFlow } = this.props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="first-line request-line">
|
|
||||||
<ValueEditor
|
<ValueEditor
|
||||||
ref="method"
|
|
||||||
content={flow.request.method}
|
content={flow.request.method}
|
||||||
|
readonly={readonly}
|
||||||
onDone={method => updateFlow({ request: { method } })}
|
onDone={method => updateFlow({ request: { method } })}
|
||||||
inline
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ValueEditor
|
<ValidateEditor
|
||||||
ref="url"
|
|
||||||
content={RequestUtils.pretty_url(flow.request)}
|
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}
|
isValid={url => !!parseUrl(url).host}
|
||||||
inline
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ValueEditor
|
<ValidateEditor
|
||||||
ref="httpVersion"
|
|
||||||
content={flow.request.http_version}
|
content={flow.request.http_version}
|
||||||
onDone={ver => updateFlow({ request: { http_version: parseHttpVersion(ver) } })}
|
readonly={readonly}
|
||||||
|
onDone={http_version => updateFlow({ request: { http_version } })}
|
||||||
isValid={isValidHttpVersion}
|
isValid={isValidHttpVersion}
|
||||||
inline
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
</div>
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ResponseLine extends Component {
|
function ResponseLine({ flow, readonly, updateFlow }) {
|
||||||
|
return (
|
||||||
render() {
|
<div className="first-line response-line">
|
||||||
const { flow, updateFlow } = this.props
|
<ValidateEditor
|
||||||
|
content={flow.response.http_version}
|
||||||
return (
|
readonly={readonly}
|
||||||
<div className="first-line response-line">
|
onDone={nextVer => updateFlow({ response: { http_version: nextVer } })}
|
||||||
<ValueEditor
|
isValid={isValidHttpVersion}
|
||||||
ref="httpVersion"
|
/>
|
||||||
content={flow.response.http_version}
|
|
||||||
onDone={nextVer => updateFlow({ response: { http_version: parseHttpVersion(nextVer) } })}
|
<ValidateEditor
|
||||||
isValid={isValidHttpVersion}
|
content={flow.response.status_code + ''}
|
||||||
inline
|
readonly={readonly}
|
||||||
/>
|
onDone={code => updateFlow({ response: { code: parseInt(code) } })}
|
||||||
|
isValid={code => /^\d+$/.test(code)}
|
||||||
<ValueEditor
|
/>
|
||||||
ref="code"
|
|
||||||
content={flow.response.status_code + ''}
|
<ValueEditor
|
||||||
onDone={code => updateFlow({ response: { code: parseInt(code) } })}
|
content={flow.response.reason}
|
||||||
isValid={code => /^\d+$/.test(code)}
|
readonly={readonly}
|
||||||
inline
|
onDone={msg => updateFlow({ response: { msg } })}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<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 {
|
export class Request extends Component {
|
||||||
render() {
|
render() {
|
||||||
const { flow, updateFlow } = this.props
|
const { flow, isEdit, updateFlow } = this.props
|
||||||
let onContentChange = content => flowActions.updateContent(this.props.flow, content, "request")
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="request">
|
<section className="request">
|
||||||
<FlowEditorButton onContentChange={onContentChange}/>
|
<ToggleEdit/>
|
||||||
<RequestLine ref="requestLine" flow={flow} updateFlow={updateFlow} />
|
<RequestLine
|
||||||
|
flow={flow}
|
||||||
|
readonly={!isEdit}
|
||||||
|
updateFlow={updateFlow}/>
|
||||||
<Headers
|
<Headers
|
||||||
ref="headers"
|
|
||||||
message={flow.request}
|
message={flow.request}
|
||||||
|
readonly={!isEdit}
|
||||||
onChange={headers => updateFlow({ request: { headers } })}
|
onChange={headers => updateFlow({ request: { headers } })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
<ContentView flow={flow}
|
<ContentView flow={flow} message={flow.request}/>
|
||||||
onContentChange={onContentChange}
|
|
||||||
message={flow.request}
|
|
||||||
/>
|
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
edit(k) {
|
edit(k) {
|
||||||
switch (k) {
|
throw "unimplemented"
|
||||||
case 'm':
|
/*
|
||||||
this.refs.requestLine.refs.method.focus()
|
switch (k) {
|
||||||
break
|
case 'm':
|
||||||
case 'u':
|
this.refs.requestLine.refs.method.focus()
|
||||||
this.refs.requestLine.refs.url.focus()
|
break
|
||||||
break
|
case 'u':
|
||||||
case 'v':
|
this.refs.requestLine.refs.url.focus()
|
||||||
this.refs.requestLine.refs.httpVersion.focus()
|
break
|
||||||
break
|
case 'v':
|
||||||
case 'h':
|
this.refs.requestLine.refs.httpVersion.focus()
|
||||||
this.refs.headers.edit()
|
break
|
||||||
break
|
case 'h':
|
||||||
default:
|
this.refs.headers.edit()
|
||||||
throw new Error(`Unimplemented: ${k}`)
|
break
|
||||||
}
|
default:
|
||||||
|
throw new Error(`Unimplemented: ${k}`)
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Request = Message(Request)
|
||||||
|
|
||||||
|
|
||||||
export class Response extends Component {
|
export class Response extends Component {
|
||||||
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { flow, updateFlow } = this.props
|
const { flow, isEdit, updateFlow } = this.props
|
||||||
let onContentChange = content => flowActions.updateContent(this.props.flow, content, "response")
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="response">
|
<section className="response">
|
||||||
<FlowEditorButton onContentChange={onContentChange}/>
|
<ToggleEdit/>
|
||||||
<ResponseLine ref="responseLine" flow={flow} updateFlow={updateFlow} />
|
<ResponseLine
|
||||||
|
flow={flow}
|
||||||
|
readonly={!isEdit}
|
||||||
|
updateFlow={updateFlow}/>
|
||||||
<Headers
|
<Headers
|
||||||
ref="headers"
|
|
||||||
message={flow.response}
|
message={flow.response}
|
||||||
|
readonly={!isEdit}
|
||||||
onChange={headers => updateFlow({ response: { headers } })}
|
onChange={headers => updateFlow({ response: { headers } })}
|
||||||
/>
|
/>
|
||||||
<hr/>
|
<hr/>
|
||||||
<ContentView flow={flow}
|
<ContentView flow={flow} message={flow.response}/>
|
||||||
onContentChange={onContentChange}
|
|
||||||
message={flow.response}
|
|
||||||
/>
|
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
edit(k) {
|
edit(k) {
|
||||||
switch (k) {
|
throw "unimplemented"
|
||||||
case 'c':
|
/*
|
||||||
this.refs.responseLine.refs.status_code.focus()
|
switch (k) {
|
||||||
break
|
case 'c':
|
||||||
case 'm':
|
this.refs.responseLine.refs.status_code.focus()
|
||||||
this.refs.responseLine.refs.msg.focus()
|
break
|
||||||
break
|
case 'm':
|
||||||
case 'v':
|
this.refs.responseLine.refs.msg.focus()
|
||||||
this.refs.responseLine.refs.httpVersion.focus()
|
break
|
||||||
break
|
case 'v':
|
||||||
case 'h':
|
this.refs.responseLine.refs.httpVersion.focus()
|
||||||
this.refs.headers.edit()
|
break
|
||||||
break
|
case 'h':
|
||||||
default:
|
this.refs.headers.edit()
|
||||||
throw new Error(`'Unimplemented: ${k}`)
|
break
|
||||||
}
|
default:
|
||||||
|
throw new Error(`'Unimplemented: ${k}`)
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Response = Message(Response)
|
||||||
|
|
||||||
|
|
||||||
ErrorView.propTypes = {
|
ErrorView.propTypes = {
|
||||||
flow: PropTypes.object.isRequired,
|
flow: PropTypes.object.isRequired,
|
||||||
}
|
}
|
||||||
|
38
web/src/js/components/FlowView/ToggleEdit.jsx
Normal file
38
web/src/js/components/FlowView/ToggleEdit.jsx
Normal file
@ -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)
|
@ -48,6 +48,6 @@ function Footer({ settings }) {
|
|||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
state => ({
|
state => ({
|
||||||
settings: state.settings.settings,
|
settings: state.settings,
|
||||||
})
|
})
|
||||||
)(Footer)
|
)(Footer)
|
||||||
|
@ -6,7 +6,7 @@ import ViewMenu from './Header/ViewMenu'
|
|||||||
import OptionMenu from './Header/OptionMenu'
|
import OptionMenu from './Header/OptionMenu'
|
||||||
import FileMenu from './Header/FileMenu'
|
import FileMenu from './Header/FileMenu'
|
||||||
import FlowMenu from './Header/FlowMenu'
|
import FlowMenu from './Header/FlowMenu'
|
||||||
import {setActiveMenu} from '../ducks/ui.js'
|
import {setActiveMenu} from '../ducks/ui/header'
|
||||||
|
|
||||||
class Header extends Component {
|
class Header extends Component {
|
||||||
static entries = [MainMenu, ViewMenu, OptionMenu]
|
static entries = [MainMenu, ViewMenu, OptionMenu]
|
||||||
@ -17,7 +17,7 @@ class Header extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { query, selectedFlowId, activeMenu} = this.props
|
const { selectedFlowId, activeMenu} = this.props
|
||||||
|
|
||||||
let entries = [...Header.entries]
|
let entries = [...Header.entries]
|
||||||
if(selectedFlowId)
|
if(selectedFlowId)
|
||||||
@ -39,10 +39,7 @@ class Header extends Component {
|
|||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
<div className="menu">
|
<div className="menu">
|
||||||
<Active
|
<Active/>
|
||||||
ref="active"
|
|
||||||
query={query}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
)
|
)
|
||||||
@ -52,13 +49,9 @@ class Header extends Component {
|
|||||||
export default connect(
|
export default connect(
|
||||||
state => ({
|
state => ({
|
||||||
selectedFlowId: state.flows.selected[0],
|
selectedFlowId: state.flows.selected[0],
|
||||||
activeMenu: state.ui.activeMenu,
|
activeMenu: state.ui.header.activeMenu,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
setActiveMenu,
|
setActiveMenu,
|
||||||
},
|
|
||||||
null,
|
|
||||||
{
|
|
||||||
withRef: true,
|
|
||||||
}
|
}
|
||||||
)(Header)
|
)(Header)
|
||||||
|
@ -1,92 +1,50 @@
|
|||||||
import React, { Component, PropTypes } from 'react'
|
import React, { Component, PropTypes } from 'react'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import FilterInput from './FilterInput'
|
import FilterInput from './FilterInput'
|
||||||
import { Query } from '../../actions.js'
|
|
||||||
import { update as updateSettings } from '../../ducks/settings'
|
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'
|
export default function MainMenu() {
|
||||||
static route = 'flows'
|
return (
|
||||||
|
<div>
|
||||||
static propTypes = {
|
<div className="menu-row">
|
||||||
query: PropTypes.object.isRequired,
|
<FlowFilterInput/>
|
||||||
settings: PropTypes.object.isRequired,
|
<HighlightInput/>
|
||||||
updateSettings: PropTypes.func.isRequired,
|
<InterceptInput/>
|
||||||
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>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
<div className="clearfix"></div>
|
||||||
}
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
const InterceptInput = connect(
|
||||||
state => ({
|
state => ({
|
||||||
settings: state.settings.settings,
|
value: state.settings.intercept || '',
|
||||||
selectedInput: state.ui.selectedInput
|
placeholder: 'Intercept',
|
||||||
|
type: 'pause',
|
||||||
|
color: 'hsl(208, 56%, 53%)'
|
||||||
}),
|
}),
|
||||||
{
|
{ onChange: intercept => updateSettings({ intercept }) }
|
||||||
updateSettings,
|
)(FilterInput);
|
||||||
updateQuery,
|
|
||||||
setSelectedInput
|
const FlowFilterInput = connect(
|
||||||
},
|
state => ({
|
||||||
null,
|
value: state.flowView.filter || '',
|
||||||
{
|
placeholder: 'Search',
|
||||||
withRef: true,
|
type: 'search',
|
||||||
}
|
color: 'black'
|
||||||
)(MainMenu);
|
}),
|
||||||
|
{ onChange: updateFilter }
|
||||||
|
)(FilterInput);
|
||||||
|
|
||||||
|
const HighlightInput = connect(
|
||||||
|
state => ({
|
||||||
|
value: state.flowView.highlight || '',
|
||||||
|
placeholder: 'Highlight',
|
||||||
|
type: 'tag',
|
||||||
|
color: 'hsl(48, 100%, 50%)'
|
||||||
|
}),
|
||||||
|
{ onChange: updateHighlight }
|
||||||
|
)(FilterInput);
|
||||||
|
@ -8,53 +8,52 @@ OptionMenu.title = 'Options'
|
|||||||
|
|
||||||
OptionMenu.propTypes = {
|
OptionMenu.propTypes = {
|
||||||
settings: PropTypes.object.isRequired,
|
settings: PropTypes.object.isRequired,
|
||||||
onSettingsChange: PropTypes.func.isRequired,
|
updateSettings: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
function OptionMenu({ settings, onSettingsChange }) {
|
function OptionMenu({ settings, updateSettings }) {
|
||||||
// @todo use settings.map
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="menu-row">
|
<div className="menu-row">
|
||||||
<ToggleButton text="showhost"
|
<ToggleButton text="showhost"
|
||||||
checked={settings.showhost}
|
checked={settings.showhost}
|
||||||
onToggle={() => onSettingsChange({ showhost: !settings.showhost })}
|
onToggle={() => updateSettings({ showhost: !settings.showhost })}
|
||||||
/>
|
/>
|
||||||
<ToggleButton text="no_upstream_cert"
|
<ToggleButton text="no_upstream_cert"
|
||||||
checked={settings.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"
|
<ToggleButton text="rawtcp"
|
||||||
checked={settings.rawtcp}
|
checked={settings.rawtcp}
|
||||||
onToggle={() => onSettingsChange({ rawtcp: !settings.rawtcp })}
|
onToggle={() => updateSettings({ rawtcp: !settings.rawtcp })}
|
||||||
/>
|
/>
|
||||||
<ToggleButton text="http2"
|
<ToggleButton text="http2"
|
||||||
checked={settings.http2}
|
checked={settings.http2}
|
||||||
onToggle={() => onSettingsChange({ http2: !settings.http2 })}
|
onToggle={() => updateSettings({ http2: !settings.http2 })}
|
||||||
/>
|
/>
|
||||||
<ToggleButton text="anticache"
|
<ToggleButton text="anticache"
|
||||||
checked={settings.anticache}
|
checked={settings.anticache}
|
||||||
onToggle={() => onSettingsChange({ anticache: !settings.anticache })}
|
onToggle={() => updateSettings({ anticache: !settings.anticache })}
|
||||||
/>
|
/>
|
||||||
<ToggleButton text="anticomp"
|
<ToggleButton text="anticomp"
|
||||||
checked={settings.anticomp}
|
checked={settings.anticomp}
|
||||||
onToggle={() => onSettingsChange({ anticomp: !settings.anticomp })}
|
onToggle={() => updateSettings({ anticomp: !settings.anticomp })}
|
||||||
/>
|
/>
|
||||||
<ToggleInputButton name="stickyauth" placeholder="Sticky auth filter"
|
<ToggleInputButton name="stickyauth" placeholder="Sticky auth filter"
|
||||||
checked={!!settings.stickyauth}
|
checked={!!settings.stickyauth}
|
||||||
txt={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"
|
<ToggleInputButton name="stickycookie" placeholder="Sticky cookie filter"
|
||||||
checked={!!settings.stickycookie}
|
checked={!!settings.stickycookie}
|
||||||
txt={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..."
|
<ToggleInputButton name="stream" placeholder="stream..."
|
||||||
checked={!!settings.stream}
|
checked={!!settings.stream}
|
||||||
txt={settings.stream || ''}
|
txt={settings.stream || ''}
|
||||||
inputType="number"
|
inputType="number"
|
||||||
onToggleChanged={txt => onSettingsChange({ stream: !settings.stream ? txt : null })}
|
onToggleChanged={txt => updateSettings({ stream: !settings.stream ? txt : null })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="clearfix"/>
|
<div className="clearfix"/>
|
||||||
@ -64,9 +63,9 @@ function OptionMenu({ settings, onSettingsChange }) {
|
|||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
state => ({
|
state => ({
|
||||||
settings: state.settings.settings,
|
settings: state.settings,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
onSettingsChange: updateSettings,
|
updateSettings,
|
||||||
}
|
}
|
||||||
)(OptionMenu)
|
)(OptionMenu)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React, { Component, PropTypes } from 'react'
|
import React, { Component, PropTypes } from 'react'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { Query } from '../actions.js'
|
|
||||||
import Splitter from './common/Splitter'
|
import Splitter from './common/Splitter'
|
||||||
import FlowTable from './FlowTable'
|
import FlowTable from './FlowTable'
|
||||||
import FlowView from './FlowView'
|
import FlowView from './FlowView'
|
||||||
@ -14,19 +13,6 @@ class MainView extends Component {
|
|||||||
sort: PropTypes.object,
|
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() {
|
render() {
|
||||||
const { flows, selectedFlow, highlight } = this.props
|
const { flows, selectedFlow, highlight } = this.props
|
||||||
return (
|
return (
|
||||||
@ -66,9 +52,5 @@ export default connect(
|
|||||||
updateFilter,
|
updateFilter,
|
||||||
updateHighlight,
|
updateHighlight,
|
||||||
updateFlow: flowsActions.update,
|
updateFlow: flowsActions.update,
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
withRef: true
|
|
||||||
}
|
}
|
||||||
)(MainView)
|
)(MainView)
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
import React, { Component, PropTypes } from 'react'
|
import React, { Component, PropTypes } from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import _ from 'lodash'
|
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
|
|
||||||
import { init as appInit, destruct as appDestruct } from '../ducks/app'
|
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 Header from './Header'
|
||||||
import EventLog from './EventLog'
|
import EventLog from './EventLog'
|
||||||
import Footer from './Footer'
|
import Footer from './Footer'
|
||||||
import { Key } from '../utils.js'
|
|
||||||
|
|
||||||
class ProxyAppMain extends Component {
|
class ProxyAppMain extends Component {
|
||||||
|
|
||||||
@ -27,6 +24,15 @@ class ProxyAppMain extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
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) {
|
if (nextProps.query === this.props.query && nextProps.selectedFlowId === this.props.selectedFlowId && nextProps.panel === this.props.panel) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -35,13 +41,14 @@ class ProxyAppMain extends Component {
|
|||||||
} else {
|
} else {
|
||||||
this.context.router.replace({ pathname: '/flows', query: nextProps.query })
|
this.context.router.replace({ pathname: '/flows', query: nextProps.query })
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { showEventLog, location, children, query } = this.props
|
const { showEventLog, location, children, query } = this.props
|
||||||
return (
|
return (
|
||||||
<div id="container" tabIndex="0">
|
<div id="container" tabIndex="0">
|
||||||
<Header ref="header" query={query} />
|
<Header/>
|
||||||
{React.cloneElement(
|
{React.cloneElement(
|
||||||
children,
|
children,
|
||||||
{ ref: 'view', location, query }
|
{ ref: 'view', location, query }
|
||||||
@ -58,8 +65,8 @@ class ProxyAppMain extends Component {
|
|||||||
export default connect(
|
export default connect(
|
||||||
state => ({
|
state => ({
|
||||||
showEventLog: state.eventLog.visible,
|
showEventLog: state.eventLog.visible,
|
||||||
query: state.ui.query,
|
query: state.flowView.filter,
|
||||||
panel: state.ui.panel,
|
panel: state.ui.flow.tab,
|
||||||
selectedFlowId: state.flows.selected[0]
|
selectedFlowId: state.flows.selected[0]
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,57 +1,57 @@
|
|||||||
import React, { Component, PropTypes } from 'react'
|
import React, { Component, PropTypes } from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ValueEditor from './ValueEditor'
|
||||||
import EditorBase from './EditorBase'
|
import classnames from 'classnames'
|
||||||
|
|
||||||
|
|
||||||
export default class ValidateEditor extends Component {
|
export default class ValidateEditor extends Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
content: PropTypes.string.isRequired,
|
content: PropTypes.string.isRequired,
|
||||||
|
readonly: PropTypes.bool,
|
||||||
onDone: PropTypes.func.isRequired,
|
onDone: PropTypes.func.isRequired,
|
||||||
onInput: PropTypes.func,
|
|
||||||
isValid: PropTypes.func,
|
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
isValid: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = { currentContent: props.content }
|
this.state = { valid: props.isValid(props.content) }
|
||||||
this.onInput = this.onInput.bind(this)
|
this.onInput = this.onInput.bind(this)
|
||||||
this.onDone = this.onDone.bind(this)
|
this.onDone = this.onDone.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
this.setState({ currentContent: nextProps.content })
|
this.setState({ valid: nextProps.isValid(nextProps.content) })
|
||||||
}
|
}
|
||||||
|
|
||||||
onInput(currentContent) {
|
onInput(content) {
|
||||||
this.setState({ currentContent })
|
this.setState({ valid: this.props.isValid(content) })
|
||||||
this.props.onInput && this.props.onInput(currentContent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onDone(content) {
|
onDone(content) {
|
||||||
if (this.props.isValid && !this.props.isValid(content)) {
|
if (!this.props.isValid(content)) {
|
||||||
this.refs.editor.reset()
|
this.editor.reset()
|
||||||
content = this.props.content
|
content = this.props.content
|
||||||
}
|
}
|
||||||
this.props.onDone(content)
|
this.props.onDone(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let className = this.props.className || ''
|
let className = classnames(
|
||||||
if (this.props.isValid) {
|
this.props.className,
|
||||||
if (this.props.isValid(this.state.currentContent)) {
|
{
|
||||||
className += ' has-success'
|
'has-success': this.state.valid,
|
||||||
} else {
|
'has-warning': !this.state.valid
|
||||||
className += ' has-warning'
|
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
return (
|
return (
|
||||||
<EditorBase
|
<ValueEditor
|
||||||
{...this.props}
|
content={this.props.content}
|
||||||
ref="editor"
|
readonly={this.props.readonly}
|
||||||
className={className}
|
|
||||||
onDone={this.onDone}
|
onDone={this.onDone}
|
||||||
onInput={this.onInput}
|
onInput={this.onInput}
|
||||||
|
className={className}
|
||||||
|
ref={(e) => this.editor = e}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
89
web/src/js/components/ValueEditor/EditorBase.jsx → web/src/js/components/ValueEditor/ValueEditor.jsx
Executable file → Normal file
89
web/src/js/components/ValueEditor/EditorBase.jsx → web/src/js/components/ValueEditor/ValueEditor.jsx
Executable file → Normal file
@ -1,61 +1,66 @@
|
|||||||
import React, { Component, PropTypes } from 'react'
|
import React, { Component, PropTypes } from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import _ from "lodash"
|
||||||
import {Key} from '../../utils.js'
|
import classnames from 'classnames'
|
||||||
|
|
||||||
export default class EditorBase extends Component {
|
import { Key } from '../../utils'
|
||||||
|
|
||||||
|
export default class ValueEditor extends Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
content: PropTypes.string.isRequired,
|
content: PropTypes.string.isRequired,
|
||||||
|
readonly: PropTypes.bool,
|
||||||
onDone: PropTypes.func.isRequired,
|
onDone: PropTypes.func.isRequired,
|
||||||
contentToHtml: PropTypes.func,
|
|
||||||
nodeToContent: PropTypes.func,
|
|
||||||
onStop: PropTypes.func,
|
|
||||||
submitOnEnter: PropTypes.bool,
|
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
tag: PropTypes.string,
|
onInput: PropTypes.func,
|
||||||
|
onKeyDown: PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
contentToHtml: content => _.escape(content),
|
onInput: () => {},
|
||||||
nodeToContent: node => node.textContent,
|
onKeyDown: () => {},
|
||||||
submitOnEnter: true,
|
|
||||||
className: '',
|
|
||||||
tag: 'div',
|
|
||||||
onStop: _.noop,
|
|
||||||
onMouseDown: _.noop,
|
|
||||||
onBlur: _.noop,
|
|
||||||
onInput: _.noop,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {editable: false}
|
this.state = { editable: false }
|
||||||
|
|
||||||
this.onPaste = this.onPaste.bind(this)
|
this.onPaste = this.onPaste.bind(this)
|
||||||
this.onMouseDown = this.onMouseDown.bind(this)
|
this.onMouseDown = this.onMouseDown.bind(this)
|
||||||
this.onMouseUp = this.onMouseUp.bind(this)
|
this.onMouseUp = this.onMouseUp.bind(this)
|
||||||
this.onFocus = this.onFocus.bind(this)
|
this.onFocus = this.onFocus.bind(this)
|
||||||
this.onClick = this.onClick.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.onBlur = this.onBlur.bind(this)
|
||||||
this.reset = this.reset.bind(this)
|
this.reset = this.reset.bind(this)
|
||||||
this.onKeyDown = this.onKeyDown.bind(this)
|
this.onKeyDown = this.onKeyDown.bind(this)
|
||||||
this.onInput = this.onInput.bind(this)
|
this.onInput = this.onInput.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
blur() {
|
||||||
// a stop would cause a blur as a side-effect.
|
// a stop would cause a blur as a side-effect.
|
||||||
// but a blur event must trigger a stop as well.
|
// 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.
|
// to fix this, make stop = blur and do the actual stop in the onBlur handler.
|
||||||
ReactDOM.findDOMNode(this).blur()
|
this.input.blur()
|
||||||
this.props.onStop()
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.input.innerHTML = _.escape(this.props.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
let className = classnames(
|
||||||
|
'inline-input',
|
||||||
|
{
|
||||||
|
'readonly': this.props.readonly,
|
||||||
|
'editable': !this.props.readonly
|
||||||
|
},
|
||||||
|
this.props.className
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
<this.props.tag
|
<div
|
||||||
tabIndex="0"
|
ref={input => this.input = input}
|
||||||
className={`inline-input ${this.props.className}`}
|
tabIndex={!this.props.readonly && "0"}
|
||||||
|
className={className}
|
||||||
contentEditable={this.state.editable || undefined}
|
contentEditable={this.state.editable || undefined}
|
||||||
onFocus={this.onFocus}
|
onFocus={this.onFocus}
|
||||||
onMouseDown={this.onMouseDown}
|
onMouseDown={this.onMouseDown}
|
||||||
@ -64,8 +69,8 @@ export default class EditorBase extends Component {
|
|||||||
onKeyDown={this.onKeyDown}
|
onKeyDown={this.onKeyDown}
|
||||||
onInput={this.onInput}
|
onInput={this.onInput}
|
||||||
onPaste={this.onPaste}
|
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) {
|
onMouseDown(e) {
|
||||||
this._mouseDown = true
|
this._mouseDown = true
|
||||||
window.addEventListener('mouseup', this.onMouseUp)
|
window.addEventListener('mouseup', this.onMouseUp)
|
||||||
this.props.onMouseDown(e)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseUp() {
|
onMouseUp() {
|
||||||
@ -94,7 +98,7 @@ export default class EditorBase extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onFocus(e) {
|
onFocus(e) {
|
||||||
if (this._mouseDown || this._ignore_events || this.state.editable) {
|
if (this._mouseDown || this._ignore_events || this.state.editable || this.props.readonly) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,31 +118,29 @@ export default class EditorBase extends Component {
|
|||||||
range = document.caretRangeFromPoint(e.clientX, e.clientY)
|
range = document.caretRangeFromPoint(e.clientX, e.clientY)
|
||||||
} else {
|
} else {
|
||||||
range = document.createRange()
|
range = document.createRange()
|
||||||
range.selectNodeContents(ReactDOM.findDOMNode(this))
|
range.selectNodeContents(this.input)
|
||||||
}
|
}
|
||||||
|
|
||||||
this._ignore_events = true
|
this._ignore_events = true
|
||||||
this.setState({ editable: true }, () => {
|
this.setState({ editable: true }, () => {
|
||||||
const node = ReactDOM.findDOMNode(this)
|
this.input.blur()
|
||||||
node.blur()
|
this.input.focus()
|
||||||
node.focus()
|
|
||||||
this._ignore_events = false
|
this._ignore_events = false
|
||||||
|
range.selectNodeContents(this.input)
|
||||||
|
sel.removeAllRanges();
|
||||||
|
sel.addRange(range);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onBlur(e) {
|
onBlur(e) {
|
||||||
if (this._ignore_events) {
|
if (this._ignore_events || this.props.readonly) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
window.getSelection().removeAllRanges() //make sure that selection is cleared on blur
|
window.getSelection().removeAllRanges() //make sure that selection is cleared on blur
|
||||||
this.setState({ editable: false })
|
this.setState({ editable: false })
|
||||||
this.props.onDone(this.props.nodeToContent(ReactDOM.findDOMNode(this)))
|
this.props.onDone(this.input.textContent)
|
||||||
this.props.onBlur(e)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
|
||||||
ReactDOM.findDOMNode(this).innerHTML = this.props.contentToHtml(this.props.content)
|
|
||||||
}
|
|
||||||
|
|
||||||
onKeyDown(e) {
|
onKeyDown(e) {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
@ -146,20 +148,21 @@ export default class EditorBase extends Component {
|
|||||||
case Key.ESC:
|
case Key.ESC:
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.reset()
|
this.reset()
|
||||||
this.stop()
|
this.blur()
|
||||||
break
|
break
|
||||||
case Key.ENTER:
|
case Key.ENTER:
|
||||||
if (this.props.submitOnEnter && !e.shiftKey) {
|
if (!e.shiftKey) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.stop()
|
this.blur()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
this.props.onKeyDown(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
onInput() {
|
onInput() {
|
||||||
this.props.onInput(this.props.nodeToContent(ReactDOM.findDOMNode(this)))
|
this.props.onInput(this.input.textContent)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -67,56 +67,49 @@ export default function reduce(state = defaultState, action) {
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function accept(flow) {
|
export function accept(flow) {
|
||||||
fetchApi(`/flows/${flow.id}/accept`, { method: 'POST' })
|
return dispatch => fetchApi(`/flows/${flow.id}/accept`, { method: 'POST' })
|
||||||
return { type: REQUEST_ACTION }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function acceptAll() {
|
export function acceptAll() {
|
||||||
fetchApi('/flows/accept', { method: 'POST' })
|
return dispatch => fetchApi('/flows/accept', { method: 'POST' })
|
||||||
return { type: REQUEST_ACTION }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function remove(flow) {
|
export function remove(flow) {
|
||||||
fetchApi(`/flows/${flow.id}`, { method: 'DELETE' })
|
return dispatch => fetchApi(`/flows/${flow.id}`, { method: 'DELETE' })
|
||||||
return { type: REQUEST_ACTION }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function duplicate(flow) {
|
export function duplicate(flow) {
|
||||||
fetchApi(`/flows/${flow.id}/duplicate`, { method: 'POST' })
|
return dispatch => fetchApi(`/flows/${flow.id}/duplicate`, { method: 'POST' })
|
||||||
return { type: REQUEST_ACTION }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function replay(flow) {
|
export function replay(flow) {
|
||||||
fetchApi(`/flows/${flow.id}/replay`, { method: 'POST' })
|
return dispatch => fetchApi(`/flows/${flow.id}/replay`, { method: 'POST' })
|
||||||
return { type: REQUEST_ACTION }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function revert(flow) {
|
export function revert(flow) {
|
||||||
fetchApi(`/flows/${flow.id}/revert`, { method: 'POST' })
|
return dispatch => fetchApi(`/flows/${flow.id}/revert`, { method: 'POST' })
|
||||||
return { type: REQUEST_ACTION }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function update(flow, data) {
|
export function update(flow, data) {
|
||||||
fetchApi.put(`/flows/${flow.id}`, data)
|
return dispatch => fetchApi.put(`/flows/${flow.id}`, data)
|
||||||
return { type: REQUEST_ACTION }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateContent(flow, file, type) {
|
export function updateContent(flow, file, type) {
|
||||||
@ -124,8 +117,7 @@ export function updateContent(flow, file, type) {
|
|||||||
if (typeof file !== File)
|
if (typeof file !== File)
|
||||||
file = new Blob([file], {type: 'plain/text'})
|
file = new Blob([file], {type: 'plain/text'})
|
||||||
body.append('file', file)
|
body.append('file', file)
|
||||||
fetchApi(`/flows/${flow.id}/${type}/content`, {method: 'post', body} )
|
return dispatch => fetchApi(`/flows/${flow.id}/${type}/content`, {method: 'post', body} )
|
||||||
return { type: REQUEST_ACTION }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -133,8 +125,7 @@ export function updateContent(flow, file, type) {
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function clear() {
|
export function clear() {
|
||||||
fetchApi('/clear', { method: 'POST' })
|
return dispatch => fetchApi('/clear', { method: 'POST' })
|
||||||
return { type: REQUEST_ACTION }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -151,8 +142,7 @@ export function download() {
|
|||||||
export function upload(file) {
|
export function upload(file) {
|
||||||
const body = new FormData()
|
const body = new FormData()
|
||||||
body.append('file', file)
|
body.append('file', file)
|
||||||
fetchApi('/flows/dump', { method: 'post', body })
|
return dispatch => fetchApi('/flows/dump', { method: 'post', body })
|
||||||
return { type: REQUEST_ACTION }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import websocket from './websocket'
|
|||||||
import flows from './flows'
|
import flows from './flows'
|
||||||
import flowView from './flowView'
|
import flowView from './flowView'
|
||||||
import settings from './settings'
|
import settings from './settings'
|
||||||
import ui from './ui'
|
import ui from './ui/index'
|
||||||
import msgQueue from './msgQueue'
|
import msgQueue from './msgQueue'
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
|
@ -11,22 +11,19 @@ export const REQUEST_UPDATE = 'REQUEST_UPDATE'
|
|||||||
export const UNKNOWN_CMD = 'SETTINGS_UNKNOWN_CMD'
|
export const UNKNOWN_CMD = 'SETTINGS_UNKNOWN_CMD'
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
settings: {},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function reducer(state = defaultState, action) {
|
export default function reducer(state = defaultState, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|
||||||
case RECEIVE:
|
case RECEIVE:
|
||||||
return {
|
return action.settings
|
||||||
...state,
|
|
||||||
settings: action.settings,
|
|
||||||
}
|
|
||||||
|
|
||||||
case UPDATE:
|
case UPDATE:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
settings: { ...state.settings, ...action.settings },
|
...action.settings,
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -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 () => {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
97
web/src/js/ducks/ui/flow.js
Normal file
97
web/src/js/ducks/ui/flow.js
Normal file
@ -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 })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
50
web/src/js/ducks/ui/header.js
Normal file
50
web/src/js/ducks/ui/header.js
Normal file
@ -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 }
|
||||||
|
}
|
8
web/src/js/ducks/ui/index.js
Normal file
8
web/src/js/ducks/ui/index.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { combineReducers } from 'redux'
|
||||||
|
import flow from './flow'
|
||||||
|
import header from './header'
|
||||||
|
|
||||||
|
export default combineReducers({
|
||||||
|
flow,
|
||||||
|
header,
|
||||||
|
})
|
122
web/src/js/ducks/ui/keyboard.js
Normal file
122
web/src/js/ducks/ui/keyboard.js
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -107,10 +107,3 @@ var isValidHttpVersion_regex = /^HTTP\/\d+(\.\d+)*$/i;
|
|||||||
export var isValidHttpVersion = function (httpVersion) {
|
export var isValidHttpVersion = function (httpVersion) {
|
||||||
return isValidHttpVersion_regex.test(httpVersion);
|
return isValidHttpVersion_regex.test(httpVersion);
|
||||||
};
|
};
|
||||||
|
|
||||||
export var parseHttpVersion = function (httpVersion) {
|
|
||||||
httpVersion = httpVersion.replace("HTTP/", "").split(".");
|
|
||||||
return _.map(httpVersion, function (x) {
|
|
||||||
return parseInt(x);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
Loading…
Reference in New Issue
Block a user