mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-29 02:57:19 +00:00
convert components in common and ContentView folder into typescript, and modified test
This commit is contained in:
parent
7ff97d6e08
commit
6def195743
@ -19,16 +19,12 @@ describe('ViewImage Component', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('ViewServer Component', () => {
|
describe('ViewServer Component', () => {
|
||||||
let store = TStore(),
|
let store = TStore()
|
||||||
setContentViewDescFn = jest.fn(),
|
|
||||||
setContentFn = jest.fn()
|
|
||||||
|
|
||||||
it('should render correctly and connect to state', () => {
|
it('should render correctly and connect to state', () => {
|
||||||
let provider = renderer.create(
|
let provider = renderer.create(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ViewServer
|
<ViewServer
|
||||||
setContentViewDescription={setContentViewDescFn}
|
|
||||||
setContent={setContentFn}
|
|
||||||
flow={tflow}
|
flow={tflow}
|
||||||
message={tflow.response}
|
message={tflow.response}
|
||||||
/>
|
/>
|
||||||
@ -37,38 +33,17 @@ describe('ViewServer Component', () => {
|
|||||||
expect(tree).toMatchSnapshot()
|
expect(tree).toMatchSnapshot()
|
||||||
|
|
||||||
let viewServer = renderer.create(
|
let viewServer = renderer.create(
|
||||||
|
<Provider store={store}>
|
||||||
<PureViewServer
|
<PureViewServer
|
||||||
showFullContent={true}
|
|
||||||
maxLines={10}
|
|
||||||
setContentViewDescription={setContentViewDescFn}
|
|
||||||
setContent={setContentViewDescFn}
|
|
||||||
flow={tflow}
|
flow={tflow}
|
||||||
message={tflow.response}
|
message={tflow.response}
|
||||||
content={JSON.stringify({lines: [['k1', 'v1']]})}
|
content={JSON.stringify({lines: [['k1', 'v1']]})}
|
||||||
/>
|
/>
|
||||||
|
</Provider>
|
||||||
)
|
)
|
||||||
tree = viewServer.toJSON()
|
tree = viewServer.toJSON()
|
||||||
expect(tree).toMatchSnapshot()
|
expect(tree).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle componentWillReceiveProps', () => {
|
|
||||||
// case of fail to parse content
|
|
||||||
let viewServer = TestUtils.renderIntoDocument(
|
|
||||||
<PureViewServer
|
|
||||||
showFullContent={true}
|
|
||||||
maxLines={10}
|
|
||||||
setContentViewDescription={setContentViewDescFn}
|
|
||||||
setContent={setContentViewDescFn}
|
|
||||||
flow={tflow}
|
|
||||||
message={tflow.response}
|
|
||||||
content={JSON.stringify({lines: [['k1', 'v1']]})}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
viewServer.UNSAFE_componentWillReceiveProps({...viewServer.props, content: '{foo' })
|
|
||||||
let e = ''
|
|
||||||
try {JSON.parse('{foo') } catch(err){ e = err.message}
|
|
||||||
expect(viewServer.data).toEqual({ description: e, lines: [] })
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Edit Component', () => {
|
describe('Edit Component', () => {
|
||||||
|
@ -1,39 +1,22 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import renderer from 'react-test-renderer'
|
import renderer from 'react-test-renderer'
|
||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
import ConnectedComponent, { ShowFullContentButton } from '../../../components/ContentView/ShowFullContentButton'
|
import ShowFullContentButton from '../../../components/ContentView/ShowFullContentButton'
|
||||||
import { TStore } from '../../ducks/tutils'
|
import { TStore } from '../../ducks/tutils'
|
||||||
|
|
||||||
|
|
||||||
describe('ShowFullContentButton Component', () => {
|
describe('ShowFullContentButton Component', () => {
|
||||||
|
let store = TStore()
|
||||||
|
|
||||||
let setShowFullContentFn = jest.fn(),
|
let setShowFullContentFn = jest.fn(),
|
||||||
showFullContentButton = renderer.create(
|
showFullContentButton = renderer.create(
|
||||||
<ShowFullContentButton
|
<Provider store={store}>
|
||||||
setShowFullContent={setShowFullContentFn}
|
<ShowFullContentButton />
|
||||||
showFullContent={false}
|
</Provider>
|
||||||
visibleLines={10}
|
|
||||||
contentLines={20}
|
|
||||||
/>
|
|
||||||
),
|
),
|
||||||
tree = showFullContentButton.toJSON()
|
tree = showFullContentButton.toJSON()
|
||||||
|
|
||||||
it('should render correctly', () => {
|
it('should render correctly', () => {
|
||||||
expect(tree).toMatchSnapshot()
|
expect(tree).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle click', () => {
|
|
||||||
tree.children[0].props.onClick()
|
|
||||||
expect(setShowFullContentFn).toBeCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should connect to state', () => {
|
|
||||||
let store = TStore(),
|
|
||||||
provider = renderer.create(
|
|
||||||
<Provider store={store}>
|
|
||||||
<ConnectedComponent/>
|
|
||||||
</Provider>
|
|
||||||
),
|
|
||||||
tree = provider.toJSON()
|
|
||||||
expect(tree).toMatchSnapshot()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
@ -4,23 +4,12 @@ exports[`ContentViewOptions Component should render correctly 1`] = `
|
|||||||
<div
|
<div
|
||||||
className="view-options"
|
className="view-options"
|
||||||
>
|
>
|
||||||
<a
|
|
||||||
className="btn btn-default btn-xs pull-left"
|
|
||||||
href="#"
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
|
||||||
<span>
|
<span>
|
||||||
<b>
|
<b>
|
||||||
View:
|
View:
|
||||||
</b>
|
</b>
|
||||||
|
edit
|
||||||
auto
|
|
||||||
|
|
||||||
<span
|
|
||||||
className="caret"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
<a
|
||||||
className="btn btn-default btn-xs"
|
className="btn btn-default btn-xs"
|
||||||
@ -32,9 +21,21 @@ exports[`ContentViewOptions Component should render correctly 1`] = `
|
|||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a
|
||||||
|
className="btn btn-default btn-xs"
|
||||||
|
href="#"
|
||||||
|
onClick={[Function]}
|
||||||
|
title="Upload a file to replace the content."
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
className="fa fa-fw fa-upload"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
className="hidden"
|
||||||
|
onChange={[Function]}
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
|
||||||
<span>
|
|
||||||
foo
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
@ -34,19 +34,6 @@ exports[`ViewServer Component should render correctly and connect to state 1`] =
|
|||||||
|
|
||||||
exports[`ViewServer Component should render correctly and connect to state 2`] = `
|
exports[`ViewServer Component should render correctly and connect to state 2`] = `
|
||||||
<div>
|
<div>
|
||||||
<pre>
|
<pre />
|
||||||
<div>
|
|
||||||
<span
|
|
||||||
className="k"
|
|
||||||
>
|
|
||||||
1
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className="v"
|
|
||||||
>
|
|
||||||
1
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</pre>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
@ -1,23 +1,3 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`ShowFullContentButton Component should connect to state 1`] = `null`;
|
exports[`ShowFullContentButton Component should render correctly 1`] = `null`;
|
||||||
|
|
||||||
exports[`ShowFullContentButton Component should render correctly 1`] = `
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
className="view-all-content-btn btn-xs btn btn-default"
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
|
||||||
Show full content
|
|
||||||
</button>
|
|
||||||
<span
|
|
||||||
className="pull-right"
|
|
||||||
>
|
|
||||||
|
|
||||||
10
|
|
||||||
/
|
|
||||||
20
|
|
||||||
are visible
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
@ -71,9 +71,7 @@ describe('Request Component', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should handle uploadContent on flow request ContentViewOptions', () => {
|
it('should handle uploadContent on flow request ContentViewOptions', () => {
|
||||||
// The line below shouldn't have .type, this is a workaround for https://github.com/facebook/react/issues/17301.
|
let contentViewOptions = provider.root.findByType(ContentViewOptions)
|
||||||
// If this test breaks, just remove it.
|
|
||||||
let contentViewOptions = provider.root.findByType(ContentViewOptions.type)
|
|
||||||
contentViewOptions.props.uploadContent('foo')
|
contentViewOptions.props.uploadContent('foo')
|
||||||
expect(fetch).toBeCalled()
|
expect(fetch).toBeCalled()
|
||||||
fetch.mockClear()
|
fetch.mockClear()
|
||||||
@ -132,9 +130,7 @@ describe('Response Component', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should handle updateContent on flow response ContentViewOptions', () => {
|
it('should handle updateContent on flow response ContentViewOptions', () => {
|
||||||
// The line below shouldn't have .type, this is a workaround for https://github.com/facebook/react/issues/17301.
|
let contentViewOptions = provider.root.findByType(ContentViewOptions)
|
||||||
// If this test breaks, just remove it.
|
|
||||||
let contentViewOptions = provider.root.findByType(ContentViewOptions.type)
|
|
||||||
contentViewOptions.props.uploadContent('foo')
|
contentViewOptions.props.uploadContent('foo')
|
||||||
expect(fetch).toBeCalled()
|
expect(fetch).toBeCalled()
|
||||||
fetch.mockClear()
|
fetch.mockClear()
|
||||||
|
@ -195,24 +195,13 @@ exports[`Request Component should render correctly 1`] = `
|
|||||||
<footer>
|
<footer>
|
||||||
<div
|
<div
|
||||||
className="view-options"
|
className="view-options"
|
||||||
>
|
|
||||||
<a
|
|
||||||
className="btn btn-default btn-xs pull-left"
|
|
||||||
href="#"
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<b>
|
<b>
|
||||||
View:
|
View:
|
||||||
</b>
|
</b>
|
||||||
|
edit
|
||||||
auto
|
|
||||||
|
|
||||||
<span
|
|
||||||
className="caret"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
<a
|
||||||
className="btn btn-default btn-xs"
|
className="btn btn-default btn-xs"
|
||||||
@ -224,10 +213,22 @@ exports[`Request Component should render correctly 1`] = `
|
|||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a
|
||||||
|
className="btn btn-default btn-xs"
|
||||||
|
href="#"
|
||||||
|
onClick={[Function]}
|
||||||
|
title="Upload a file to replace the content."
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
className="fa fa-fw fa-upload"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
className="hidden"
|
||||||
|
onChange={[Function]}
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
|
||||||
<span>
|
|
||||||
foo
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</section>
|
</section>
|
||||||
@ -455,24 +456,13 @@ exports[`Response Component should render correctly 1`] = `
|
|||||||
<footer>
|
<footer>
|
||||||
<div
|
<div
|
||||||
className="view-options"
|
className="view-options"
|
||||||
>
|
|
||||||
<a
|
|
||||||
className="btn btn-default btn-xs pull-left"
|
|
||||||
href="#"
|
|
||||||
onClick={[Function]}
|
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<b>
|
<b>
|
||||||
View:
|
View:
|
||||||
</b>
|
</b>
|
||||||
|
edit
|
||||||
auto
|
|
||||||
|
|
||||||
<span
|
|
||||||
className="caret"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
<a
|
||||||
className="btn btn-default btn-xs"
|
className="btn btn-default btn-xs"
|
||||||
@ -484,10 +474,22 @@ exports[`Response Component should render correctly 1`] = `
|
|||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a
|
||||||
|
className="btn btn-default btn-xs"
|
||||||
|
href="#"
|
||||||
|
onClick={[Function]}
|
||||||
|
title="Upload a file to replace the content."
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
className="fa fa-fw fa-upload"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
className="hidden"
|
||||||
|
onChange={[Function]}
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
|
||||||
<span>
|
|
||||||
foo
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</section>
|
</section>
|
||||||
|
@ -21,7 +21,7 @@ type ResultProps = {
|
|||||||
results: CommandResult[],
|
results: CommandResult[],
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAvailableCommands(commands, input: string = "") {
|
function getAvailableCommands(commands: object, input: string = "") {
|
||||||
if (!commands) return []
|
if (!commands) return []
|
||||||
let availableCommands: string[] = []
|
let availableCommands: string[] = []
|
||||||
for (const [command, args] of Object.entries(commands)) {
|
for (const [command, args] of Object.entries(commands)) {
|
||||||
@ -37,7 +37,7 @@ export function Results({results}: ResultProps) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (resultElement) {
|
if (resultElement) {
|
||||||
resultElement.current?.addEventListener('DOMNodeInserted', event => {
|
resultElement.current.addEventListener('DOMNodeInserted', event => {
|
||||||
const { currentTarget: target } = event;
|
const { currentTarget: target } = event;
|
||||||
target.scroll({ top: target.scrollHeight, behavior: 'auto' });
|
target.scroll({ top: target.scrollHeight, behavior: 'auto' });
|
||||||
});
|
});
|
||||||
@ -83,7 +83,7 @@ export default function CommandBar() {
|
|||||||
const [completionCandidate, setCompletionCandidate] = useState<string[]>([])
|
const [completionCandidate, setCompletionCandidate] = useState<string[]>([])
|
||||||
|
|
||||||
const [availableCommands, setAvailableCommands] = useState<string[]>([])
|
const [availableCommands, setAvailableCommands] = useState<string[]>([])
|
||||||
const [allCommands, setAllCommands] = useState({})
|
const [allCommands, setAllCommands] = useState<object>({})
|
||||||
const [nextArgs, setNextArgs] = useState<string[]>([])
|
const [nextArgs, setNextArgs] = useState<string[]>([])
|
||||||
const [currentArg, setCurrentArg] = useState<number>(0)
|
const [currentArg, setCurrentArg] = useState<number>(0)
|
||||||
const [signatureHelp, setSignatureHelp] = useState<string>("")
|
const [signatureHelp, setSignatureHelp] = useState<string>("")
|
||||||
|
@ -1,19 +1,26 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { MessageUtils } from '../../flow/utils'
|
import { MessageUtils } from '../../flow/utils'
|
||||||
|
|
||||||
|
type ContentLoaderProps = {
|
||||||
|
content: string,
|
||||||
|
contentView: object,
|
||||||
|
flow: object,
|
||||||
|
message: {
|
||||||
|
content: string,
|
||||||
|
contentHash: string,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContentLoaderStates = {
|
||||||
|
content: string | undefined,
|
||||||
|
request: { abort: () => void }| undefined,
|
||||||
|
}
|
||||||
|
|
||||||
export default function withContentLoader(View) {
|
export default function withContentLoader(View) {
|
||||||
|
|
||||||
return class extends React.Component {
|
return class extends React.Component<ContentLoaderProps, ContentLoaderStates> {
|
||||||
static displayName = View.displayName || View.name
|
static displayName: string = View.displayName || View.name
|
||||||
static matches = View.matches
|
static matches: (message: any) => boolean = View.matches
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
...View.propTypes,
|
|
||||||
content: PropTypes.string, // mark as non-required
|
|
||||||
flow: PropTypes.object.isRequired,
|
|
||||||
message: PropTypes.object.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
@ -45,6 +52,7 @@ export default function withContentLoader(View) {
|
|||||||
|
|
||||||
updateContent(props) {
|
updateContent(props) {
|
||||||
if (this.state.request) {
|
if (this.state.request) {
|
||||||
|
console.log("request:",this.state.request)
|
||||||
this.state.request.abort()
|
this.state.request.abort()
|
||||||
}
|
}
|
||||||
// We have a few special cases where we do not need to make an HTTP request.
|
// We have a few special cases where we do not need to make an HTTP request.
|
@ -1,32 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { connect } from 'react-redux'
|
|
||||||
import ViewSelector from './ViewSelector'
|
|
||||||
import UploadContentButton from './UploadContentButton'
|
|
||||||
import DownloadContentButton from './DownloadContentButton'
|
|
||||||
|
|
||||||
ContentViewOptions.propTypes = {
|
|
||||||
flow: PropTypes.object.isRequired,
|
|
||||||
message: PropTypes.object.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
function ContentViewOptions({ flow, message, uploadContent, readonly, contentViewDescription }) {
|
|
||||||
return (
|
|
||||||
<div className="view-options">
|
|
||||||
{readonly ? <ViewSelector message={message}/> : <span><b>View:</b> edit</span>}
|
|
||||||
|
|
||||||
<DownloadContentButton flow={flow} message={message}/>
|
|
||||||
|
|
||||||
{!readonly && <UploadContentButton uploadContent={uploadContent}/> }
|
|
||||||
|
|
||||||
{readonly && <span>{contentViewDescription}</span>}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
state => ({
|
|
||||||
contentViewDescription: state.ui.flow.viewDescription,
|
|
||||||
readonly: !state.ui.flow.modifiedFlow,
|
|
||||||
})
|
|
||||||
)(ContentViewOptions)
|
|
27
web/src/js/components/ContentView/ContentViewOptions.tsx
Normal file
27
web/src/js/components/ContentView/ContentViewOptions.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import ViewSelector from './ViewSelector'
|
||||||
|
import UploadContentButton from './UploadContentButton'
|
||||||
|
import DownloadContentButton from './DownloadContentButton'
|
||||||
|
import { useAppSelector } from "../../ducks";
|
||||||
|
|
||||||
|
type ContentViewOptionsProps = {
|
||||||
|
flow: object,
|
||||||
|
message: object,
|
||||||
|
uploadContent: () => void,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ContentViewOptions({ flow, message, uploadContent }: ContentViewOptionsProps) {
|
||||||
|
const contentViewDescription = useAppSelector(state => state.ui.flow.viewDescription)
|
||||||
|
const readonly = useAppSelector(state => state.ui.flow.modifiedFlow);
|
||||||
|
return (
|
||||||
|
<div className="view-options">
|
||||||
|
{readonly ? <ViewSelector /> : <span><b>View:</b> edit</span>}
|
||||||
|
|
||||||
|
<DownloadContentButton flow={flow} message={message}/>
|
||||||
|
|
||||||
|
{!readonly && <UploadContentButton uploadContent={uploadContent}/> }
|
||||||
|
|
||||||
|
{readonly && <span>{contentViewDescription}</span>}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -1,99 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { connect } from 'react-redux'
|
|
||||||
import { setContentViewDescription, setContent } from '../../ducks/ui/flow'
|
|
||||||
import withContentLoader from './ContentLoader'
|
|
||||||
import { MessageUtils } from '../../flow/utils'
|
|
||||||
import CodeEditor from './CodeEditor'
|
|
||||||
|
|
||||||
|
|
||||||
const isImage = /^image\/(png|jpe?g|gif|webp|vnc.microsoft.icon|x-icon)$/i
|
|
||||||
ViewImage.matches = msg => isImage.test(MessageUtils.getContentType(msg))
|
|
||||||
ViewImage.propTypes = {
|
|
||||||
flow: PropTypes.object.isRequired,
|
|
||||||
message: PropTypes.object.isRequired,
|
|
||||||
}
|
|
||||||
function ViewImage({ flow, message }) {
|
|
||||||
return (
|
|
||||||
<div className="flowview-image">
|
|
||||||
<img src={MessageUtils.getContentURL(flow, message)} alt="preview" className="img-thumbnail"/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Edit.propTypes = {
|
|
||||||
content: PropTypes.string.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
function Edit({ content, onChange }) {
|
|
||||||
return <CodeEditor content={content} onChange={onChange}/>
|
|
||||||
}
|
|
||||||
Edit = withContentLoader(Edit)
|
|
||||||
|
|
||||||
export class PureViewServer extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
showFullContent: PropTypes.bool.isRequired,
|
|
||||||
maxLines: PropTypes.number.isRequired,
|
|
||||||
setContentViewDescription : PropTypes.func.isRequired,
|
|
||||||
setContent: PropTypes.func.isRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
UNSAFE_componentWillMount(){
|
|
||||||
this.setContentView(this.props)
|
|
||||||
}
|
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps){
|
|
||||||
if (nextProps.content !== this.props.content) {
|
|
||||||
this.setContentView(nextProps)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setContentView(props){
|
|
||||||
try {
|
|
||||||
this.data = JSON.parse(props.content)
|
|
||||||
}catch(err) {
|
|
||||||
this.data = {lines: [], description: err.message}
|
|
||||||
}
|
|
||||||
|
|
||||||
props.setContentViewDescription(props.contentView !== this.data.description ? this.data.description : '')
|
|
||||||
props.setContent(this.data.lines)
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {content, contentView, message, maxLines} = this.props
|
|
||||||
let lines = this.props.showFullContent ? this.data.lines : this.data.lines.slice(0, maxLines)
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{ViewImage.matches(message) && <ViewImage {...this.props} />}
|
|
||||||
<pre>
|
|
||||||
{lines.map((line, i) =>
|
|
||||||
<div key={`line${i}`}>
|
|
||||||
{line.map((element, j) => {
|
|
||||||
let [style, text] = element
|
|
||||||
return (
|
|
||||||
<span key={`tuple${j}`} className={style}>
|
|
||||||
{text}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const ViewServer = connect(
|
|
||||||
state => ({
|
|
||||||
showFullContent: state.ui.flow.showFullContent,
|
|
||||||
maxLines: state.ui.flow.maxContentLines
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
setContentViewDescription,
|
|
||||||
setContent
|
|
||||||
}
|
|
||||||
)(withContentLoader(PureViewServer))
|
|
||||||
|
|
||||||
export { Edit, ViewServer, ViewImage }
|
|
97
web/src/js/components/ContentView/ContentViews.tsx
Normal file
97
web/src/js/components/ContentView/ContentViews.tsx
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { setContentViewDescription, setContent } from '../../ducks/ui/flow'
|
||||||
|
import withContentLoader from './ContentLoader'
|
||||||
|
import { MessageUtils } from '../../flow/utils'
|
||||||
|
import CodeEditor from './CodeEditor'
|
||||||
|
import { useAppDispatch, useAppSelector } from "../../ducks";
|
||||||
|
|
||||||
|
|
||||||
|
const isImage = /^image\/(png|jpe?g|gif|webp|vnc.microsoft.icon|x-icon)$/i
|
||||||
|
ViewImage.matches = msg => isImage.test(MessageUtils.getContentType(msg))
|
||||||
|
|
||||||
|
type ViewImageProps = {
|
||||||
|
flow: object,
|
||||||
|
message: object,
|
||||||
|
}
|
||||||
|
|
||||||
|
function ViewImage({ flow, message }: ViewImageProps) {
|
||||||
|
return (
|
||||||
|
<div className="flowview-image">
|
||||||
|
<img src={MessageUtils.getContentURL(flow, message)} alt="preview" className="img-thumbnail"/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type EditProps = {
|
||||||
|
content: string,
|
||||||
|
onChange: () => void,
|
||||||
|
}
|
||||||
|
|
||||||
|
function PureEdit({ content, onChange }: EditProps) {
|
||||||
|
return <CodeEditor content={content} onChange={onChange}/>
|
||||||
|
}
|
||||||
|
const Edit = withContentLoader(PureEdit)
|
||||||
|
|
||||||
|
type PureViewServerProps = {
|
||||||
|
flow: object,
|
||||||
|
message: object,
|
||||||
|
content: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
type PureViewServerStates = {
|
||||||
|
lines: string[][],
|
||||||
|
description: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PureViewServer({flow, message, content}: PureViewServerProps) {
|
||||||
|
const [data, setData] = useState<PureViewServerStates>({
|
||||||
|
lines: [],
|
||||||
|
description: "",
|
||||||
|
})
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch(),
|
||||||
|
showFullContent: boolean = useAppSelector(state => state.ui.flow.showFullContent),
|
||||||
|
maxLines: number = useAppSelector(state => state.ui.flow.maxContentLines)
|
||||||
|
|
||||||
|
let lines = showFullContent ? data.lines : data.lines?.slice(0, maxLines)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setContentView({flow, message, content})
|
||||||
|
}, [flow, message, content])
|
||||||
|
|
||||||
|
const setContentView = (props) => {
|
||||||
|
try {
|
||||||
|
setData(JSON.parse(props.content))
|
||||||
|
}catch(err) {
|
||||||
|
setData({lines: [], description: err.message})
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(setContentViewDescription(props.contentView !== data.description ? data.description : ''))
|
||||||
|
dispatch(setContent(data.lines))
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{ViewImage.matches(message) && <ViewImage flow={flow} message={message}/>}
|
||||||
|
<pre>
|
||||||
|
{lines.map((line, i) =>
|
||||||
|
<div key={`line${i}`}>
|
||||||
|
{line.map((element, j) => {
|
||||||
|
let [style, text] = element
|
||||||
|
return (
|
||||||
|
<span key={`tuple${j}`} className={style}>
|
||||||
|
{text}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const ViewServer = withContentLoader(PureViewServer)
|
||||||
|
|
||||||
|
export { Edit, ViewServer, ViewImage }
|
@ -1,13 +1,12 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { MessageUtils } from "../../flow/utils"
|
import { MessageUtils } from "../../flow/utils"
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
DownloadContentButton.propTypes = {
|
type DownloadContentButtonProps = {
|
||||||
flow: PropTypes.object.isRequired,
|
flow: object,
|
||||||
message: PropTypes.object.isRequired,
|
message: object,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DownloadContentButton({ flow, message }) {
|
export default function DownloadContentButton({ flow, message }: DownloadContentButtonProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a className="btn btn-default btn-xs"
|
<a className="btn btn-default btn-xs"
|
@ -3,7 +3,18 @@ import { formatSize } from '../../utils'
|
|||||||
import UploadContentButton from './UploadContentButton'
|
import UploadContentButton from './UploadContentButton'
|
||||||
import DownloadContentButton from './DownloadContentButton'
|
import DownloadContentButton from './DownloadContentButton'
|
||||||
|
|
||||||
export function ContentEmpty({ flow, message }) {
|
interface ContentProps {
|
||||||
|
flow: { request: object },
|
||||||
|
message: { contentLength: number },
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContentTooLargeProps extends ContentProps {
|
||||||
|
onClick: () => void,
|
||||||
|
uploadContent: () => any,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function ContentEmpty({ flow, message }: ContentProps) {
|
||||||
return (
|
return (
|
||||||
<div className="alert alert-info">
|
<div className="alert alert-info">
|
||||||
No {flow.request === message ? 'request' : 'response'} content.
|
No {flow.request === message ? 'request' : 'response'} content.
|
||||||
@ -11,7 +22,7 @@ export function ContentEmpty({ flow, message }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ContentMissing({ flow, message }) {
|
export function ContentMissing({ flow, message }: ContentProps) {
|
||||||
return (
|
return (
|
||||||
<div className="alert alert-info">
|
<div className="alert alert-info">
|
||||||
{flow.request === message ? 'Request' : 'Response'} content missing.
|
{flow.request === message ? 'Request' : 'Response'} content missing.
|
||||||
@ -19,7 +30,7 @@ export function ContentMissing({ flow, message }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ContentTooLarge({ message, onClick, uploadContent, flow }) {
|
export function ContentTooLarge({ message, onClick, uploadContent, flow }: ContentTooLargeProps) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="alert alert-warning">
|
<div className="alert alert-warning">
|
@ -1,39 +0,0 @@
|
|||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { connect } from 'react-redux'
|
|
||||||
import { render } from 'react-dom';
|
|
||||||
import Button from '../common/Button';
|
|
||||||
import { setShowFullContent } from '../../ducks/ui/flow'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ShowFullContentButton.propTypes = {
|
|
||||||
setShowFullContent: PropTypes.func.isRequired,
|
|
||||||
showFullContent: PropTypes.bool.isRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ShowFullContentButton ( {setShowFullContent, showFullContent, visibleLines, contentLines} ){
|
|
||||||
|
|
||||||
return (
|
|
||||||
!showFullContent &&
|
|
||||||
<div>
|
|
||||||
<Button className="view-all-content-btn btn-xs" onClick={() => setShowFullContent()}>
|
|
||||||
Show full content
|
|
||||||
</Button>
|
|
||||||
<span className="pull-right"> {visibleLines}/{contentLines} are visible </span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
state => ({
|
|
||||||
showFullContent: state.ui.flow.showFullContent,
|
|
||||||
visibleLines: state.ui.flow.maxContentLines,
|
|
||||||
contentLines: state.ui.flow.content.length
|
|
||||||
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
setShowFullContent
|
|
||||||
}
|
|
||||||
)(ShowFullContentButton)
|
|
||||||
|
|
22
web/src/js/components/ContentView/ShowFullContentButton.tsx
Normal file
22
web/src/js/components/ContentView/ShowFullContentButton.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import Button from '../common/Button';
|
||||||
|
import { setShowFullContent } from '../../ducks/ui/flow'
|
||||||
|
import {useAppDispatch, useAppSelector} from "../../ducks";
|
||||||
|
|
||||||
|
export default function ShowFullContentButton() {
|
||||||
|
const dispatch = useAppDispatch(),
|
||||||
|
showFullContent = useAppSelector(state => state.ui.flow.showFullContent),
|
||||||
|
visibleLines = useAppSelector(state => state.ui.flow.maxContentLines),
|
||||||
|
contentLines = useAppSelector(state => state.ui.flow.content.length)
|
||||||
|
|
||||||
|
return (
|
||||||
|
!showFullContent &&
|
||||||
|
<div>
|
||||||
|
<Button className="view-all-content-btn btn-xs" onClick={() => dispatch(setShowFullContent())}>
|
||||||
|
Show full content
|
||||||
|
</Button>
|
||||||
|
<span className="pull-right"> {visibleLines}/{contentLines} are visible </span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,11 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import FileChooser from '../common/FileChooser'
|
import FileChooser from '../common/FileChooser'
|
||||||
|
|
||||||
UploadContentButton.propTypes = {
|
type UploadContentButtonProps = {
|
||||||
uploadContent: PropTypes.func.isRequired,
|
uploadContent: () => any,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function UploadContentButton({ uploadContent }) {
|
export default function UploadContentButton({ uploadContent }: UploadContentButtonProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FileChooser
|
<FileChooser
|
@ -1,15 +1,16 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import classnames from "classnames"
|
import classnames from "classnames"
|
||||||
|
|
||||||
Button.propTypes = {
|
type ButtonProps = {
|
||||||
onClick: PropTypes.func.isRequired,
|
onClick: () => void,
|
||||||
children: PropTypes.node,
|
children?: React.ReactNode,
|
||||||
icon: PropTypes.string,
|
icon?: string,
|
||||||
title: PropTypes.string,
|
disabled?: boolean,
|
||||||
|
className?: string,
|
||||||
|
title?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Button({ onClick, children, icon, disabled, className, title }) {
|
export default function Button({ onClick, children, icon, disabled, className, title }: ButtonProps) {
|
||||||
return (
|
return (
|
||||||
<button className={classnames(className, 'btn btn-default')}
|
<button className={classnames(className, 'btn btn-default')}
|
||||||
onClick={disabled ? undefined : onClick}
|
onClick={disabled ? undefined : onClick}
|
@ -1,11 +1,11 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import PropTypes from "prop-types"
|
|
||||||
|
|
||||||
DocsLink.propTypes = {
|
type DocLinkProps = {
|
||||||
resource: PropTypes.string.isRequired,
|
children: React.ReactNode,
|
||||||
|
resource: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DocsLink({ children, resource }) {
|
export default function DocsLink({ children, resource }: DocLinkProps) {
|
||||||
let url = `https://docs.mitmproxy.org/stable/${resource}`
|
let url = `https://docs.mitmproxy.org/stable/${resource}`
|
||||||
return (
|
return (
|
||||||
<a target="_blank" href={url}>
|
<a target="_blank" href={url}>
|
@ -2,7 +2,7 @@ import React from "react";
|
|||||||
|
|
||||||
type FileChooserProps = {
|
type FileChooserProps = {
|
||||||
icon: string
|
icon: string
|
||||||
text: string
|
text?: string
|
||||||
className?: string
|
className?: string
|
||||||
title?: string
|
title?: string
|
||||||
onOpenFile: (File) => void
|
onOpenFile: (File) => void
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
|
|
||||||
export default function HideInStatic({ children }) {
|
|
||||||
return (window.MITMWEB_CONF && window.MITMWEB_CONF.static) ? null : [children]
|
|
||||||
}
|
|
9
web/src/js/components/common/HideInStatic.tsx
Normal file
9
web/src/js/components/common/HideInStatic.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
type HideInStaticProps = {
|
||||||
|
children: React.ReactNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function HideInStatic({ children }: HideInStaticProps) {
|
||||||
|
return (window.MITMWEB_CONF && window.MITMWEB_CONF.static) ? null : [children]
|
||||||
|
}
|
@ -2,14 +2,24 @@ import React, { Component } from 'react'
|
|||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
|
|
||||||
export default class Splitter extends Component {
|
type SplitterStates = {
|
||||||
|
applied: boolean,
|
||||||
|
startX: number,
|
||||||
|
startY: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
type SplitterProps = {
|
||||||
|
axis: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Splitter extends Component<SplitterProps, SplitterStates> {
|
||||||
|
|
||||||
static defaultProps = { axis: 'x' }
|
static defaultProps = { axis: 'x' }
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context)
|
super(props, context)
|
||||||
|
|
||||||
this.state = { applied: false, startX: false, startY: false }
|
this.state = { applied: false, startX: 0, startY: 0 }
|
||||||
|
|
||||||
this.onMouseMove = this.onMouseMove.bind(this)
|
this.onMouseMove = this.onMouseMove.bind(this)
|
||||||
this.onMouseDown = this.onMouseDown.bind(this)
|
this.onMouseDown = this.onMouseDown.bind(this)
|
@ -1,13 +1,12 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
ToggleButton.propTypes = {
|
type ToggleButtonProps = {
|
||||||
checked: PropTypes.bool.isRequired,
|
checked: boolean,
|
||||||
onToggle: PropTypes.func.isRequired,
|
onToggle: () => void,
|
||||||
text: PropTypes.string.isRequired
|
text: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ToggleButton({ checked, onToggle, text }) {
|
export default function ToggleButton({ checked, onToggle, text }: ToggleButtonProps) {
|
||||||
return (
|
return (
|
||||||
<div className={"btn btn-toggle " + (checked ? "btn-primary" : "btn-default")} onClick={onToggle}>
|
<div className={"btn btn-toggle " + (checked ? "btn-primary" : "btn-default")} onClick={onToggle}>
|
||||||
<i className={"fa fa-fw " + (checked ? "fa-check-square-o" : "fa-square-o")}/>
|
<i className={"fa fa-fw " + (checked ? "fa-check-square-o" : "fa-square-o")}/>
|
@ -1,19 +1,21 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import { Key } from '../../utils'
|
import { Key } from '../../utils'
|
||||||
|
|
||||||
export default class ToggleInputButton extends Component {
|
type ToggleInputButtonProps = {
|
||||||
|
name: string,
|
||||||
|
txt: string,
|
||||||
|
onToggleChanged: (string) => void,
|
||||||
|
checked: boolean,
|
||||||
|
placeholder: string,
|
||||||
|
inputType: string,
|
||||||
|
}
|
||||||
|
|
||||||
static propTypes = {
|
type ToggleInputButtonStates = {
|
||||||
name: PropTypes.string.isRequired,
|
txt: string,
|
||||||
txt: PropTypes.string,
|
}
|
||||||
onToggleChanged: PropTypes.func.isRequired,
|
|
||||||
checked: PropTypes.bool.isRequired,
|
|
||||||
placeholder: PropTypes.string.isRequired,
|
|
||||||
inputType: PropTypes.string
|
|
||||||
}
|
|
||||||
|
|
||||||
|
export default class ToggleInputButton extends Component<ToggleInputButtonProps, ToggleInputButtonStates> {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = { txt: props.txt || '' }
|
this.state = { txt: props.txt || '' }
|
Loading…
Reference in New Issue
Block a user