mirror of
https://github.com/Kong/insomnia
synced 2024-11-07 22:30:15 +00:00
Add new multipart response viewer (#590)
This commit is contained in:
parent
e05377b513
commit
2547e57dad
@ -193,13 +193,13 @@ export function getAuthTypeName (authType, useLong = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getContentTypeFromHeaders (headers) {
|
export function getContentTypeFromHeaders (headers, defaultValue = null) {
|
||||||
if (!Array.isArray(headers)) {
|
if (!Array.isArray(headers)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const header = headers.find(({name}) => name.toLowerCase() === 'content-type');
|
const header = headers.find(({name}) => name.toLowerCase() === 'content-type');
|
||||||
return header ? header.value : null;
|
return header ? header.value : defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RESPONSE_CODE_REASONS = {
|
export const RESPONSE_CODE_REASONS = {
|
||||||
|
52
app/ui/components/modals/wrapper-modal.js
Normal file
52
app/ui/components/modals/wrapper-modal.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// @flow
|
||||||
|
import * as React from 'react';
|
||||||
|
import autobind from 'autobind-decorator';
|
||||||
|
import Modal from '../base/modal';
|
||||||
|
import ModalBody from '../base/modal-body';
|
||||||
|
import ModalHeader from '../base/modal-header';
|
||||||
|
|
||||||
|
type Props = {};
|
||||||
|
type State = {
|
||||||
|
title: string,
|
||||||
|
body: React.Node
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
class WrapperModal extends React.PureComponent<Props, State> {
|
||||||
|
modal: ?Modal;
|
||||||
|
|
||||||
|
constructor (props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
title: '',
|
||||||
|
body: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_setModalRef (m: ?Modal) {
|
||||||
|
this.modal = m;
|
||||||
|
}
|
||||||
|
|
||||||
|
show (options: Object = {}) {
|
||||||
|
const {title, body} = options;
|
||||||
|
this.setState({title, body});
|
||||||
|
|
||||||
|
this.modal && this.modal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const {title, body} = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal ref={this._setModalRef}>
|
||||||
|
<ModalHeader>{title || 'Uh Oh!'}</ModalHeader>
|
||||||
|
<ModalBody className="wide pad">
|
||||||
|
{body}
|
||||||
|
</ModalBody>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WrapperModal;
|
@ -316,13 +316,12 @@ class RequestPane extends React.PureComponent<Props> {
|
|||||||
</button>
|
</button>
|
||||||
</Tab>
|
</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanel className="react-tabs__tab-panel editor-wrapper">
|
<TabPanel key={uniqueKey} className="react-tabs__tab-panel editor-wrapper">
|
||||||
<BodyEditor
|
<BodyEditor
|
||||||
handleUpdateRequestMimeType={updateRequestMimeType}
|
handleUpdateRequestMimeType={updateRequestMimeType}
|
||||||
handleRender={handleRender}
|
handleRender={handleRender}
|
||||||
handleGetRenderContext={handleGetRenderContext}
|
handleGetRenderContext={handleGetRenderContext}
|
||||||
nunjucksPowerUserMode={nunjucksPowerUserMode}
|
nunjucksPowerUserMode={nunjucksPowerUserMode}
|
||||||
key={uniqueKey}
|
|
||||||
request={request}
|
request={request}
|
||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
environmentId={environmentId}
|
environmentId={environmentId}
|
||||||
@ -337,9 +336,8 @@ class RequestPane extends React.PureComponent<Props> {
|
|||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel className="react-tabs__tab-panel scrollable-container">
|
<TabPanel className="react-tabs__tab-panel scrollable-container">
|
||||||
<div className="scrollable">
|
<div className="scrollable">
|
||||||
<ErrorBoundary errorClassName="font-error pad text-center">
|
<ErrorBoundary key={uniqueKey} errorClassName="font-error pad text-center">
|
||||||
<AuthWrapper
|
<AuthWrapper
|
||||||
key={uniqueKey}
|
|
||||||
oAuth2Token={oAuth2Token}
|
oAuth2Token={oAuth2Token}
|
||||||
showPasswords={showPasswords}
|
showPasswords={showPasswords}
|
||||||
request={request}
|
request={request}
|
||||||
@ -356,10 +354,9 @@ class RequestPane extends React.PureComponent<Props> {
|
|||||||
<div className="pad pad-bottom-sm query-editor__preview">
|
<div className="pad pad-bottom-sm query-editor__preview">
|
||||||
<label className="label--small no-pad-top">Url Preview</label>
|
<label className="label--small no-pad-top">Url Preview</label>
|
||||||
<code className="txt-sm block faint">
|
<code className="txt-sm block faint">
|
||||||
<ErrorBoundary
|
<ErrorBoundary key={uniqueKey}
|
||||||
errorClassName="tall wide vertically-align font-error pad text-center">
|
errorClassName="tall wide vertically-align font-error pad text-center">
|
||||||
<RenderedQueryString
|
<RenderedQueryString
|
||||||
key={uniqueKey}
|
|
||||||
handleRender={handleRender}
|
handleRender={handleRender}
|
||||||
request={request}
|
request={request}
|
||||||
/>
|
/>
|
||||||
@ -368,11 +365,10 @@ class RequestPane extends React.PureComponent<Props> {
|
|||||||
</div>
|
</div>
|
||||||
<div className="scrollable-container">
|
<div className="scrollable-container">
|
||||||
<div className="scrollable">
|
<div className="scrollable">
|
||||||
<ErrorBoundary
|
<ErrorBoundary key={uniqueKey}
|
||||||
errorClassName="tall wide vertically-align font-error pad text-center">
|
errorClassName="tall wide vertically-align font-error pad text-center">
|
||||||
<KeyValueEditor
|
<KeyValueEditor
|
||||||
sortable
|
sortable
|
||||||
key={uniqueKey}
|
|
||||||
namePlaceholder="name"
|
namePlaceholder="name"
|
||||||
valuePlaceholder="value"
|
valuePlaceholder="value"
|
||||||
onToggleDisable={this._trackQueryToggle}
|
onToggleDisable={this._trackQueryToggle}
|
||||||
@ -396,9 +392,8 @@ class RequestPane extends React.PureComponent<Props> {
|
|||||||
</div>
|
</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel className="react-tabs__tab-panel header-editor">
|
<TabPanel className="react-tabs__tab-panel header-editor">
|
||||||
<ErrorBoundary errorClassName="font-error pad text-center">
|
<ErrorBoundary key={uniqueKey} errorClassName="font-error pad text-center">
|
||||||
<RequestHeadersEditor
|
<RequestHeadersEditor
|
||||||
key={uniqueKey}
|
|
||||||
headers={request.headers}
|
headers={request.headers}
|
||||||
handleRender={handleRender}
|
handleRender={handleRender}
|
||||||
handleGetRenderContext={handleGetRenderContext}
|
handleGetRenderContext={handleGetRenderContext}
|
||||||
|
@ -279,9 +279,8 @@ class ResponsePane extends React.PureComponent<Props> {
|
|||||||
</Tab>
|
</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanel className="react-tabs__tab-panel">
|
<TabPanel className="react-tabs__tab-panel">
|
||||||
<ErrorBoundary errorClassName="font-error pad text-center">
|
<ErrorBoundary key={response._id} errorClassName="font-error pad text-center">
|
||||||
<ResponseViewer
|
<ResponseViewer
|
||||||
key={response._id}
|
|
||||||
// Send larger one because legacy responses have bytesContent === -1
|
// Send larger one because legacy responses have bytesContent === -1
|
||||||
responseId={response._id}
|
responseId={response._id}
|
||||||
bytes={Math.max(response.bytesContent, response.bytesRead)}
|
bytes={Math.max(response.bytesContent, response.bytesRead)}
|
||||||
@ -303,32 +302,27 @@ class ResponsePane extends React.PureComponent<Props> {
|
|||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel className="react-tabs__tab-panel scrollable-container">
|
<TabPanel className="react-tabs__tab-panel scrollable-container">
|
||||||
<div className="scrollable pad">
|
<div className="scrollable pad">
|
||||||
<ErrorBoundary errorClassName="font-error pad text-center">
|
<ErrorBoundary key={response._id} errorClassName="font-error pad text-center">
|
||||||
<ResponseHeadersViewer
|
<ResponseHeadersViewer headers={response.headers} />
|
||||||
key={response._id}
|
|
||||||
headers={response.headers}
|
|
||||||
/>
|
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel className="react-tabs__tab-panel scrollable-container">
|
<TabPanel className="react-tabs__tab-panel scrollable-container">
|
||||||
<div className="scrollable pad">
|
<div className="scrollable pad">
|
||||||
<ErrorBoundary errorClassName="font-error pad text-center">
|
<ErrorBoundary key={response._id} errorClassName="font-error pad text-center">
|
||||||
<ResponseCookiesViewer
|
<ResponseCookiesViewer
|
||||||
handleShowRequestSettings={handleShowRequestSettings}
|
handleShowRequestSettings={handleShowRequestSettings}
|
||||||
cookiesSent={response.settingSendCookies}
|
cookiesSent={response.settingSendCookies}
|
||||||
cookiesStored={response.settingStoreCookies}
|
cookiesStored={response.settingStoreCookies}
|
||||||
showCookiesModal={showCookiesModal}
|
showCookiesModal={showCookiesModal}
|
||||||
key={response._id}
|
|
||||||
headers={cookieHeaders}
|
headers={cookieHeaders}
|
||||||
/>
|
/>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel className="react-tabs__tab-panel">
|
<TabPanel className="react-tabs__tab-panel">
|
||||||
<ErrorBoundary errorClassName="font-error pad text-center">
|
<ErrorBoundary key={response._id} errorClassName="font-error pad text-center">
|
||||||
<ResponseTimelineViewer
|
<ResponseTimelineViewer
|
||||||
key={response._id}
|
|
||||||
timeline={response.timeline || []}
|
timeline={response.timeline || []}
|
||||||
editorLineWrapping={editorLineWrapping}
|
editorLineWrapping={editorLineWrapping}
|
||||||
editorFontSize={editorFontSize}
|
editorFontSize={editorFontSize}
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import React, {PureComponent} from 'react';
|
// @flow
|
||||||
import PropTypes from 'prop-types';
|
import * as React from 'react';
|
||||||
import CopyButton from '../base/copy-button';
|
import CopyButton from '../base/copy-button';
|
||||||
|
import type {ResponseHeader} from '../../../models/response';
|
||||||
|
|
||||||
class ResponseHeadersViewer extends PureComponent {
|
type Props = {
|
||||||
|
headers: Array<ResponseHeader>
|
||||||
|
};
|
||||||
|
|
||||||
|
class ResponseHeadersViewer extends React.PureComponent<Props> {
|
||||||
render () {
|
render () {
|
||||||
const {headers} = this.props;
|
const {headers} = this.props;
|
||||||
|
|
||||||
const headersString = headers.map(
|
const headersString = headers.map(h => `${h.name}: ${h.value}`).join('\n');
|
||||||
h => `${h.name}: ${h.value}`
|
|
||||||
).join('\n');
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
<table key='table' className="table--fancy table--striped">
|
<table key='table' className="table--fancy table--striped">
|
||||||
@ -37,8 +40,4 @@ class ResponseHeadersViewer extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ResponseHeadersViewer.propTypes = {
|
|
||||||
headers: PropTypes.array.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ResponseHeadersViewer;
|
export default ResponseHeadersViewer;
|
||||||
|
293
app/ui/components/viewers/response-multipart.js
Normal file
293
app/ui/components/viewers/response-multipart.js
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
// @flow
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as electron from 'electron';
|
||||||
|
import mimes from 'mime-types';
|
||||||
|
import fs from 'fs';
|
||||||
|
import moment from 'moment';
|
||||||
|
import path from 'path';
|
||||||
|
import {PassThrough} from 'stream';
|
||||||
|
import multiparty from 'multiparty';
|
||||||
|
import autobind from 'autobind-decorator';
|
||||||
|
import ResponseViewer from './response-viewer';
|
||||||
|
import {getContentTypeFromHeaders, PREVIEW_MODE_FRIENDLY} from '../../../common/constants';
|
||||||
|
import type {ResponseHeader} from '../../../models/response';
|
||||||
|
import {Dropdown, DropdownButton, DropdownItem} from '../base/dropdown/index';
|
||||||
|
import {trackEvent} from '../../../analytics/index';
|
||||||
|
import WrapperModal from '../modals/wrapper-modal';
|
||||||
|
import {showModal} from '../modals/index';
|
||||||
|
import ResponseHeadersViewer from './response-headers-viewer';
|
||||||
|
|
||||||
|
type Part = {
|
||||||
|
name: string,
|
||||||
|
bytes: number,
|
||||||
|
value: Buffer,
|
||||||
|
filename: string | null,
|
||||||
|
headers: Array<ResponseHeader>,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
responseId: string,
|
||||||
|
bodyBuffer: Buffer | null,
|
||||||
|
contentType: string,
|
||||||
|
filter: string,
|
||||||
|
filterHistory: Array<string>,
|
||||||
|
editorFontSize: number,
|
||||||
|
editorIndentSize: number,
|
||||||
|
editorKeyMap: string,
|
||||||
|
editorLineWrapping: boolean,
|
||||||
|
url: string
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
activePart: number,
|
||||||
|
parts: Array<Part>,
|
||||||
|
error: string | null
|
||||||
|
};
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
class ResponseMultipart extends React.PureComponent<Props, State> {
|
||||||
|
constructor (props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
activePart: -1,
|
||||||
|
parts: [],
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this._setParts();
|
||||||
|
}
|
||||||
|
|
||||||
|
async _setParts () {
|
||||||
|
try {
|
||||||
|
const parts = await this._getParts();
|
||||||
|
this.setState({parts, activePart: 0, error: null});
|
||||||
|
} catch (err) {
|
||||||
|
this.setState({error: err.message});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_describePart (part: Part) {
|
||||||
|
const segments = [part.name];
|
||||||
|
if (part.filename) {
|
||||||
|
segments.push(`(${part.filename})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return segments.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
async _handleSelectPart (index: number) {
|
||||||
|
this.setState({activePart: index});
|
||||||
|
}
|
||||||
|
|
||||||
|
_getBody () {
|
||||||
|
const {parts, activePart} = this.state;
|
||||||
|
const part = parts[activePart];
|
||||||
|
|
||||||
|
if (!part) {
|
||||||
|
return new Buffer('');
|
||||||
|
}
|
||||||
|
|
||||||
|
return part.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleViewHeaders () {
|
||||||
|
const {parts, activePart} = this.state;
|
||||||
|
const part = parts[activePart];
|
||||||
|
|
||||||
|
if (!part) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showModal(WrapperModal, {
|
||||||
|
title: <span>Headers for <code>{part.name}</code></span>,
|
||||||
|
body: <ResponseHeadersViewer
|
||||||
|
headers={[...part.headers, ...part.headers, ...part.headers, ...part.headers]}/>
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleSaveAsFile () {
|
||||||
|
const {parts, activePart} = this.state;
|
||||||
|
const part = parts[activePart];
|
||||||
|
|
||||||
|
if (!part) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = getContentTypeFromHeaders(part.headers, 'text/plain');
|
||||||
|
|
||||||
|
const extension = mimes.extension(contentType) || '.txt';
|
||||||
|
const lastDir = window.localStorage.getItem('insomnia.lastExportPath');
|
||||||
|
const dir = lastDir || electron.remote.app.getPath('desktop');
|
||||||
|
const date = moment().format('YYYY-MM-DD');
|
||||||
|
const filename = part.filename || `${part.name}_${date}`;
|
||||||
|
const options = {
|
||||||
|
title: 'Save as File',
|
||||||
|
buttonLabel: 'Save',
|
||||||
|
defaultPath: path.join(dir, filename),
|
||||||
|
filters: [{
|
||||||
|
name: 'Download', extensions: [extension]
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
electron.remote.dialog.showSaveDialog(options, outputPath => {
|
||||||
|
if (!outputPath) {
|
||||||
|
trackEvent('Response', 'Multipart Save Cancel');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remember last exported path
|
||||||
|
window.localStorage.setItem('insomnia.lastExportPath', path.dirname(filename));
|
||||||
|
|
||||||
|
// Save the file
|
||||||
|
fs.writeFile(outputPath, part.value, err => {
|
||||||
|
if (err) {
|
||||||
|
console.warn('Failed to save multipart to file', err);
|
||||||
|
trackEvent('Response', 'Multipart Save Failure');
|
||||||
|
} else {
|
||||||
|
trackEvent('Response', 'Multipart Save Success');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_getParts (): Promise<Array<Part>> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const {bodyBuffer, contentType} = this.props;
|
||||||
|
const parts = [];
|
||||||
|
|
||||||
|
if (!bodyBuffer) {
|
||||||
|
return resolve(parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fakeReq = new PassThrough();
|
||||||
|
(fakeReq: Object).headers = {
|
||||||
|
'content-type': contentType
|
||||||
|
};
|
||||||
|
|
||||||
|
const form = new multiparty.Form();
|
||||||
|
form.on('part', part => {
|
||||||
|
const dataBuffers = [];
|
||||||
|
part.on('data', data => {
|
||||||
|
dataBuffers.push(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
part.on('error', err => {
|
||||||
|
reject(new Error(`Failed to parse part: ${err.message}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
part.on('end', () => {
|
||||||
|
parts.push({
|
||||||
|
value: Buffer.concat(dataBuffers),
|
||||||
|
name: part.name,
|
||||||
|
filename: part.filename || null,
|
||||||
|
bytes: part.byteCount,
|
||||||
|
headers: Object.keys(part.headers).map(name => ({name, value: part.headers[name]}))
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
form.on('error', err => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
form.on('close', () => {
|
||||||
|
resolve(parts);
|
||||||
|
});
|
||||||
|
|
||||||
|
form.parse(fakeReq);
|
||||||
|
|
||||||
|
fakeReq.write(bodyBuffer);
|
||||||
|
fakeReq.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
responseId,
|
||||||
|
filter,
|
||||||
|
filterHistory,
|
||||||
|
url,
|
||||||
|
editorFontSize,
|
||||||
|
editorIndentSize,
|
||||||
|
editorKeyMap,
|
||||||
|
editorLineWrapping
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
activePart,
|
||||||
|
parts,
|
||||||
|
error
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="pad monospace" style={{fontSize: editorFontSize}}>
|
||||||
|
Failed to parse multipart response: {error}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedPart = parts[activePart];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pad-sm tall wide"
|
||||||
|
style={{display: 'grid', gridTemplateRows: 'auto minmax(0, 1fr)'}}>
|
||||||
|
<div className="pad-bottom-sm"
|
||||||
|
style={{display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) auto'}}>
|
||||||
|
<div>
|
||||||
|
<Dropdown wide>
|
||||||
|
<DropdownButton className="btn btn--clicky">
|
||||||
|
<div style={{minWidth: '200px', display: 'inline-block'}}>
|
||||||
|
{selectedPart ? this._describePart(selectedPart) : 'Unknown'}
|
||||||
|
</div>
|
||||||
|
<i className="fa fa-caret-down fa--skinny space-left"/>
|
||||||
|
</DropdownButton>
|
||||||
|
{parts.map((part, i) => (
|
||||||
|
<DropdownItem key={i} value={i} onClick={this._handleSelectPart}>
|
||||||
|
{i === activePart ? <i className="fa fa-check"/> : <i className="fa fa-empty"/>}
|
||||||
|
{this._describePart(part)}
|
||||||
|
</DropdownItem>
|
||||||
|
))}
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
<Dropdown right>
|
||||||
|
<DropdownButton className="btn btn--clicky">
|
||||||
|
<i className="fa fa-bars"/>
|
||||||
|
</DropdownButton>
|
||||||
|
<DropdownItem onClick={this._handleViewHeaders}>
|
||||||
|
<i className="fa fa-list"/> View Headers
|
||||||
|
</DropdownItem>
|
||||||
|
<DropdownItem onClick={this._handleSaveAsFile}>
|
||||||
|
<i className="fa fa-save"/> Save as File
|
||||||
|
</DropdownItem>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
{selectedPart ? (
|
||||||
|
<div className="tall wide">
|
||||||
|
<ResponseViewer
|
||||||
|
key={`${responseId}::${activePart}`}
|
||||||
|
getBody={this._getBody}
|
||||||
|
responseId={`${responseId}[${activePart}]`}
|
||||||
|
previewMode={PREVIEW_MODE_FRIENDLY}
|
||||||
|
filter={filter}
|
||||||
|
filterHistory={filterHistory}
|
||||||
|
editorFontSize={editorFontSize}
|
||||||
|
editorIndentSize={editorIndentSize}
|
||||||
|
editorKeyMap={editorKeyMap}
|
||||||
|
editorLineWrapping={editorLineWrapping}
|
||||||
|
url={url}
|
||||||
|
bytes={selectedPart.bytes || 0}
|
||||||
|
contentType={getContentTypeFromHeaders(selectedPart.headers, 'text/plain')}
|
||||||
|
updateFilter={null}
|
||||||
|
error={null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ResponseMultipart;
|
@ -6,6 +6,7 @@ import {shell} from 'electron';
|
|||||||
import PDFViewer from '../pdf-viewer';
|
import PDFViewer from '../pdf-viewer';
|
||||||
import CodeEditor from '../codemirror/code-editor';
|
import CodeEditor from '../codemirror/code-editor';
|
||||||
import ResponseWebView from './response-webview';
|
import ResponseWebView from './response-webview';
|
||||||
|
import MultipartViewer from './response-multipart';
|
||||||
import ResponseRaw from './response-raw';
|
import ResponseRaw from './response-raw';
|
||||||
import ResponseError from './response-error';
|
import ResponseError from './response-error';
|
||||||
import {LARGE_RESPONSE_MB, PREVIEW_MODE_FRIENDLY, PREVIEW_MODE_RAW} from '../../../common/constants';
|
import {LARGE_RESPONSE_MB, PREVIEW_MODE_FRIENDLY, PREVIEW_MODE_RAW} from '../../../common/constants';
|
||||||
@ -27,8 +28,8 @@ type Props = {
|
|||||||
contentType: string,
|
contentType: string,
|
||||||
|
|
||||||
// Optional
|
// Optional
|
||||||
updateFilter?: Function,
|
updateFilter: Function | null,
|
||||||
error?: string
|
error: string | null
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
@ -244,6 +245,21 @@ class ResponseViewer extends React.Component<Props, State> {
|
|||||||
<PDFViewer body={bodyBuffer} uniqueKey={responseId}/>
|
<PDFViewer body={bodyBuffer} uniqueKey={responseId}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
} else if (previewMode === PREVIEW_MODE_FRIENDLY && ct.indexOf('multipart/') === 0) {
|
||||||
|
return (
|
||||||
|
<MultipartViewer
|
||||||
|
responseId={responseId}
|
||||||
|
bodyBuffer={bodyBuffer}
|
||||||
|
contentType={contentType}
|
||||||
|
filter={filter}
|
||||||
|
filterHistory={filterHistory}
|
||||||
|
editorFontSize={editorFontSize}
|
||||||
|
editorIndentSize={editorIndentSize}
|
||||||
|
editorKeyMap={editorKeyMap}
|
||||||
|
editorLineWrapping={editorLineWrapping}
|
||||||
|
url={url}
|
||||||
|
/>
|
||||||
|
);
|
||||||
} else if (previewMode === PREVIEW_MODE_FRIENDLY && ct.indexOf('audio/') === 0) {
|
} else if (previewMode === PREVIEW_MODE_FRIENDLY && ct.indexOf('audio/') === 0) {
|
||||||
const justContentType = contentType.split(';')[0];
|
const justContentType = contentType.split(';')[0];
|
||||||
const base64Body = bodyBuffer.toString('base64');
|
const base64Body = bodyBuffer.toString('base64');
|
||||||
|
@ -11,6 +11,7 @@ import autobind from 'autobind-decorator';
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import {registerModal, showModal} from './modals/index';
|
import {registerModal, showModal} from './modals/index';
|
||||||
import AlertModal from './modals/alert-modal';
|
import AlertModal from './modals/alert-modal';
|
||||||
|
import WrapperModal from './modals/wrapper-modal';
|
||||||
import ErrorModal from './modals/error-modal';
|
import ErrorModal from './modals/error-modal';
|
||||||
import ChangelogModal from './modals/changelog-modal';
|
import ChangelogModal from './modals/changelog-modal';
|
||||||
import CookiesModal from './modals/cookies-modal';
|
import CookiesModal from './modals/cookies-modal';
|
||||||
@ -405,6 +406,7 @@ class Wrapper extends React.PureComponent<Props, State> {
|
|||||||
<ErrorModal ref={registerModal}/>
|
<ErrorModal ref={registerModal}/>
|
||||||
<PromptModal ref={registerModal}/>
|
<PromptModal ref={registerModal}/>
|
||||||
|
|
||||||
|
<WrapperModal ref={registerModal}/>
|
||||||
<ChangelogModal ref={registerModal}/>
|
<ChangelogModal ref={registerModal}/>
|
||||||
<LoginModal ref={registerModal}/>
|
<LoginModal ref={registerModal}/>
|
||||||
<AskModal ref={registerModal}/>
|
<AskModal ref={registerModal}/>
|
||||||
|
@ -78,6 +78,7 @@
|
|||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
line-height: normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
3
flow-typed/moment.js
vendored
3
flow-typed/moment.js
vendored
@ -1,7 +1,8 @@
|
|||||||
declare type moment = {
|
declare type moment = {
|
||||||
fromNow: () => string;
|
fromNow: () => string;
|
||||||
|
format: (fmt: string) => string;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare module 'moment' {
|
declare module 'moment' {
|
||||||
declare module.exports: (date: any) => moment
|
declare module.exports: (date?: any) => moment
|
||||||
}
|
}
|
||||||
|
3
flow-typed/multiparty.js
vendored
Normal file
3
flow-typed/multiparty.js
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
declare module 'multiparty' {
|
||||||
|
declare module.exports: *
|
||||||
|
}
|
12
package-lock.json
generated
12
package-lock.json
generated
@ -4027,7 +4027,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
|
||||||
"integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
|
"integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"pend": "1.2.0"
|
"pend": "1.2.0"
|
||||||
}
|
}
|
||||||
@ -7334,6 +7333,14 @@
|
|||||||
"integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=",
|
"integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"multiparty": {
|
||||||
|
"version": "4.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/multiparty/-/multiparty-4.1.3.tgz",
|
||||||
|
"integrity": "sha1-PEPH/LGJbhdGBDap3Qtu8WaOT5Q=",
|
||||||
|
"requires": {
|
||||||
|
"fd-slicer": "1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"mute-stream": {
|
"mute-stream": {
|
||||||
"version": "0.0.5",
|
"version": "0.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz",
|
||||||
@ -8146,8 +8153,7 @@
|
|||||||
"pend": {
|
"pend": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||||
"integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=",
|
"integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"performance-now": {
|
"performance-now": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
|
@ -148,6 +148,7 @@
|
|||||||
"mime-types": "^2.1.14",
|
"mime-types": "^2.1.14",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"moment": "^2.18.1",
|
"moment": "^2.18.1",
|
||||||
|
"multiparty": "^4.1.3",
|
||||||
"nedb": "^1.8.0",
|
"nedb": "^1.8.0",
|
||||||
"node-forge": "^0.7.0",
|
"node-forge": "^0.7.0",
|
||||||
"nunjucks": "^3.0.0",
|
"nunjucks": "^3.0.0",
|
||||||
|
Loading…
Reference in New Issue
Block a user