diff --git a/packages/insomnia-app/app/ui/components/dropdowns/request-group-actions-dropdown.js b/packages/insomnia-app/app/ui/components/dropdowns/request-group-actions-dropdown.js index 8fea8a56e..9b655a654 100644 --- a/packages/insomnia-app/app/ui/components/dropdowns/request-group-actions-dropdown.js +++ b/packages/insomnia-app/app/ui/components/dropdowns/request-group-actions-dropdown.js @@ -38,6 +38,11 @@ class RequestGroupActionsDropdown extends PureComponent { trackEvent('Folder', 'Duplicate', 'Folder Action'); } + _handleRequestGroupMove () { + this.props.handleMoveRequestGroup(this.props.requestGroup); + trackEvent('Folder', 'Move', 'Folder Action'); + } + async _handleRequestGroupCreate () { this.props.handleCreateRequestGroup(this.props.requestGroup._id); trackEvent('Folder', 'Create', 'Folder Action'); @@ -85,6 +90,9 @@ class RequestGroupActionsDropdown extends PureComponent { Environment + + Move + Delete @@ -98,6 +106,7 @@ RequestGroupActionsDropdown.propTypes = { handleCreateRequest: PropTypes.func.isRequired, handleCreateRequestGroup: PropTypes.func.isRequired, handleDuplicateRequestGroup: PropTypes.func.isRequired, + handleMoveRequestGroup: PropTypes.func.isRequired, // Optional requestGroup: PropTypes.object diff --git a/packages/insomnia-app/app/ui/components/modals/move-request-group-modal.js b/packages/insomnia-app/app/ui/components/modals/move-request-group-modal.js new file mode 100644 index 000000000..567c0e036 --- /dev/null +++ b/packages/insomnia-app/app/ui/components/modals/move-request-group-modal.js @@ -0,0 +1,109 @@ +// @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'; +import ModalFooter from '../base/modal-footer'; +import type {RequestGroup} from '../../../models/request-group'; +import type {Workspace} from '../../../models/workspace'; +import * as models from '../../../models'; +import HelpTooltip from '../help-tooltip'; + +type Props = { + workspaces: Array +}; + +type State = { + requestGroup: RequestGroup | null, + selectedWorkspaceId: string | null +}; + +@autobind +class MoveRequestGroupModal extends React.PureComponent { + modal: ?Modal; + + constructor (props: Props) { + super(props); + + this.state = { + requestGroup: null, + selectedWorkspaceId: null + }; + } + + _setModalRef (n: ?Modal) { + this.modal = n; + } + + async _handleSubmit (e: SyntheticEvent) { + e.preventDefault(); + + const {requestGroup, selectedWorkspaceId} = this.state; + if (!requestGroup) { + return; + } + + const workspace = await models.workspace.getById(selectedWorkspaceId || 'n/a'); + if (!workspace) { + return; + } + + const newRequestGroup = await models.requestGroup.duplicate(requestGroup); + await models.requestGroup.update(newRequestGroup, { + sortKey: -1E9, + parentId: selectedWorkspaceId, + name: requestGroup.name // Because duplicating will add (Copy) suffix + }); + this.hide(); + } + + _handleChangeSelectedWorkspace (e: SyntheticEvent) { + const selectedWorkspaceId = e.currentTarget.value; + this.setState({selectedWorkspaceId}); + } + + show (options: {requestGroup: RequestGroup}) { + const {requestGroup} = options; + this.setState({requestGroup}); + this.modal && this.modal.show(); + } + + hide () { + this.modal && this.modal.hide(); + } + + render () { + const {workspaces} = this.props; + const {selectedWorkspaceId} = this.state; + return ( +
+ + Move Folder to Workspace + +
+ +
+
+ + + +
+
+ ); + } +} + +MoveRequestGroupModal.propTypes = {}; + +export default MoveRequestGroupModal; diff --git a/packages/insomnia-app/app/ui/components/modals/request-settings-modal.js b/packages/insomnia-app/app/ui/components/modals/request-settings-modal.js index 16112cd18..a5e8c25cf 100644 --- a/packages/insomnia-app/app/ui/components/modals/request-settings-modal.js +++ b/packages/insomnia-app/app/ui/components/modals/request-settings-modal.js @@ -1,5 +1,5 @@ -import React, {PureComponent} from 'react'; -import PropTypes from 'prop-types'; +// @flow +import * as React from 'react'; import autobind from 'autobind-decorator'; import Modal from '../base/modal'; import ModalBody from '../base/modal-body'; @@ -9,40 +9,83 @@ import * as models from '../../../models'; import {trackEvent} from '../../../common/analytics'; import DebouncedInput from '../base/debounced-input'; import MarkdownEditor from '../markdown-editor'; +import * as db from '../../../common/database'; +import type {Workspace} from '../../../models/workspace'; +import type {Request} from '../../../models/request'; + +type Props = { + editorFontSize: number, + editorIndentSize: number, + editorKeyMap: string, + editorLineWrapping: boolean, + nunjucksPowerUserMode: boolean, + handleRender: Function, + handleGetRenderContext: Function, + workspaces: Array +}; + +type State = { + request: Request | null, + showDescription: boolean, + defaultPreviewMode: boolean, + activeWorkspaceIdToCopyTo: string | null, + workspace: Workspace | null, + justCopied: boolean, + justMoved: boolean +}; @autobind -class RequestSettingsModal extends PureComponent { - constructor (props) { +class RequestSettingsModal extends React.PureComponent { + modal: ?Modal; + _editor: ?MarkdownEditor; + + constructor (props: Props) { super(props); this.state = { request: null, showDescription: false, - defaultPreviewMode: false + defaultPreviewMode: false, + activeWorkspaceIdToCopyTo: null, + workspace: null, + workspaces: [], + justCopied: false, + justMoved: false }; } - _setModalRef (n) { + _setModalRef (n: ?Modal) { this.modal = n; } - _setEditorRef (n) { + _setEditorRef (n: ?MarkdownEditor) { this._editor = n; } - async _updateRequestSettingBoolean (e) { - const value = e.target.checked; - const setting = e.target.name; + async _updateRequestSettingBoolean (e: SyntheticEvent) { + if (!this.state.request) { + // Should never happen + return; + } + + const value = e.currentTarget.checked; + const setting = e.currentTarget.name; const request = await models.request.update(this.state.request, {[setting]: value}); this.setState({request}); trackEvent('Request Settings', setting, value ? 'Enable' : 'Disable'); } - async _handleNameChange (name) { + async _handleNameChange (name: string) { + if (!this.state.request) { + return; + } const request = await models.request.update(this.state.request, {name}); this.setState({request}); } - async _handleDescriptionChange (description) { + async _handleDescriptionChange (description: string) { + if (!this.state.request) { + return; + } const request = await models.request.update(this.state.request, {description}); this.setState({request, defaultPreviewMode: false}); } @@ -52,37 +95,108 @@ class RequestSettingsModal extends PureComponent { this.setState({showDescription: true}); } - show ({request, forceEditMode}) { - const hasDescription = !!request.description; - this.setState({ - request, - showDescription: forceEditMode || hasDescription, - defaultPreviewMode: hasDescription && !forceEditMode + _handleUpdateMoveCopyWorkspace (e: SyntheticEvent) { + const workspaceId = e.currentTarget.value; + this.setState({activeWorkspaceIdToCopyTo: workspaceId}); + } + + async _handleMoveToWorkspace () { + const {activeWorkspaceIdToCopyTo, request} = this.state; + if (!request) { + return; + } + + const workspace = await models.workspace.getById(activeWorkspaceIdToCopyTo || 'n/a'); + if (!workspace) { + return; + } + + await models.request.update(request, { + sortKey: -1E9, // Move to top of sort order + parentId: activeWorkspaceIdToCopyTo }); - this.modal.show(); + this.setState({justMoved: true}); + setTimeout(() => { + this.setState({justMoved: false}); + }, 2000); + trackEvent('Request Settings', 'Move to Workspace'); + } - if (forceEditMode) { - setTimeout(() => this._editor.focus(), 400); + async _handleCopyToWorkspace () { + const {activeWorkspaceIdToCopyTo, request} = this.state; + if (!request) { + return; } + + const workspace = await models.workspace.getById(activeWorkspaceIdToCopyTo || 'n/a'); + if (!workspace) { + return; + } + + const newRequest = await models.request.duplicate(request); + await models.request.update(newRequest, { + sortKey: -1E9, // Move to top of sort order + name: request.name, // Because duplicate will add (Copy) suffix + parentId: activeWorkspaceIdToCopyTo + }); + + this.setState({justCopied: true}); + setTimeout(() => { + this.setState({justCopied: false}); + }, 2000); + trackEvent('Request Settings', 'Copy to Workspace'); + } + + async show ({request, forceEditMode}: {request: Request, forceEditMode: boolean}) { + const {workspaces} = this.props; + + const hasDescription = !!request.description; + + // Find workspaces for use with moving workspace + const ancestors = await db.withAncestors(request); + const doc = ancestors.find(doc => doc.type === models.workspace.type); + const workspaceId = doc ? doc._id : 'should-never-happen'; + const workspace = workspaces.find(w => w._id === workspaceId); + + this.setState({ + request, + workspace: workspace, + activeWorkspaceIdToCopyTo: workspace ? workspace._id : 'n/a', + showDescription: forceEditMode || hasDescription, + defaultPreviewMode: hasDescription && !forceEditMode + }, () => { + this.modal && this.modal.show(); + + if (forceEditMode) { + setTimeout(() => { + this._editor && this._editor.focus(); + }, 400); + } + }); } hide () { - this.modal.hide(); + this.modal && this.modal.hide(); } - renderCheckboxInput (setting) { + renderCheckboxInput (setting: string) { + const {request} = this.state; + if (!request) { + return; + } + return ( ); } - renderModalBody (request) { + renderModalBody (request: Request) { const { editorLineWrapping, editorFontSize, @@ -90,10 +204,18 @@ class RequestSettingsModal extends PureComponent { editorKeyMap, handleRender, handleGetRenderContext, - nunjucksPowerUserMode + nunjucksPowerUserMode, + workspaces } = this.props; - const {showDescription, defaultPreviewMode} = this.state; + const { + showDescription, + defaultPreviewMode, + activeWorkspaceIdToCopyTo, + justMoved, + justCopied, + workspace + } = this.state; return (
@@ -172,6 +294,40 @@ class RequestSettingsModal extends PureComponent { {this.renderCheckboxInput('settingRebuildPath')}
+
+
+
+ +
+
+ +
+
+ +
+
); @@ -194,14 +350,4 @@ class RequestSettingsModal extends PureComponent { } } -RequestSettingsModal.propTypes = { - editorFontSize: PropTypes.number.isRequired, - editorIndentSize: PropTypes.number.isRequired, - editorKeyMap: PropTypes.string.isRequired, - editorLineWrapping: PropTypes.bool.isRequired, - nunjucksPowerUserMode: PropTypes.bool.isRequired, - handleRender: PropTypes.func.isRequired, - handleGetRenderContext: PropTypes.func.isRequired -}; - export default RequestSettingsModal; diff --git a/packages/insomnia-app/app/ui/components/sidebar/sidebar-children.js b/packages/insomnia-app/app/ui/components/sidebar/sidebar-children.js index c25cc14f1..12ee7981b 100644 --- a/packages/insomnia-app/app/ui/components/sidebar/sidebar-children.js +++ b/packages/insomnia-app/app/ui/components/sidebar/sidebar-children.js @@ -21,6 +21,7 @@ type Props = { handleSetRequestGroupCollapsed: Function, handleDuplicateRequest: Function, handleDuplicateRequestGroup: Function, + handleMoveRequestGroup: Function, handleGenerateCode: Function, handleCopyAsCurl: Function, moveDoc: Function, @@ -39,6 +40,7 @@ class SidebarChildren extends React.PureComponent { handleSetRequestGroupCollapsed, handleDuplicateRequest, handleDuplicateRequestGroup, + handleMoveRequestGroup, handleGenerateCode, handleCopyAsCurl, moveDoc, @@ -93,12 +95,13 @@ class SidebarChildren extends React.PureComponent { return ( { handleCreateRequestGroup, handleDuplicateRequest, handleDuplicateRequestGroup, + handleMoveRequestGroup, handleExportFile, handleMoveDoc, handleResetDragPaneHorizontal, @@ -420,6 +423,7 @@ class Wrapper extends React.PureComponent { handleRender={handleRender} handleGetRenderContext={handleGetRenderContext} nunjucksPowerUserMode={settings.nunjucksPowerUserMode} + workspaces={workspaces} /> { workspace={activeWorkspace} /> + + { handleRemoveWorkspace={this._handleRemoveActiveWorkspace} handleDuplicateWorkspace={handleDuplicateWorkspace} /> + + { editorIndentSize={settings.editorIndentSize} editorKeyMap={settings.editorKeyMap} /> + { handleToggleMenuBar={handleToggleMenuBar} settings={settings} /> + { activateRequest={handleActivateRequest} handleSetActiveWorkspace={handleSetActiveWorkspace} /> + { getRenderContext={handleGetRenderContext} nunjucksPowerUserMode={settings.nunjucksPowerUserMode} /> + + { handleGenerateCode={handleGenerateCode} handleCopyAsCurl={handleCopyAsCurl} handleDuplicateRequestGroup={handleDuplicateRequestGroup} + handleMoveRequestGroup={handleMoveRequestGroup} handleSetActiveEnvironment={handleSetActiveEnvironment} moveDoc={handleMoveDoc} handleSetRequestGroupCollapsed={handleSetRequestGroupCollapsed} diff --git a/packages/insomnia-app/app/ui/containers/app.js b/packages/insomnia-app/app/ui/containers/app.js index b206376ad..d003d227b 100644 --- a/packages/insomnia-app/app/ui/containers/app.js +++ b/packages/insomnia-app/app/ui/containers/app.js @@ -40,6 +40,7 @@ import * as plugins from '../../plugins'; import * as templating from '../../templating/index'; import AskModal from '../components/modals/ask-modal'; import {updateMimeType} from '../../models/request'; +import MoveRequestGroupModal from '../components/modals/move-request-group-modal'; @autobind class App extends PureComponent { @@ -183,6 +184,10 @@ class App extends PureComponent { models.requestGroup.duplicate(requestGroup); } + async _requestGroupMove (requestGroup) { + showModal(MoveRequestGroupModal, {requestGroup}); + } + async _requestDuplicate (request) { if (!request) { return; @@ -870,6 +875,7 @@ class App extends PureComponent { handleGetRenderContext={this._handleGetRenderContext} handleDuplicateRequest={this._requestDuplicate} handleDuplicateRequestGroup={this._requestGroupDuplicate} + handleMoveRequestGroup={this._requestGroupMove} handleDuplicateWorkspace={this._workspaceDuplicate} handleCreateRequestGroup={this._requestGroupCreate} handleGenerateCode={this._handleGenerateCode}