mirror of
https://github.com/Kong/insomnia.git
synced 2026-04-23 07:38:58 -04:00
Duplicate from dashboard and from settings (#3820)
This commit is contained in:
@@ -141,7 +141,10 @@ export const ACTIVITY_MIGRATION: GlobalActivity = 'migration';
|
||||
export const ACTIVITY_ANALYTICS: GlobalActivity = 'analytics';
|
||||
export const DEPRECATED_ACTIVITY_INSOMNIA = 'insomnia';
|
||||
|
||||
export const isWorkspaceActivity = (activity: string): activity is GlobalActivity => {
|
||||
export const isWorkspaceActivity = (activity?: string): activity is GlobalActivity =>
|
||||
isDesignActivity(activity) || isCollectionActivity(activity);
|
||||
|
||||
export const isDesignActivity = (activity?: string): activity is GlobalActivity => {
|
||||
switch (activity) {
|
||||
case ACTIVITY_SPEC:
|
||||
case ACTIVITY_DEBUG:
|
||||
@@ -157,6 +160,22 @@ export const isWorkspaceActivity = (activity: string): activity is GlobalActivit
|
||||
}
|
||||
};
|
||||
|
||||
export const isCollectionActivity = (activity?: string): activity is GlobalActivity => {
|
||||
switch (activity) {
|
||||
case ACTIVITY_DEBUG:
|
||||
return true;
|
||||
|
||||
case ACTIVITY_SPEC:
|
||||
case ACTIVITY_UNIT_TEST:
|
||||
case ACTIVITY_HOME:
|
||||
case ACTIVITY_ONBOARDING:
|
||||
case ACTIVITY_MIGRATION:
|
||||
case ACTIVITY_ANALYTICS:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const isValidActivity = (activity: string): activity is GlobalActivity => {
|
||||
switch (activity) {
|
||||
case ACTIVITY_SPEC:
|
||||
|
||||
@@ -9,6 +9,9 @@ type StringId =
|
||||
| 'home'
|
||||
| 'space'
|
||||
| 'workspace'
|
||||
| 'baseSpace'
|
||||
| 'localSpace'
|
||||
| 'remoteSpace'
|
||||
;
|
||||
|
||||
export const strings: Record<StringId, StringInfo> = {
|
||||
@@ -32,4 +35,16 @@ export const strings: Record<StringId, StringInfo> = {
|
||||
singular: 'Workspace',
|
||||
plural: 'Workspaces',
|
||||
},
|
||||
baseSpace: {
|
||||
singular: 'Base',
|
||||
plural: 'Base',
|
||||
},
|
||||
localSpace: {
|
||||
singular: 'Local',
|
||||
plural: 'Local',
|
||||
},
|
||||
remoteSpace: {
|
||||
singular: 'Remote',
|
||||
plural: 'Remote',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -15,9 +15,10 @@ export async function rename(w: Workspace, s: ApiSpec, name: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function duplicate(w: Workspace, name: string) {
|
||||
export async function duplicate(w: Workspace, { name, parentId }: Pick<Workspace, 'name' | 'parentId'>) {
|
||||
const newWorkspace = await db.duplicate(w, {
|
||||
name,
|
||||
parentId,
|
||||
});
|
||||
await models.apiSpec.updateOrCreateForParentId(newWorkspace._id, {
|
||||
fileName: name,
|
||||
|
||||
@@ -14,6 +14,22 @@ describe('initialize-project', () => {
|
||||
beforeEach(globalBeforeEach);
|
||||
|
||||
describe('initializeLocalProjectAndMarkForSync()', () => {
|
||||
it('should do nothing if not request collection', async () => {
|
||||
// Arrange
|
||||
const workspace = await models.workspace.create({ scope: 'design' });
|
||||
await models.workspace.ensureChildren(workspace);
|
||||
const vcs = new VCS(new MemoryDriver());
|
||||
const switchAndCreateProjectIfNotExistSpy = jest.spyOn(vcs, 'switchAndCreateProjectIfNotExist');
|
||||
|
||||
// Act
|
||||
await initializeLocalProjectAndMarkForSync({ workspace, vcs });
|
||||
|
||||
// Assert
|
||||
expect(switchAndCreateProjectIfNotExistSpy).not.toHaveBeenCalled();
|
||||
const workspaceMeta = await models.workspaceMeta.getByParentId(workspace._id);
|
||||
expect(workspaceMeta?.pushSnapshotOnInitialize).toBe(false);
|
||||
switchAndCreateProjectIfNotExistSpy.mockClear();
|
||||
});
|
||||
it('should create a local project and commit', async () => {
|
||||
const workspace = await models.workspace.create();
|
||||
await models.workspace.ensureChildren(workspace);
|
||||
@@ -63,10 +79,23 @@ describe('initialize-project', () => {
|
||||
pushSpy.mockClear();
|
||||
});
|
||||
|
||||
it('should not push if no active project', async () => {
|
||||
const space = await models.space.create({ remoteId: null });
|
||||
const workspace = await models.workspace.create({ parentId: space._id });
|
||||
const workspaceMeta = await models.workspaceMeta.create({ parentId: workspace._id });
|
||||
vcs.clearProject();
|
||||
|
||||
await pushSnapshotOnInitialize({ vcs, space, workspace, workspaceMeta });
|
||||
|
||||
expect(pushSpy).not.toHaveBeenCalled();
|
||||
await expect(models.workspaceMeta.getByParentId(workspace._id)).resolves.toStrictEqual(workspaceMeta);
|
||||
});
|
||||
|
||||
it('should not push snapshot if not remote space', async () => {
|
||||
const space = await models.space.create({ remoteId: null });
|
||||
const workspace = await models.workspace.create({ parentId: space._id });
|
||||
const workspaceMeta = await models.workspaceMeta.create({ parentId: workspace._id });
|
||||
vcs.switchAndCreateProjectIfNotExist(workspace._id, workspace.name);
|
||||
|
||||
await pushSnapshotOnInitialize({ vcs, space, workspace, workspaceMeta });
|
||||
|
||||
@@ -79,6 +108,7 @@ describe('initialize-project', () => {
|
||||
const anotherSpace = await models.space.create({ remoteId: 'def' });
|
||||
const workspace = await models.workspace.create({ parentId: anotherSpace._id });
|
||||
const workspaceMeta = await models.workspaceMeta.create({ parentId: workspace._id });
|
||||
vcs.switchAndCreateProjectIfNotExist(workspace._id, workspace.name);
|
||||
|
||||
await pushSnapshotOnInitialize({ vcs, space, workspace, workspaceMeta });
|
||||
|
||||
@@ -90,6 +120,7 @@ describe('initialize-project', () => {
|
||||
const space = await models.space.create({ remoteId: 'abc' });
|
||||
const workspace = await models.workspace.create({ parentId: space._id });
|
||||
const workspaceMeta = await models.workspaceMeta.create({ parentId: workspace._id, pushSnapshotOnInitialize: false });
|
||||
vcs.switchAndCreateProjectIfNotExist(workspace._id, workspace.name);
|
||||
|
||||
await pushSnapshotOnInitialize({ vcs, space, workspace, workspaceMeta });
|
||||
|
||||
@@ -101,6 +132,7 @@ describe('initialize-project', () => {
|
||||
const space = await models.space.create({ remoteId: 'abc' });
|
||||
const workspace = await models.workspace.create({ parentId: space._id });
|
||||
const workspaceMeta = await models.workspaceMeta.create({ parentId: workspace._id, pushSnapshotOnInitialize: true });
|
||||
vcs.switchAndCreateProjectIfNotExist(workspace._id, workspace.name);
|
||||
|
||||
await pushSnapshotOnInitialize({ vcs, space, workspace, workspaceMeta });
|
||||
|
||||
|
||||
@@ -2,13 +2,18 @@ import { database } from '../../common/database';
|
||||
import * as models from '../../models';
|
||||
import { getStatusCandidates } from '../../models/helpers/get-status-candidates';
|
||||
import { Space } from '../../models/space';
|
||||
import { Workspace } from '../../models/workspace';
|
||||
import { isCollection, Workspace } from '../../models/workspace';
|
||||
import { WorkspaceMeta } from '../../models/workspace-meta';
|
||||
import { VCS } from './vcs';
|
||||
|
||||
const blankStage = {};
|
||||
|
||||
export const initializeLocalProjectAndMarkForSync = async ({ vcs, workspace }: { vcs: VCS; workspace: Workspace; }) => {
|
||||
if (!isCollection(workspace)) {
|
||||
// Don't initialize and mark for sync unless we're in a collection
|
||||
return;
|
||||
}
|
||||
|
||||
// Create local project
|
||||
await vcs.switchAndCreateProjectIfNotExist(workspace._id, workspace.name);
|
||||
|
||||
@@ -39,8 +44,14 @@ export const pushSnapshotOnInitialize = async ({
|
||||
}) => {
|
||||
const spaceIsForWorkspace = spaceId === workspace.parentId;
|
||||
const markedForPush = workspaceMeta?.pushSnapshotOnInitialize;
|
||||
|
||||
// A race condition occurs in App.tsx when updating the active workspace
|
||||
// One code path is that a React Key updates, forcing all children to unmount and remount (https://github.com/Kong/insomnia/blob/9a943879060927d6ab1c21d3e12daba39ad05eea/packages/insomnia-app/app/ui/containers/app.tsx#L1514-L1514)
|
||||
// At the same time, we set VCS to null, then set it to the correct value, in state in App.tsx, forcing downstream updates (https://github.com/Kong/insomnia/blob/9a943879060927d6ab1c21d3e12daba39ad05eea/packages/insomnia-app/app/ui/containers/app.tsx#L1149-L1149)
|
||||
// This race condition causes us to hit this codepath twice while activating a workspace but the first time it has no project so we shouldn't do anything
|
||||
const hasProject = vcs.hasProject();
|
||||
|
||||
if (markedForPush && spaceIsForWorkspace && spaceRemoteId) {
|
||||
if (markedForPush && spaceIsForWorkspace && spaceRemoteId && hasProject) {
|
||||
await models.workspaceMeta.updateByParentId(workspace._id, { pushSnapshotOnInitialize: false });
|
||||
await vcs.push(spaceRemoteId);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ export interface ModalProps {
|
||||
noEscape?: boolean,
|
||||
dontFocus?: boolean,
|
||||
closeOnKeyCodes?: any[],
|
||||
onShow?: Function,
|
||||
onHide?: Function,
|
||||
onCancel?: Function,
|
||||
onKeyDown?: Function,
|
||||
@@ -107,6 +108,8 @@ class Modal extends PureComponent<ModalProps, State> {
|
||||
forceRefreshCounter: forceRefreshCounter + (freshState ? 1 : 0),
|
||||
});
|
||||
|
||||
this.props.onShow?.();
|
||||
|
||||
if (this.props.dontFocus) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -49,9 +49,9 @@ const TooltipIcon = ({ message, icon }: { message: string, icon: SvgIconProps['i
|
||||
);
|
||||
|
||||
const spinner = <i className="fa fa-spin fa-refresh" />;
|
||||
const home = <TooltipIcon message={`Base ${strings.space.singular} (Always Local)`} icon="home" />;
|
||||
const remoteSpace = <TooltipIcon message={`Remote ${strings.space.singular}`} icon="globe" />;
|
||||
const localSpace = <TooltipIcon message={`Local ${strings.space.singular}`} icon="laptop" />;
|
||||
const home = <TooltipIcon message={`${strings.baseSpace.singular} ${strings.space.singular} (Always ${strings.localSpace.singular})`} icon="home" />;
|
||||
const remoteSpace = <TooltipIcon message={`${strings.remoteSpace.singular} ${strings.space.singular}`} icon="globe" />;
|
||||
const localSpace = <TooltipIcon message={`${strings.localSpace.singular} ${strings.space.singular}`} icon="laptop" />;
|
||||
|
||||
interface Props {
|
||||
vcs?: VCS;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { SvgIcon } from 'insomnia-components';
|
||||
import React, { FC, useCallback, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { parseApiSpec } from '../../../common/api-specs';
|
||||
import { getWorkspaceLabel } from '../../../common/get-workspace-label';
|
||||
@@ -16,10 +15,10 @@ import type { DocumentAction } from '../../../plugins';
|
||||
import { getDocumentActions } from '../../../plugins';
|
||||
import * as pluginContexts from '../../../plugins/context';
|
||||
import { useLoadingRecord } from '../../hooks/use-loading-record';
|
||||
import { setActiveWorkspace } from '../../redux/modules/global';
|
||||
import { Dropdown, DropdownButton, DropdownDivider, DropdownItem } from '../base/dropdown';
|
||||
import { showError, showModal, showPrompt } from '../modals';
|
||||
import AskModal from '../modals/ask-modal';
|
||||
import { showWorkspaceDuplicateModal } from '../modals/workspace-duplicate-modal';
|
||||
|
||||
interface Props {
|
||||
workspace: Workspace;
|
||||
@@ -30,21 +29,9 @@ interface Props {
|
||||
const spinner = <i className="fa fa-refresh fa-spin" />;
|
||||
|
||||
const useWorkspaceHandlers = ({ workspace, apiSpec }: Props) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleDuplicate = useCallback(() => {
|
||||
showPrompt({
|
||||
title: `Duplicate ${getWorkspaceLabel(workspace).singular}`,
|
||||
defaultValue: getWorkspaceName(workspace, apiSpec),
|
||||
submitName: 'Create',
|
||||
selectText: true,
|
||||
label: 'New Name',
|
||||
onComplete: async newName => {
|
||||
const newWorkspace = await workspaceOperations.duplicate(workspace, newName);
|
||||
dispatch(setActiveWorkspace(newWorkspace._id));
|
||||
},
|
||||
});
|
||||
}, [apiSpec, workspace, dispatch]);
|
||||
showWorkspaceDuplicateModal({ workspace, apiSpec });
|
||||
}, [apiSpec, workspace]);
|
||||
|
||||
const handleRename = useCallback(() => {
|
||||
showPrompt({
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
import { autoBindMethodsForReact } from 'class-autobind-decorator';
|
||||
import React, { createRef, FC, forwardRef, ForwardRefRenderFunction, PureComponent } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { AUTOBIND_CFG } from '../../../common/constants';
|
||||
import { getWorkspaceLabel } from '../../../common/get-workspace-label';
|
||||
import { strings } from '../../../common/strings';
|
||||
import * as models from '../../../models';
|
||||
import { ApiSpec } from '../../../models/api-spec';
|
||||
import getWorkspaceName from '../../../models/helpers/get-workspace-name';
|
||||
import * as workspaceOperations from '../../../models/helpers/workspace-operations';
|
||||
import { isBaseSpace, isLocalSpace, isRemoteSpace, Space } from '../../../models/space';
|
||||
import { Workspace } from '../../../models/workspace';
|
||||
import { initializeLocalProjectAndMarkForSync } from '../../../sync/vcs/initialize-project';
|
||||
import { VCS } from '../../../sync/vcs/vcs';
|
||||
import { activateWorkspace } from '../../redux/modules/workspace';
|
||||
import { selectActiveSpace, selectIsLoggedIn, selectSpaces } from '../../redux/selectors';
|
||||
import Modal from '../base/modal';
|
||||
import ModalBody from '../base/modal-body';
|
||||
import ModalFooter from '../base/modal-footer';
|
||||
import ModalHeader from '../base/modal-header';
|
||||
import { showModal } from '.';
|
||||
|
||||
interface Options {
|
||||
workspace: Workspace;
|
||||
apiSpec: ApiSpec;
|
||||
onDone?: () => void;
|
||||
}
|
||||
|
||||
interface FormFields {
|
||||
newName: string;
|
||||
spaceId: string;
|
||||
}
|
||||
|
||||
interface InnerProps extends Options, Props {
|
||||
hide: () => void,
|
||||
}
|
||||
|
||||
const SpaceOption: FC<Space> = space => (
|
||||
<option key={space._id} value={space._id}>
|
||||
{space.name} ({isBaseSpace(space) ? strings.baseSpace.singular : isLocalSpace(space) ? strings.localSpace.singular : strings.remoteSpace.singular})
|
||||
</option>
|
||||
);
|
||||
|
||||
const WorkspaceDuplicateModalInternalWithRef: ForwardRefRenderFunction<Modal, InnerProps> = ({ workspace, apiSpec, onDone, hide, vcs }, ref) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const spaces = useSelector(selectSpaces);
|
||||
const activeSpace = useSelector(selectActiveSpace);
|
||||
const isLoggedIn = useSelector(selectIsLoggedIn);
|
||||
|
||||
const title = `Duplicate ${getWorkspaceLabel(workspace).singular}`;
|
||||
const defaultWorkspaceName = getWorkspaceName(workspace, apiSpec);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: {
|
||||
errors,
|
||||
} } = useForm<FormFields>({
|
||||
defaultValues: {
|
||||
newName: defaultWorkspaceName,
|
||||
spaceId: activeSpace._id,
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = useCallback(async ({ spaceId, newName }: FormFields) => {
|
||||
const duplicateToSpace = spaces.find(space => space._id === spaceId);
|
||||
if (!duplicateToSpace) {
|
||||
throw new Error('Space could not be found');
|
||||
}
|
||||
|
||||
const newWorkspace = await workspaceOperations.duplicate(workspace, { name: newName, parentId: spaceId });
|
||||
await models.workspace.ensureChildren(newWorkspace);
|
||||
|
||||
// Mark for sync if logged in and in the expected space
|
||||
if (isLoggedIn && vcs && isRemoteSpace(duplicateToSpace)) {
|
||||
await initializeLocalProjectAndMarkForSync({ vcs: vcs.newInstance(), workspace: newWorkspace });
|
||||
}
|
||||
|
||||
dispatch(activateWorkspace(newWorkspace));
|
||||
hide();
|
||||
onDone?.();
|
||||
}, [dispatch, hide, isLoggedIn, onDone, spaces, vcs, workspace]);
|
||||
|
||||
return <Modal ref={ref} onShow={reset}>
|
||||
<ModalHeader>{title}</ModalHeader>
|
||||
<ModalBody className="wide">
|
||||
<form className="wide pad" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="form-control form-control--wide form-control--outlined">
|
||||
<label>
|
||||
New Name
|
||||
<input {...register('newName', { validate: v => Boolean(v.trim()) || 'Should not be blank' })} />
|
||||
{errors.newName && <div className="font-error space-top">{errors.newName.message}</div>}
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>
|
||||
{strings.space.singular} to duplicate into
|
||||
<select {...register('spaceId')}>
|
||||
{spaces.map(SpaceOption)}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<button className="btn" onClick={handleSubmit(onSubmit)}>
|
||||
Duplicate
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</Modal>;
|
||||
};
|
||||
|
||||
const WorkspaceDuplicateModalInternal = forwardRef(WorkspaceDuplicateModalInternalWithRef);
|
||||
|
||||
interface Props {
|
||||
vcs?: VCS;
|
||||
}
|
||||
|
||||
interface State {
|
||||
options?: Options;
|
||||
}
|
||||
|
||||
@autoBindMethodsForReact(AUTOBIND_CFG)
|
||||
export class WorkspaceDuplicateModal extends PureComponent<Props, State> {
|
||||
state: State = { };
|
||||
modal = createRef<Modal>();
|
||||
|
||||
show(options: Options) {
|
||||
this.setState({ options }, () => {
|
||||
this.modal?.current?.show();
|
||||
});
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.modal?.current?.hide();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.options) {
|
||||
return <WorkspaceDuplicateModalInternal
|
||||
ref={this.modal}
|
||||
{...this.state.options}
|
||||
{...this.props}
|
||||
hide={this.hide}
|
||||
/>;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const showWorkspaceDuplicateModal = (options: Options) => showModal(WorkspaceDuplicateModal, options);
|
||||
@@ -19,6 +19,7 @@ import ModalHeader from '../base/modal-header';
|
||||
import PromptButton from '../base/prompt-button';
|
||||
import HelpTooltip from '../help-tooltip';
|
||||
import MarkdownEditor from '../markdown-editor';
|
||||
import { showWorkspaceDuplicateModal } from './workspace-duplicate-modal';
|
||||
|
||||
interface Props {
|
||||
clientCertificates: ClientCertificate[];
|
||||
@@ -33,7 +34,6 @@ interface Props {
|
||||
handleRender: HandleRender;
|
||||
handleGetRenderContext: HandleGetRenderContext;
|
||||
handleRemoveWorkspace: Function;
|
||||
handleDuplicateWorkspace: Function;
|
||||
handleClearAllResponses: Function;
|
||||
}
|
||||
|
||||
@@ -90,9 +90,8 @@ class WorkspaceSettingsModal extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
_handleDuplicateWorkspace() {
|
||||
this.props.handleDuplicateWorkspace(() => {
|
||||
this.hide();
|
||||
});
|
||||
const { workspace, apiSpec } = this.props;
|
||||
showWorkspaceDuplicateModal({ workspace, apiSpec, onDone: this.hide });
|
||||
}
|
||||
|
||||
_handleToggleCertificateForm() {
|
||||
|
||||
@@ -29,6 +29,7 @@ import { fuzzyMatchAll, isNotNullOrUndefined } from '../../common/misc';
|
||||
import { descendingNumberSort } from '../../common/sorting';
|
||||
import { strings } from '../../common/strings';
|
||||
import * as models from '../../models';
|
||||
import { isRemoteSpace } from '../../models/space';
|
||||
import { isDesign, Workspace, WorkspaceScopeKeys } from '../../models/workspace';
|
||||
import { MemClient } from '../../sync/git/mem-client';
|
||||
import { initializeLocalProjectAndMarkForSync } from '../../sync/vcs/initialize-project';
|
||||
@@ -95,10 +96,8 @@ class WrapperHome extends PureComponent<Props, State> {
|
||||
handleCreateWorkspace({
|
||||
scope: WorkspaceScopeKeys.collection,
|
||||
onCreate: async workspace => {
|
||||
const spaceRemoteId = activeSpace?.remoteId;
|
||||
|
||||
// Don't mark for sync if not logged in at the time of creation
|
||||
if (isLoggedIn && vcs && spaceRemoteId) {
|
||||
if (isLoggedIn && vcs && isRemoteSpace(activeSpace)) {
|
||||
await initializeLocalProjectAndMarkForSync({ vcs: vcs.newInstance(), workspace });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -75,6 +75,7 @@ import SyncDeleteModal from './modals/sync-delete-modal';
|
||||
import SyncHistoryModal from './modals/sync-history-modal';
|
||||
import SyncMergeModal from './modals/sync-merge-modal';
|
||||
import SyncStagingModal from './modals/sync-staging-modal';
|
||||
import { WorkspaceDuplicateModal } from './modals/workspace-duplicate-modal';
|
||||
import WorkspaceEnvironmentsEditModal from './modals/workspace-environments-edit-modal';
|
||||
import WorkspaceSettingsModal from './modals/workspace-settings-modal';
|
||||
import WrapperModal from './modals/wrapper-modal';
|
||||
@@ -96,7 +97,6 @@ export type WrapperProps = AppProps & {
|
||||
handleCreateRequest: (id: string) => void;
|
||||
handleDuplicateRequest: Function;
|
||||
handleDuplicateRequestGroup: (requestGroup: RequestGroup) => void;
|
||||
handleDuplicateWorkspace: Function;
|
||||
handleCreateRequestGroup: (parentId: string) => void;
|
||||
handleGenerateCodeForActiveRequest: Function;
|
||||
handleGenerateCode: Function;
|
||||
@@ -479,7 +479,6 @@ class Wrapper extends PureComponent<WrapperProps, State> {
|
||||
activity,
|
||||
gitVCS,
|
||||
handleActivateRequest,
|
||||
handleDuplicateWorkspace,
|
||||
handleExportRequestsToFile,
|
||||
handleGetRenderContext,
|
||||
handleInitializeEntities,
|
||||
@@ -538,6 +537,7 @@ class Wrapper extends PureComponent<WrapperProps, State> {
|
||||
<RequestRenderErrorModal ref={registerModal} />
|
||||
<GenerateConfigModal ref={registerModal} settings={settings} />
|
||||
<SpaceSettingsModal ref={registerModal} />
|
||||
<WorkspaceDuplicateModal ref={registerModal} vcs={vcs || undefined} />
|
||||
|
||||
<CodePromptModal
|
||||
ref={registerModal}
|
||||
@@ -619,7 +619,6 @@ class Wrapper extends PureComponent<WrapperProps, State> {
|
||||
handleGetRenderContext={handleGetRenderContext}
|
||||
nunjucksPowerUserMode={settings.nunjucksPowerUserMode}
|
||||
handleRemoveWorkspace={this._handleRemoveActiveWorkspace}
|
||||
handleDuplicateWorkspace={handleDuplicateWorkspace}
|
||||
handleClearAllResponses={this._handleActiveWorkspaceClearAllResponses}
|
||||
isVariableUncovered={isVariableUncovered}
|
||||
/> : null}
|
||||
|
||||
@@ -32,7 +32,6 @@ import {
|
||||
} from '../../common/constants';
|
||||
import { database as db } from '../../common/database';
|
||||
import { getDataDirectory } from '../../common/electron-helpers';
|
||||
import { getWorkspaceLabel } from '../../common/get-workspace-label';
|
||||
import { exportHarRequest } from '../../common/har';
|
||||
import { hotKeyRefs } from '../../common/hotkeys';
|
||||
import { executeHotKey } from '../../common/hotkeys-listener';
|
||||
@@ -48,13 +47,12 @@ import * as models from '../../models';
|
||||
import { isEnvironment } from '../../models/environment';
|
||||
import { GrpcRequest, isGrpcRequest, isGrpcRequestId } from '../../models/grpc-request';
|
||||
import { GrpcRequestMeta } from '../../models/grpc-request-meta';
|
||||
import getWorkspaceName from '../../models/helpers/get-workspace-name';
|
||||
import * as requestOperations from '../../models/helpers/request-operations';
|
||||
import * as workspaceOperations from '../../models/helpers/workspace-operations';
|
||||
import { Request, updateMimeType } from '../../models/request';
|
||||
import { isRequestGroup, RequestGroup } from '../../models/request-group';
|
||||
import { RequestMeta } from '../../models/request-meta';
|
||||
import { Response } from '../../models/response';
|
||||
import { isNotBaseSpace } from '../../models/space';
|
||||
import { isCollection, isWorkspace } from '../../models/workspace';
|
||||
import { WorkspaceMeta } from '../../models/workspace-meta';
|
||||
import * as network from '../../network/network';
|
||||
@@ -501,32 +499,6 @@ class App extends PureComponent<AppProps, State> {
|
||||
});
|
||||
}
|
||||
|
||||
_workspaceDuplicateById(callback: () => void, workspaceId: string) {
|
||||
const workspace = this.props.workspaces.find(w => w._id === workspaceId);
|
||||
const apiSpec = this.props.apiSpecs.find(s => s.parentId === workspaceId);
|
||||
showPrompt({
|
||||
// @ts-expect-error -- TSCONVERSION workspace can be null
|
||||
title: `Duplicate ${getWorkspaceLabel(workspace).singular}`,
|
||||
// @ts-expect-error -- TSCONVERSION workspace can be null
|
||||
defaultValue: getWorkspaceName(workspace, apiSpec),
|
||||
submitName: 'Create',
|
||||
selectText: true,
|
||||
label: 'New Name',
|
||||
onComplete: async name => {
|
||||
// @ts-expect-error -- TSCONVERSION workspace can be null
|
||||
const newWorkspace = await workspaceOperations.duplicate(workspace, name);
|
||||
await this.props.handleSetActiveWorkspace(newWorkspace._id);
|
||||
callback();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
_workspaceDuplicate(callback: () => void) {
|
||||
if (this.props.activeWorkspace) {
|
||||
this._workspaceDuplicateById(callback, this.props.activeWorkspace._id);
|
||||
}
|
||||
}
|
||||
|
||||
async _fetchRenderContext() {
|
||||
const { activeEnvironment, activeRequest, activeWorkspace } = this.props;
|
||||
const ancestors = await render.getRenderContextAncestors(activeRequest || activeWorkspace);
|
||||
@@ -1367,7 +1339,9 @@ class App extends PureComponent<AppProps, State> {
|
||||
const bufferId = await db.bufferChanges();
|
||||
console.log(`[developer] clearing all "${type}" entities`);
|
||||
const allEntities = await db.all(type);
|
||||
await db.batchModifyDocs({ remove: allEntities });
|
||||
const filteredEntites = allEntities
|
||||
.filter(isNotBaseSpace); // don't clear the base space
|
||||
await db.batchModifyDocs({ remove: filteredEntites });
|
||||
db.flushChanges(bufferId);
|
||||
}
|
||||
},
|
||||
@@ -1389,7 +1363,9 @@ class App extends PureComponent<AppProps, State> {
|
||||
.reverse().map(async type => {
|
||||
console.log(`[developer] clearing all "${type}" entities`);
|
||||
const allEntities = await db.all(type);
|
||||
await db.batchModifyDocs({ remove: allEntities });
|
||||
const filteredEntites = allEntities
|
||||
.filter(isNotBaseSpace); // don't clear the base space
|
||||
await db.batchModifyDocs({ remove: filteredEntites });
|
||||
});
|
||||
await Promise.all(promises);
|
||||
db.flushChanges(bufferId);
|
||||
@@ -1565,7 +1541,6 @@ class App extends PureComponent<AppProps, State> {
|
||||
handleGetRenderContext={this._handleGetRenderContext}
|
||||
handleDuplicateRequest={this._requestDuplicate}
|
||||
handleDuplicateRequestGroup={App._requestGroupDuplicate}
|
||||
handleDuplicateWorkspace={this._workspaceDuplicate}
|
||||
handleCreateRequestGroup={this._requestGroupCreate}
|
||||
handleGenerateCode={App._handleGenerateCode}
|
||||
handleGenerateCodeForActiveRequest={this._handleGenerateCodeForActiveRequest}
|
||||
|
||||
@@ -4,7 +4,7 @@ import thunk from 'redux-thunk';
|
||||
import { globalBeforeEach } from '../../../../__jest__/before-each';
|
||||
import { reduxStateForTest } from '../../../../__jest__/redux-state-for-test';
|
||||
import { trackEvent, trackSegmentEvent } from '../../../../common/analytics';
|
||||
import { ACTIVITY_DEBUG, ACTIVITY_SPEC } from '../../../../common/constants';
|
||||
import { ACTIVITY_DEBUG, ACTIVITY_HOME, ACTIVITY_SPEC, ACTIVITY_UNIT_TEST } from '../../../../common/constants';
|
||||
import { database } from '../../../../common/database';
|
||||
import * as models from '../../../../models';
|
||||
import { ApiSpec } from '../../../../models/api-spec';
|
||||
@@ -14,8 +14,8 @@ import { BASE_SPACE_ID } from '../../../../models/space';
|
||||
import { Workspace, WorkspaceScope, WorkspaceScopeKeys } from '../../../../models/workspace';
|
||||
import { WorkspaceMeta } from '../../../../models/workspace-meta';
|
||||
import { getAndClearShowPromptMockArgs } from '../../../../test-utils';
|
||||
import { SET_ACTIVE_ACTIVITY, SET_ACTIVE_WORKSPACE } from '../global';
|
||||
import { createWorkspace } from '../workspace';
|
||||
import { SET_ACTIVE_ACTIVITY, SET_ACTIVE_SPACE, SET_ACTIVE_WORKSPACE } from '../global';
|
||||
import { activateWorkspace, createWorkspace } from '../workspace';
|
||||
|
||||
jest.mock('../../../components/modals');
|
||||
jest.mock('../../../../common/analytics');
|
||||
@@ -23,15 +23,6 @@ jest.mock('../../../../common/analytics');
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureMockStore(middlewares);
|
||||
|
||||
const createStoreWithSpace = async () => {
|
||||
const space = await models.initModel(models.space.type);
|
||||
|
||||
const entities = { spaces: { [space._id]: space } };
|
||||
const global = { activeSpaceId: space._id };
|
||||
const store = mockStore({ entities, global });
|
||||
return { store, space };
|
||||
};
|
||||
|
||||
const expectedModelsCreated = async (name: string, scope: WorkspaceScope, parentId: string) => {
|
||||
const workspaces = await models.workspace.all();
|
||||
expect(workspaces).toHaveLength(1);
|
||||
@@ -55,7 +46,8 @@ describe('workspace', () => {
|
||||
beforeEach(globalBeforeEach);
|
||||
describe('createWorkspace', () => {
|
||||
it('should create document', async () => {
|
||||
const { store, space } = await createStoreWithSpace();
|
||||
const spaceId = BASE_SPACE_ID;
|
||||
const store = mockStore(await reduxStateForTest({ activeSpaceId: spaceId }));
|
||||
|
||||
// @ts-expect-error redux-thunk types
|
||||
store.dispatch(createWorkspace({ scope: WorkspaceScopeKeys.design }));
|
||||
@@ -67,7 +59,7 @@ describe('workspace', () => {
|
||||
const workspaceName = 'name';
|
||||
await onComplete?.(workspaceName);
|
||||
|
||||
const workspaceId = await expectedModelsCreated(workspaceName, WorkspaceScopeKeys.design, space._id);
|
||||
const workspaceId = await expectedModelsCreated(workspaceName, WorkspaceScopeKeys.design, spaceId);
|
||||
|
||||
expect(trackSegmentEvent).toHaveBeenCalledWith('Document Created');
|
||||
expect(trackEvent).toHaveBeenCalledWith('Workspace', 'Create');
|
||||
@@ -84,7 +76,8 @@ describe('workspace', () => {
|
||||
});
|
||||
|
||||
it('should create collection', async () => {
|
||||
const { store, space } = await createStoreWithSpace();
|
||||
const spaceId = BASE_SPACE_ID;
|
||||
const store = mockStore(await reduxStateForTest({ activeSpaceId: spaceId }));
|
||||
|
||||
// @ts-expect-error redux-thunk types
|
||||
store.dispatch(createWorkspace({ scope: WorkspaceScopeKeys.collection }));
|
||||
@@ -96,7 +89,7 @@ describe('workspace', () => {
|
||||
const workspaceName = 'name';
|
||||
await onComplete?.(workspaceName);
|
||||
|
||||
const workspaceId = await expectedModelsCreated(workspaceName, WorkspaceScopeKeys.collection, space._id);
|
||||
const workspaceId = await expectedModelsCreated(workspaceName, WorkspaceScopeKeys.collection, spaceId);
|
||||
|
||||
expect(trackSegmentEvent).toHaveBeenCalledWith('Collection Created');
|
||||
expect(trackEvent).toHaveBeenCalledWith('Workspace', 'Create');
|
||||
@@ -141,4 +134,138 @@ describe('workspace', () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('activateWorkspace', () => {
|
||||
it('should activate space and workspace and activity from home', async () => {
|
||||
const space = await models.space.create();
|
||||
const workspace = await models.workspace.create({ scope: 'design', parentId: space._id });
|
||||
const store = mockStore(await reduxStateForTest({ activeSpaceId: 'abc', activeWorkspaceId: 'def', activeActivity: ACTIVITY_HOME }));
|
||||
|
||||
await store.dispatch(activateWorkspace(workspace));
|
||||
|
||||
expect(store.getActions()).toEqual([
|
||||
{
|
||||
type: SET_ACTIVE_SPACE,
|
||||
spaceId: space._id,
|
||||
},
|
||||
{
|
||||
type: SET_ACTIVE_WORKSPACE,
|
||||
workspaceId: workspace._id,
|
||||
},
|
||||
{
|
||||
type: SET_ACTIVE_ACTIVITY,
|
||||
activity: ACTIVITY_SPEC,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should switch to the default design activity', async () => {
|
||||
const space = await models.space.create();
|
||||
const workspace = await models.workspace.create({ scope: 'design', parentId: space._id });
|
||||
const store = mockStore(await reduxStateForTest({ activeSpaceId: space._id, activeWorkspaceId: workspace._id, activeActivity: ACTIVITY_HOME }));
|
||||
|
||||
await store.dispatch(activateWorkspace(workspace));
|
||||
|
||||
expect(store.getActions()).toEqual([
|
||||
{
|
||||
type: SET_ACTIVE_SPACE,
|
||||
spaceId: space._id,
|
||||
},
|
||||
{
|
||||
type: SET_ACTIVE_WORKSPACE,
|
||||
workspaceId: workspace._id,
|
||||
},
|
||||
{
|
||||
type: SET_ACTIVE_ACTIVITY,
|
||||
activity: ACTIVITY_SPEC,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([ACTIVITY_UNIT_TEST, ACTIVITY_SPEC, ACTIVITY_DEBUG])('should not switch activity if already in a supported design activity: %s', async activeActivity => {
|
||||
const space = await models.space.create();
|
||||
const workspace = await models.workspace.create({ scope: 'design', parentId: space._id });
|
||||
const store = mockStore(await reduxStateForTest({ activeSpaceId: space._id, activeWorkspaceId: workspace._id, activeActivity }));
|
||||
|
||||
await store.dispatch(activateWorkspace(workspace));
|
||||
|
||||
expect(store.getActions()).toEqual([
|
||||
{
|
||||
type: SET_ACTIVE_SPACE,
|
||||
spaceId: space._id,
|
||||
},
|
||||
{
|
||||
type: SET_ACTIVE_WORKSPACE,
|
||||
workspaceId: workspace._id,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([ACTIVITY_DEBUG])('should not switch activity if already in a supported collection activity: %s', async activeActivity => {
|
||||
const space = await models.space.create();
|
||||
const workspace = await models.workspace.create({ scope: 'design', parentId: space._id });
|
||||
const store = mockStore(await reduxStateForTest({ activeSpaceId: space._id, activeWorkspaceId: workspace._id, activeActivity }));
|
||||
|
||||
await store.dispatch(activateWorkspace(workspace));
|
||||
|
||||
expect(store.getActions()).toEqual([
|
||||
{
|
||||
type: SET_ACTIVE_SPACE,
|
||||
spaceId: space._id,
|
||||
},
|
||||
{
|
||||
type: SET_ACTIVE_WORKSPACE,
|
||||
workspaceId: workspace._id,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should switch to the default collection activity', async () => {
|
||||
const space = await models.space.create();
|
||||
const workspace = await models.workspace.create({ scope: 'collection', parentId: space._id });
|
||||
const store = mockStore(await reduxStateForTest({ activeSpaceId: space._id, activeWorkspaceId: workspace._id, activeActivity: ACTIVITY_HOME }));
|
||||
|
||||
await store.dispatch(activateWorkspace(workspace));
|
||||
|
||||
expect(store.getActions()).toEqual([
|
||||
{
|
||||
type: SET_ACTIVE_SPACE,
|
||||
spaceId: space._id,
|
||||
},
|
||||
{
|
||||
type: SET_ACTIVE_WORKSPACE,
|
||||
workspaceId: workspace._id,
|
||||
},
|
||||
{
|
||||
type: SET_ACTIVE_ACTIVITY,
|
||||
activity: ACTIVITY_DEBUG,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should switch to the cached activity', async () => {
|
||||
const space = await models.space.create();
|
||||
const workspace = await models.workspace.create({ scope: 'design', parentId: space._id });
|
||||
await models.workspace.ensureChildren(workspace);
|
||||
await models.workspaceMeta.updateByParentId(workspace._id, { activeActivity: ACTIVITY_UNIT_TEST });
|
||||
const store = mockStore(await reduxStateForTest({ activeSpaceId: space._id, activeWorkspaceId: workspace._id, activeActivity: ACTIVITY_HOME }));
|
||||
|
||||
await store.dispatch(activateWorkspace(workspace));
|
||||
|
||||
expect(store.getActions()).toEqual([
|
||||
{
|
||||
type: SET_ACTIVE_SPACE,
|
||||
spaceId: space._id,
|
||||
},
|
||||
{
|
||||
type: SET_ACTIVE_WORKSPACE,
|
||||
workspaceId: workspace._id,
|
||||
},
|
||||
{
|
||||
type: SET_ACTIVE_ACTIVITY,
|
||||
activity: ACTIVITY_UNIT_TEST,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import { combineReducers, Dispatch } from 'redux';
|
||||
import { unreachableCase } from 'ts-assert-unreachable';
|
||||
|
||||
import { trackEvent } from '../../../common/analytics';
|
||||
import type { GlobalActivity } from '../../../common/constants';
|
||||
import { GlobalActivity } from '../../../common/constants';
|
||||
import {
|
||||
ACTIVITY_ANALYTICS,
|
||||
ACTIVITY_DEBUG,
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Dispatch } from 'redux';
|
||||
|
||||
import { trackEvent, trackSegmentEvent } from '../../../common/analytics';
|
||||
import { ACTIVITY_DEBUG, ACTIVITY_SPEC } from '../../../common/constants';
|
||||
import { ACTIVITY_DEBUG, ACTIVITY_SPEC, GlobalActivity, isCollectionActivity, isDesignActivity } from '../../../common/constants';
|
||||
import { database } from '../../../common/database';
|
||||
import * as models from '../../../models';
|
||||
import { isDesign, Workspace, WorkspaceScope } from '../../../models/workspace';
|
||||
import { isCollection, isDesign, Workspace, WorkspaceScope } from '../../../models/workspace';
|
||||
import { showPrompt } from '../../components/modals';
|
||||
import { selectActiveSpace } from '../selectors';
|
||||
import { setActiveActivity, setActiveWorkspace } from './global';
|
||||
import { selectActiveActivity, selectActiveSpace } from '../selectors';
|
||||
import { RootState } from '.';
|
||||
import { setActiveActivity, setActiveSpace, setActiveWorkspace } from './global';
|
||||
|
||||
type OnWorkspaceCreateCallback = (arg0: Workspace) => Promise<void> | void;
|
||||
|
||||
@@ -70,3 +71,31 @@ export const createWorkspace = ({ scope, onCreate }: {
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export const activateWorkspace = (workspace: Workspace) => {
|
||||
return async (dispatch: Dispatch, getState: () => RootState) => {
|
||||
|
||||
const activeActivity = selectActiveActivity(getState()) || undefined;
|
||||
|
||||
// Activate the correct space
|
||||
const nextSpaceId = workspace.parentId;
|
||||
dispatch(setActiveSpace(nextSpaceId));
|
||||
|
||||
// Activate the correct workspace
|
||||
const nextWorkspaceId = workspace._id;
|
||||
dispatch(setActiveWorkspace(nextWorkspaceId));
|
||||
|
||||
// Activate the correct activity
|
||||
if (isCollection(workspace) && isCollectionActivity(activeActivity)) {
|
||||
// we are in a collection, and our active activity is a collection activity
|
||||
return;
|
||||
} else if (isDesign(workspace) && isDesignActivity(activeActivity)) {
|
||||
// we are in a design document, and our active activity is a design activity
|
||||
return;
|
||||
} else {
|
||||
const { activeActivity: cachedActivity } = await models.workspaceMeta.getOrCreateByParentId(workspace._id);
|
||||
const nextActivity = cachedActivity as GlobalActivity || (isDesign(workspace) ? ACTIVITY_SPEC : ACTIVITY_DEBUG);
|
||||
dispatch(setActiveActivity(nextActivity));
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -23,6 +23,11 @@ export const selectEntities = createSelector(
|
||||
entities => entities,
|
||||
);
|
||||
|
||||
export const selectGlobal = createSelector(
|
||||
(state: RootState) => state.global,
|
||||
global => global,
|
||||
);
|
||||
|
||||
export const selectEntitiesLists = createSelector(
|
||||
selectEntities,
|
||||
entities => {
|
||||
@@ -79,6 +84,11 @@ export const selectAllWorkspaces = createSelector(
|
||||
entities => entities.workspaces,
|
||||
);
|
||||
|
||||
export const selectAllApiSpecs = createSelector(
|
||||
selectEntitiesLists,
|
||||
entities => entities.apiSpecs,
|
||||
);
|
||||
|
||||
export const selectWorkspacesForActiveSpace = createSelector(
|
||||
selectAllWorkspaces,
|
||||
selectActiveSpace,
|
||||
@@ -378,3 +388,13 @@ export const selectSyncItems = createSelector(
|
||||
selectActiveWorkspaceEntities,
|
||||
getStatusCandidates,
|
||||
);
|
||||
|
||||
export const selectIsLoggedIn = createSelector(
|
||||
selectGlobal,
|
||||
global => global.isLoggedIn,
|
||||
);
|
||||
|
||||
export const selectActiveActivity = createSelector(
|
||||
selectGlobal,
|
||||
global => global.activeActivity,
|
||||
);
|
||||
113
packages/insomnia-app/package-lock.json
generated
113
packages/insomnia-app/package-lock.json
generated
@@ -6968,12 +6968,54 @@
|
||||
"verror": "^1.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
||||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"cli-truncate": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-1.1.0.tgz",
|
||||
"integrity": "sha512-bAtZo0u82gCfaAGfSNxUdTI9mNyza7D8w4CVCcaOsy7sgwDzvx6ekr6cuWJqY3UGzgnQ1+4wgENup5eIhgxEYA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"slice-ansi": "^1.0.0",
|
||||
"string-width": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"slice-ansi": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz",
|
||||
"integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-fullwidth-code-point": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"string-width": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
||||
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-fullwidth-code-point": "^2.0.0",
|
||||
"strip-ansi": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
||||
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -10364,7 +10406,62 @@
|
||||
"resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.6.tgz",
|
||||
"integrity": "sha512-1NBe55C75bKGZaY9UHxvXG3G0gEp0ziht7quhuFrW3SPgZDw9HI6qvYXRSV5M/Eupyu8ljuJ6Cba+ec15PZ4Xw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"cli-truncate": "^1.1.0",
|
||||
"node-addon-api": "^1.6.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
||||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"cli-truncate": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-1.1.0.tgz",
|
||||
"integrity": "sha512-bAtZo0u82gCfaAGfSNxUdTI9mNyza7D8w4CVCcaOsy7sgwDzvx6ekr6cuWJqY3UGzgnQ1+4wgENup5eIhgxEYA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"slice-ansi": "^1.0.0",
|
||||
"string-width": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"slice-ansi": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz",
|
||||
"integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-fullwidth-code-point": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"string-width": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
||||
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-fullwidth-code-point": "^2.0.0",
|
||||
"strip-ansi": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
||||
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
@@ -14331,6 +14428,13 @@
|
||||
"jsep": "^0.3.4"
|
||||
}
|
||||
},
|
||||
"node-addon-api": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz",
|
||||
"integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node-cache": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/node-cache/-/node-cache-3.2.1.tgz",
|
||||
@@ -16080,6 +16184,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-hook-form": {
|
||||
"version": "7.12.1",
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.12.1.tgz",
|
||||
"integrity": "sha512-JBu5TZK3IXzDKw9SuNFwyQFdIx5uGZSmN9QTDsNsDSYdccU/O+43jBUh0zKG4jDc4hiNYYgDw34lLt7qLSeusA=="
|
||||
},
|
||||
"react-hot-loader": {
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.13.0.tgz",
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
"react-dnd",
|
||||
"react-dnd-html5-backend",
|
||||
"react-dom",
|
||||
"react-hook-form",
|
||||
"react-redux",
|
||||
"react-sortable-hoc",
|
||||
"react-tabs",
|
||||
@@ -146,6 +147,7 @@
|
||||
"react-dnd": "^7.4.5",
|
||||
"react-dnd-html5-backend": "^7.4.4",
|
||||
"react-dom": "^16.8.3",
|
||||
"react-hook-form": "^7.12.1",
|
||||
"react-redux": "^7.0.1",
|
||||
"react-sortable-hoc": "^0.8.3",
|
||||
"react-tabs": "^3.0.0",
|
||||
|
||||
Reference in New Issue
Block a user