From 8168610d8da6700aae62bda026fb01b261622565 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Thu, 29 Jun 2017 11:27:29 -0700 Subject: [PATCH] More prominent copy as curl (Closes #336) --- app/ui/components/base/copy-button.js | 15 ++++++++++++--- app/ui/components/base/dropdown/dropdown.js | 11 +++++++---- .../dropdowns/request-actions-dropdown.js | 13 +++++++++++++ app/ui/components/modals/request-create-modal.js | 3 +++ app/ui/components/sidebar/sidebar-children.js | 3 +++ .../sidebar/sidebar-request-group-row.js | 1 + app/ui/components/sidebar/sidebar-request-row.js | 3 +++ app/ui/components/sidebar/sidebar.js | 3 +++ app/ui/components/wrapper.js | 3 +++ app/ui/containers/app.js | 13 +++++++++++++ 10 files changed, 61 insertions(+), 7 deletions(-) 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}