Move folders and requests to new workspaces (#837)

This commit is contained in:
Gregory Schier 2018-03-29 09:57:24 -04:00 committed by GitHub
parent 6df9aa15c2
commit c8172bf5b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 334 additions and 38 deletions

View File

@ -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 {
<DropdownItem onClick={this._handleEditEnvironment}>
<i className="fa fa-code"/> Environment
</DropdownItem>
<DropdownItem onClick={this._handleRequestGroupMove}>
<i className="fa fa-exchange"/> Move
</DropdownItem>
<DropdownItem buttonClass={PromptButton} addIcon onClick={this._handleDeleteFolder}>
<i className="fa fa-trash-o"/> Delete
</DropdownItem>
@ -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

View File

@ -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<Workspace>
};
type State = {
requestGroup: RequestGroup | null,
selectedWorkspaceId: string | null
};
@autobind
class MoveRequestGroupModal extends React.PureComponent<Props, State> {
modal: ?Modal;
constructor (props: Props) {
super(props);
this.state = {
requestGroup: null,
selectedWorkspaceId: null
};
}
_setModalRef (n: ?Modal) {
this.modal = n;
}
async _handleSubmit (e: SyntheticEvent<HTMLFormElement>) {
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<HTMLSelectElement>) {
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 (
<form onSubmit={this._handleSubmit}>
<Modal ref={this._setModalRef} {...this.props}>
<ModalHeader key="header">Move Folder to Workspace</ModalHeader>
<ModalBody key="body" className="pad">
<div className="form-control form-control--outlined">
<label>New Workspace&nbsp;
<HelpTooltip>
Workspace will be moved to the root of the new workspace
</HelpTooltip>
<select onChange={this._handleChangeSelectedWorkspace} value={selectedWorkspaceId}>
<option value="n/a">-- Select Workspace --</option>
{workspaces.map(w => (
<option key={w._id} value={w._id}>{w.name}</option>
))}
</select>
</label>
</div>
</ModalBody>
<ModalFooter key="footer">
<button type="submit" className="btn">Move</button>
</ModalFooter>
</Modal>
</form>
);
}
}
MoveRequestGroupModal.propTypes = {};
export default MoveRequestGroupModal;

View File

@ -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<Workspace>
};
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<Props, State> {
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<HTMLInputElement>) {
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<HTMLSelectElement>) {
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 (
<input
type="checkbox"
name={setting}
checked={this.state.request[setting]}
checked={request[setting]}
onChange={this._updateRequestSettingBoolean}
/>
);
}
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 (
<div>
@ -172,6 +294,40 @@ class RequestSettingsModal extends PureComponent {
{this.renderCheckboxInput('settingRebuildPath')}
</label>
</div>
<hr/>
<div className="form-row">
<div className="form-control form-control--outlined">
<label>Move/Copy to Workspace
<HelpTooltip position="top" className="space-left">
Copy or move the current request to a new workspace. It will be placed at the
root of the new workspace's folder structure.
</HelpTooltip>
<select value={activeWorkspaceIdToCopyTo}
onChange={this._handleUpdateMoveCopyWorkspace}>
<option value="n/a">-- Select Workspace --</option>
{workspaces.map(w => {
if (workspace && workspace._id === w._id) {
return null;
}
return (
<option key={w._id} value={w._id}>{w.name}</option>
);
})}
</select>
</label>
</div>
<div className="form-control form-control--no-label width-auto">
<button disabled={justCopied} className="btn btn--clicky" onClick={this._handleCopyToWorkspace}>
{justCopied ? 'Copied!' : 'Copy'}
</button>
</div>
<div className="form-control form-control--no-label width-auto">
<button disabled={justMoved} className="btn btn--clicky" onClick={this._handleMoveToWorkspace}>
{justMoved ? 'Moved!' : 'Move'}
</button>
</div>
</div>
</div>
</div>
);
@ -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;

View File

@ -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<Props> {
handleSetRequestGroupCollapsed,
handleDuplicateRequest,
handleDuplicateRequestGroup,
handleMoveRequestGroup,
handleGenerateCode,
handleCopyAsCurl,
moveDoc,
@ -93,12 +95,13 @@ class SidebarChildren extends React.PureComponent<Props> {
return (
<SidebarRequestGroupRow
handleActivateRequest={handleActivateRequest}
key={requestGroup._id}
isActive={isActive}
moveDoc={moveDoc}
handleActivateRequest={handleActivateRequest}
handleSetRequestGroupCollapsed={handleSetRequestGroupCollapsed}
handleDuplicateRequestGroup={handleDuplicateRequestGroup}
handleMoveRequestGroup={handleMoveRequestGroup}
isCollapsed={child.collapsed}
handleCreateRequest={handleCreateRequest}
handleCreateRequestGroup={handleCreateRequestGroup}

View File

@ -51,6 +51,7 @@ class SidebarRequestGroupRow extends PureComponent {
handleCreateRequest,
handleCreateRequestGroup,
handleDuplicateRequestGroup,
handleMoveRequestGroup,
isDragging,
isDraggingOver,
workspace
@ -91,6 +92,7 @@ class SidebarRequestGroupRow extends PureComponent {
handleCreateRequest={handleCreateRequest}
handleCreateRequestGroup={handleCreateRequestGroup}
handleDuplicateRequestGroup={handleDuplicateRequestGroup}
handleMoveRequestGroup={handleMoveRequestGroup}
workspace={workspace}
requestGroup={requestGroup}
right
@ -123,6 +125,7 @@ SidebarRequestGroupRow.propTypes = {
// Functions
handleSetRequestGroupCollapsed: PropTypes.func.isRequired,
handleDuplicateRequestGroup: PropTypes.func.isRequired,
handleMoveRequestGroup: PropTypes.func.isRequired,
moveDoc: PropTypes.func.isRequired,
handleActivateRequest: PropTypes.func.isRequired,
handleCreateRequest: PropTypes.func.isRequired,

View File

@ -46,6 +46,7 @@ class Sidebar extends PureComponent {
handleCreateRequest,
handleDuplicateRequest,
handleDuplicateRequestGroup,
handleMoveRequestGroup,
handleGenerateCode,
handleCopyAsCurl,
handleCreateRequestGroup,
@ -102,6 +103,7 @@ class Sidebar extends PureComponent {
handleSetRequestGroupCollapsed={handleSetRequestGroupCollapsed}
handleDuplicateRequest={handleDuplicateRequest}
handleDuplicateRequestGroup={handleDuplicateRequestGroup}
handleMoveRequestGroup={handleMoveRequestGroup}
handleGenerateCode={handleGenerateCode}
handleCopyAsCurl={handleCopyAsCurl}
moveDoc={moveDoc}
@ -133,6 +135,7 @@ Sidebar.propTypes = {
handleCreateRequestGroup: PropTypes.func.isRequired,
handleDuplicateRequest: PropTypes.func.isRequired,
handleDuplicateRequestGroup: PropTypes.func.isRequired,
handleMoveRequestGroup: PropTypes.func.isRequired,
handleGenerateCode: PropTypes.func.isRequired,
handleCopyAsCurl: PropTypes.func.isRequired,
showEnvironmentsModal: PropTypes.func.isRequired,

View File

@ -43,6 +43,7 @@ import type {CookieJar} from '../../models/cookie-jar';
import type {Environment} from '../../models/environment';
import ErrorBoundary from './error-boundary';
import type {ClientCertificate} from '../../models/client-certificate';
import MoveRequestGroupModal from './modals/move-request-group-modal';
type Props = {
// Helper Functions
@ -58,6 +59,7 @@ type Props = {
handleCreateRequest: Function,
handleDuplicateRequest: Function,
handleDuplicateRequestGroup: Function,
handleMoveRequestGroup: Function,
handleDuplicateWorkspace: Function,
handleCreateRequestGroup: Function,
handleGenerateCodeForActiveRequest: Function,
@ -339,6 +341,7 @@ class Wrapper extends React.PureComponent<Props, State> {
handleCreateRequestGroup,
handleDuplicateRequest,
handleDuplicateRequestGroup,
handleMoveRequestGroup,
handleExportFile,
handleMoveDoc,
handleResetDragPaneHorizontal,
@ -420,6 +423,7 @@ class Wrapper extends React.PureComponent<Props, State> {
handleRender={handleRender}
handleGetRenderContext={handleGetRenderContext}
nunjucksPowerUserMode={settings.nunjucksPowerUserMode}
workspaces={workspaces}
/>
<CookiesModal
@ -448,6 +452,11 @@ class Wrapper extends React.PureComponent<Props, State> {
workspace={activeWorkspace}
/>
<MoveRequestGroupModal
ref={registerModal}
workspaces={workspaces}
/>
<WorkspaceSettingsModal
ref={registerModal}
clientCertificates={activeWorkspaceClientCertificates}
@ -462,10 +471,12 @@ class Wrapper extends React.PureComponent<Props, State> {
handleRemoveWorkspace={this._handleRemoveActiveWorkspace}
handleDuplicateWorkspace={handleDuplicateWorkspace}
/>
<WorkspaceShareSettingsModal
ref={registerModal}
workspace={activeWorkspace}
/>
<GenerateCodeModal
ref={registerModal}
environmentId={activeEnvironment ? activeEnvironment._id : 'n/a'}
@ -473,6 +484,7 @@ class Wrapper extends React.PureComponent<Props, State> {
editorIndentSize={settings.editorIndentSize}
editorKeyMap={settings.editorKeyMap}
/>
<SettingsModal
ref={registerModal}
handleExportWorkspaceToFile={this._handleExportWorkspaceToFile}
@ -482,6 +494,7 @@ class Wrapper extends React.PureComponent<Props, State> {
handleToggleMenuBar={handleToggleMenuBar}
settings={settings}
/>
<RequestSwitcherModal
ref={registerModal}
workspaces={workspaces}
@ -491,6 +504,7 @@ class Wrapper extends React.PureComponent<Props, State> {
activateRequest={handleActivateRequest}
handleSetActiveWorkspace={handleSetActiveWorkspace}
/>
<EnvironmentEditModal
ref={registerModal}
editorFontSize={settings.editorFontSize}
@ -502,10 +516,12 @@ class Wrapper extends React.PureComponent<Props, State> {
getRenderContext={handleGetRenderContext}
nunjucksPowerUserMode={settings.nunjucksPowerUserMode}
/>
<SetupSyncModal
ref={registerModal}
workspace={activeWorkspace}
/>
<WorkspaceEnvironmentsEditModal
ref={registerModal}
onChange={models.workspace.update}
@ -539,6 +555,7 @@ class Wrapper extends React.PureComponent<Props, State> {
handleGenerateCode={handleGenerateCode}
handleCopyAsCurl={handleCopyAsCurl}
handleDuplicateRequestGroup={handleDuplicateRequestGroup}
handleMoveRequestGroup={handleMoveRequestGroup}
handleSetActiveEnvironment={handleSetActiveEnvironment}
moveDoc={handleMoveDoc}
handleSetRequestGroupCollapsed={handleSetRequestGroupCollapsed}

View File

@ -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}