diff --git a/app/ui/components/base/copy-button.js b/app/ui/components/base/copy-button.js
index d5386777b6..538f1763d6 100644
--- a/app/ui/components/base/copy-button.js
+++ b/app/ui/components/base/copy-button.js
@@ -11,11 +11,17 @@ class CopyButton extends PureComponent {
};
}
- _handleClick (e) {
+ async _handleClick (e) {
e.preventDefault();
e.stopPropagation();
- clipboard.writeText(this.props.content);
+ const content = typeof this.props.content === 'string'
+ ? this.props.content
+ : await this.props.content();
+
+ if (content) {
+ clipboard.writeText(content);
+ }
this.setState({showConfirmation: true});
@@ -53,7 +59,10 @@ class CopyButton extends PureComponent {
CopyButton.propTypes = {
// Required
- content: PropTypes.string.isRequired,
+ content: PropTypes.oneOfType([
+ PropTypes.string.isRequired,
+ PropTypes.func.isRequired
+ ]).isRequired,
// Optional
children: PropTypes.node,
diff --git a/app/ui/components/base/dropdown/dropdown.js b/app/ui/components/base/dropdown/dropdown.js
index e53be0dc8c..296846e1d1 100644
--- a/app/ui/components/base/dropdown/dropdown.js
+++ b/app/ui/components/base/dropdown/dropdown.js
@@ -14,7 +14,10 @@ class Dropdown extends PureComponent {
this.state = {
open: false,
dropUp: false,
- focused: false
+ focused: false,
+
+ // Use this to force new menu every time dropdown opens
+ uniquenessKey: 0
};
}
@@ -158,7 +161,7 @@ class Dropdown extends PureComponent {
const dropdownTop = this._node.getBoundingClientRect().top;
const dropUp = dropdownTop > bodyHeight - 200;
- this.setState({open: true, dropUp});
+ this.setState({open: true, dropUp, uniquenessKey: this.state.uniquenessKey + 1});
this.props.onOpen && this.props.onOpen();
}
@@ -172,7 +175,7 @@ class Dropdown extends PureComponent {
render () {
const {right, outline, wide, className, style, children} = this.props;
- const {dropUp, open} = this.state;
+ const {dropUp, open, uniquenessKey} = this.state;
const classes = classnames('dropdown', className, {
'dropdown--wide': wide,
@@ -214,7 +217,7 @@ class Dropdown extends PureComponent {
dropdownButtons[0],
diff --git a/app/ui/components/dropdowns/request-actions-dropdown.js b/app/ui/components/dropdowns/request-actions-dropdown.js
index 2bee836139..0f8692f7cd 100644
--- a/app/ui/components/dropdowns/request-actions-dropdown.js
+++ b/app/ui/components/dropdowns/request-actions-dropdown.js
@@ -5,6 +5,7 @@ import {Dropdown, DropdownButton, DropdownHint, DropdownItem} from '../base/drop
import * as models from '../../../models';
import {trackEvent} from '../../../analytics/index';
import {DropdownDivider} from '../base/dropdown/index';
+import CopyButton from '../base/copy-button';
@autobind
class RequestActionsDropdown extends PureComponent {
@@ -23,6 +24,11 @@ class RequestActionsDropdown extends PureComponent {
trackEvent('Request', 'Generate Code', 'Request Action');
}
+ _handleCopyAsCurl () {
+ trackEvent('Request', 'Copy As Curl', 'Request Action');
+ this.props.handleCopyAsCurl(this.props.request);
+ }
+
_handleRemove () {
const {request} = this.props;
models.request.remove(request);
@@ -52,6 +58,12 @@ class RequestActionsDropdown extends PureComponent {
Generate Code
+
+ Copy as Curl
+
Delete
@@ -70,6 +82,7 @@ class RequestActionsDropdown extends PureComponent {
RequestActionsDropdown.propTypes = {
handleDuplicateRequest: PropTypes.func.isRequired,
handleGenerateCode: PropTypes.func.isRequired,
+ handleCopyAsCurl: PropTypes.func.isRequired,
handleShowSettings: PropTypes.func.isRequired,
request: PropTypes.object.isRequired
};
diff --git a/app/ui/components/modals/request-create-modal.js b/app/ui/components/modals/request-create-modal.js
index 847718f79d..bcfb78c08a 100644
--- a/app/ui/components/modals/request-create-modal.js
+++ b/app/ui/components/modals/request-create-modal.js
@@ -133,6 +133,9 @@ class RequestCreateModal extends PureComponent {
+
+ * Tip: paste Curl command into URL afterwards to import it
+
diff --git a/app/ui/components/sidebar/sidebar-children.js b/app/ui/components/sidebar/sidebar-children.js
index 8b012548dd..440c3f1b04 100644
--- a/app/ui/components/sidebar/sidebar-children.js
+++ b/app/ui/components/sidebar/sidebar-children.js
@@ -11,6 +11,7 @@ class SidebarChildren extends PureComponent {
handleDuplicateRequest,
handleDuplicateRequestGroup,
handleGenerateCode,
+ handleCopyAsCurl,
moveDoc,
handleActivateRequest,
activeRequest,
@@ -32,6 +33,7 @@ class SidebarChildren extends PureComponent {
handleActivateRequest={handleActivateRequest}
handleDuplicateRequest={handleDuplicateRequest}
handleGenerateCode={handleGenerateCode}
+ handleCopyAsCurl={handleCopyAsCurl}
requestCreate={handleCreateRequest}
isActive={child.doc._id === activeRequestId}
request={child.doc}
@@ -100,6 +102,7 @@ SidebarChildren.propTypes = {
handleDuplicateRequest: PropTypes.func.isRequired,
handleDuplicateRequestGroup: PropTypes.func.isRequired,
handleGenerateCode: PropTypes.func.isRequired,
+ handleCopyAsCurl: PropTypes.func.isRequired,
moveDoc: PropTypes.func.isRequired,
childObjects: PropTypes.arrayOf(PropTypes.object).isRequired,
workspace: PropTypes.object.isRequired,
diff --git a/app/ui/components/sidebar/sidebar-request-group-row.js b/app/ui/components/sidebar/sidebar-request-group-row.js
index 438b493d7e..23a900a69e 100644
--- a/app/ui/components/sidebar/sidebar-request-group-row.js
+++ b/app/ui/components/sidebar/sidebar-request-group-row.js
@@ -104,6 +104,7 @@ class SidebarRequestGroupRow extends PureComponent {
handleActivateRequest={misc.nullFn}
handleDuplicateRequest={misc.nullFn}
handleGenerateCode={misc.nullFn}
+ handleCopyAsCurl={misc.nullFn}
moveDoc={moveDoc}
isActive={false}
request={null}
diff --git a/app/ui/components/sidebar/sidebar-request-row.js b/app/ui/components/sidebar/sidebar-request-row.js
index c909efc6ca..5199259fbe 100644
--- a/app/ui/components/sidebar/sidebar-request-row.js
+++ b/app/ui/components/sidebar/sidebar-request-row.js
@@ -71,6 +71,7 @@ class SidebarRequestRow extends PureComponent {
const {
handleDuplicateRequest,
handleGenerateCode,
+ handleCopyAsCurl,
connectDragSource,
connectDropTarget,
isDragging,
@@ -125,6 +126,7 @@ class SidebarRequestRow extends PureComponent {
ref={this._setRequestActionsDropdownRef}
handleDuplicateRequest={handleDuplicateRequest}
handleGenerateCode={handleGenerateCode}
+ handleCopyAsCurl={handleCopyAsCurl}
handleShowSettings={this._handleShowRequestSettings}
request={request}
requestGroup={requestGroup}
@@ -148,6 +150,7 @@ SidebarRequestRow.propTypes = {
handleActivateRequest: PropTypes.func.isRequired,
handleDuplicateRequest: PropTypes.func.isRequired,
handleGenerateCode: PropTypes.func.isRequired,
+ handleCopyAsCurl: PropTypes.func.isRequired,
requestCreate: PropTypes.func.isRequired,
moveDoc: PropTypes.func.isRequired,
diff --git a/app/ui/components/sidebar/sidebar.js b/app/ui/components/sidebar/sidebar.js
index 973f8c1896..8d474c162d 100644
--- a/app/ui/components/sidebar/sidebar.js
+++ b/app/ui/components/sidebar/sidebar.js
@@ -45,6 +45,7 @@ class Sidebar extends PureComponent {
handleDuplicateRequest,
handleDuplicateRequestGroup,
handleGenerateCode,
+ handleCopyAsCurl,
handleCreateRequestGroup,
handleSetRequestGroupCollapsed,
moveDoc,
@@ -98,6 +99,7 @@ class Sidebar extends PureComponent {
handleDuplicateRequest={handleDuplicateRequest}
handleDuplicateRequestGroup={handleDuplicateRequestGroup}
handleGenerateCode={handleGenerateCode}
+ handleCopyAsCurl={handleCopyAsCurl}
moveDoc={moveDoc}
workspace={workspace}
activeRequest={activeRequest}
@@ -128,6 +130,7 @@ Sidebar.propTypes = {
handleDuplicateRequest: PropTypes.func.isRequired,
handleDuplicateRequestGroup: PropTypes.func.isRequired,
handleGenerateCode: PropTypes.func.isRequired,
+ handleCopyAsCurl: PropTypes.func.isRequired,
showEnvironmentsModal: PropTypes.func.isRequired,
showCookiesModal: PropTypes.func.isRequired,
diff --git a/app/ui/components/wrapper.js b/app/ui/components/wrapper.js
index 059729c456..7d96fcb5aa 100644
--- a/app/ui/components/wrapper.js
+++ b/app/ui/components/wrapper.js
@@ -271,6 +271,7 @@ class Wrapper extends PureComponent {
handleDuplicateWorkspace,
handleGenerateCodeForActiveRequest,
handleGenerateCode,
+ handleCopyAsCurl,
isLoading,
loadStartTime,
paneWidth,
@@ -309,6 +310,7 @@ class Wrapper extends PureComponent {
handleSetActiveWorkspace={handleSetActiveWorkspace}
handleDuplicateRequest={handleDuplicateRequest}
handleGenerateCode={handleGenerateCode}
+ handleCopyAsCurl={handleCopyAsCurl}
handleDuplicateRequestGroup={handleDuplicateRequestGroup}
handleSetActiveEnvironment={handleSetActiveEnvironment}
moveDoc={handleMoveDoc}
@@ -529,6 +531,7 @@ Wrapper.propTypes = {
handleCreateRequestGroup: PropTypes.func.isRequired,
handleGenerateCodeForActiveRequest: PropTypes.func.isRequired,
handleGenerateCode: PropTypes.func.isRequired,
+ handleCopyAsCurl: PropTypes.func.isRequired,
handleCreateRequestForWorkspace: PropTypes.func.isRequired,
handleSetRequestPaneRef: PropTypes.func.isRequired,
handleSetResponsePaneRef: PropTypes.func.isRequired,
diff --git a/app/ui/containers/app.js b/app/ui/containers/app.js
index 0d6b885512..1b7728926b 100644
--- a/app/ui/containers/app.js
+++ b/app/ui/containers/app.js
@@ -1,8 +1,10 @@
import React, {PropTypes, PureComponent} from 'react';
import autobind from 'autobind-decorator';
import fs from 'fs';
+import {clipboard} from 'electron';
import {parse as urlParse} from 'url';
import {ipcRenderer, remote} from 'electron';
+import HTTPSnippet, {availableTargets} from 'httpsnippet';
import ReactDOM from 'react-dom';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
@@ -32,6 +34,7 @@ import * as path from 'path';
import * as render from '../../common/render';
import {getKeys} from '../../templating/utils';
import {showPrompt} from '../components/modals/index';
+import {exportHar} from '../../common/har';
const KEY_ENTER = 13;
const KEY_COMMA = 188;
@@ -298,6 +301,15 @@ class App extends PureComponent {
showModal(GenerateCodeModal, request);
}
+ async _handleCopyAsCurl (request) {
+ const {activeEnvironment} = this.props;
+ const environmentId = activeEnvironment ? activeEnvironment._id : 'n/a';
+ const har = await exportHar(request._id, environmentId);
+ const snippet = new HTTPSnippet(har);
+ const cmd = snippet.convert('shell', 'curl');
+ clipboard.writeText(cmd);
+ }
+
async _updateRequestGroupMetaByParentId (requestGroupId, patch) {
const requestGroupMeta = await models.requestGroupMeta.getByParentId(requestGroupId);
if (requestGroupMeta) {
@@ -801,6 +813,7 @@ class App extends PureComponent {
handleCreateRequestGroup={this._requestGroupCreate}
handleGenerateCode={this._handleGenerateCode}
handleGenerateCodeForActiveRequest={this._handleGenerateCodeForActiveRequest}
+ handleCopyAsCurl={this._handleCopyAsCurl}
handleSetResponsePreviewMode={this._handleSetResponsePreviewMode}
handleSetResponseFilter={this._handleSetResponseFilter}
handleSendRequestWithEnvironment={this._handleSendRequestWithEnvironment}