mirror of
https://github.com/Kong/insomnia
synced 2024-11-08 23:00:30 +00:00
307 lines
8.5 KiB
TypeScript
307 lines
8.5 KiB
TypeScript
import { Button, ToggleSwitch } from 'insomnia-components';
|
|
import React, { FunctionComponent, useCallback, useMemo, useState } from 'react';
|
|
import { useDispatch } from 'react-redux';
|
|
|
|
import { trackEvent } from '../../common/analytics';
|
|
import { getDataDirectory, getDesignerDataDir, restartApp } from '../../common/electron-helpers';
|
|
import type { MigrationOptions } from '../../common/migrate-from-designer';
|
|
import migrateFromDesigner, { existsAndIsDirectory } from '../../common/migrate-from-designer';
|
|
import { goToNextActivity } from '../redux/modules/global';
|
|
import { HelpTooltip } from './help-tooltip';
|
|
import { OnboardingContainer } from './onboarding-container';
|
|
import type { WrapperProps } from './wrapper';
|
|
|
|
type Step = 'options' | 'migrating' | 'results';
|
|
|
|
interface SettingProps {
|
|
label: string;
|
|
name: keyof MigrationOptions;
|
|
options: MigrationOptions;
|
|
}
|
|
|
|
type TextSettingProps = SettingProps & {
|
|
handleChange: (arg0: React.SyntheticEvent<HTMLInputElement>) => void;
|
|
errorMessage?: string;
|
|
};
|
|
|
|
const TextSetting: FunctionComponent<TextSettingProps> = ({ handleChange, label, name, options, errorMessage }) => {
|
|
if (!options.hasOwnProperty(name)) {
|
|
throw new Error(`Invalid text setting name ${name}`);
|
|
}
|
|
|
|
const hasError = !!errorMessage;
|
|
return (
|
|
<div className="form-control form-control--outlined margin-bottom">
|
|
<label>
|
|
{label}
|
|
<input
|
|
className={hasError ? 'input--error' : ''}
|
|
type="text"
|
|
name={name}
|
|
// @ts-expect-error -- TSCONVERSION this is a genuine error. booleans shoudl not be allowed here
|
|
defaultValue={options[name]}
|
|
onBlur={handleChange}
|
|
/>
|
|
{hasError && <div className="font-error space-top">{errorMessage}</div>}
|
|
</label>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
type BooleanSettingProps = SettingProps & {
|
|
handleChange: (arg0: boolean, arg1: Record<string, any>, arg2: string) => void;
|
|
help?: string;
|
|
};
|
|
|
|
const BooleanSetting = ({ handleChange, label, name, options, help }: BooleanSettingProps) => {
|
|
if (!options.hasOwnProperty(name)) {
|
|
throw new Error(`Invalid text setting name ${name}`);
|
|
}
|
|
|
|
const labelNode = useMemo(
|
|
() => (
|
|
<>
|
|
{label}
|
|
{help && <HelpTooltip className="space-left">{help}</HelpTooltip>}
|
|
</>
|
|
),
|
|
[help, label],
|
|
);
|
|
return (
|
|
<ToggleSwitch
|
|
labelClassName="row margin-bottom wide"
|
|
checked={Boolean(options[name])}
|
|
// @ts-expect-error -- TSCONVERSION
|
|
id={name}
|
|
label={labelNode}
|
|
onChange={handleChange}
|
|
/>
|
|
);
|
|
};
|
|
|
|
interface OptionsProps {
|
|
start: (arg0: MigrationOptions) => void;
|
|
cancel: () => void;
|
|
}
|
|
|
|
const Options = ({ start, cancel }: OptionsProps) => {
|
|
const [options, setOptions] = useState<MigrationOptions>(() => ({
|
|
useDesignerSettings: false,
|
|
copyWorkspaces: false,
|
|
copyPlugins: false,
|
|
designerDataDir: getDesignerDataDir(),
|
|
coreDataDir: getDataDirectory(),
|
|
}));
|
|
const handleInputChange = useCallback((e: React.SyntheticEvent<HTMLInputElement>) => {
|
|
const { name, value } = e.currentTarget;
|
|
setOptions(prevOpts => ({ ...prevOpts, [name]: value }));
|
|
}, []);
|
|
const handleSwitchChange = useCallback(
|
|
(checked: boolean, _event, id: string) => {
|
|
setOptions(prevOpts => ({ ...prevOpts, [id]: checked }));
|
|
},
|
|
[],
|
|
);
|
|
const {
|
|
coreDataDir,
|
|
designerDataDir,
|
|
useDesignerSettings,
|
|
copyWorkspaces,
|
|
copyPlugins,
|
|
} = options;
|
|
const coreExists = useMemo(() => existsAndIsDirectory(coreDataDir), [coreDataDir]);
|
|
const designerExists = useMemo(() => existsAndIsDirectory(designerDataDir), [
|
|
designerDataDir,
|
|
]);
|
|
const hasSomethingToMigrate = useDesignerSettings || copyWorkspaces || copyPlugins;
|
|
const dirsExist = coreExists && designerExists;
|
|
const canStart = hasSomethingToMigrate && dirsExist;
|
|
return (
|
|
<>
|
|
<p>
|
|
From the list below, select the individual items you would like to migrate from Designer.
|
|
</p>
|
|
<div className="text-left">
|
|
<BooleanSetting
|
|
label="Copy Workspaces"
|
|
name="copyWorkspaces"
|
|
options={options}
|
|
handleChange={handleSwitchChange}
|
|
help={
|
|
'This includes all resources linked to a workspace (eg. requests, proto files, api specs, environments, etc)'
|
|
}
|
|
/>
|
|
<BooleanSetting
|
|
label="Copy Plugins"
|
|
name="copyPlugins"
|
|
options={options}
|
|
handleChange={handleSwitchChange}
|
|
help={
|
|
'Merge plugins between Designer and Insomnia, keeping the Designer version where a duplicate exists'
|
|
}
|
|
/>
|
|
<BooleanSetting
|
|
label="Copy Designer Application Settings"
|
|
name="useDesignerSettings"
|
|
options={options}
|
|
handleChange={handleSwitchChange}
|
|
help={'Keep user preferences from Designer'}
|
|
/>
|
|
<details>
|
|
<summary className="margin-bottom">Advanced options</summary>
|
|
<TextSetting
|
|
label="Designer Data Directory"
|
|
name="designerDataDir"
|
|
options={options}
|
|
handleChange={handleInputChange}
|
|
errorMessage={designerExists ? undefined : 'Directory does not exist'}
|
|
/>
|
|
<TextSetting
|
|
label="Insomnia Data Directory"
|
|
name="coreDataDir"
|
|
options={options}
|
|
handleChange={handleInputChange}
|
|
errorMessage={coreExists ? undefined : 'Directory does not exist'}
|
|
/>
|
|
</details>
|
|
</div>
|
|
|
|
<div className="margin-top">
|
|
<Button
|
|
key="start"
|
|
bg="surprise"
|
|
radius="3px"
|
|
size="medium"
|
|
variant="contained"
|
|
onClick={() => start(options)}
|
|
disabled={!canStart}
|
|
>
|
|
Start Migration
|
|
</Button>
|
|
<button key="cancel" className="btn btn--super-compact" onClick={cancel}>
|
|
Skip for now
|
|
</button>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
const Migrating = () => (
|
|
<>
|
|
<p>
|
|
<strong>Migrating</strong>
|
|
</p>
|
|
<p>
|
|
<i className="fa fa-spin fa-refresh" />
|
|
</p>
|
|
</>
|
|
);
|
|
|
|
const RestartButton = () => (
|
|
<button key="restart" className="btn btn--clicky" onClick={restartApp}>
|
|
Restart Now
|
|
</button>
|
|
);
|
|
|
|
const Success = () => (
|
|
<>
|
|
<p>
|
|
<strong>Migrated successfully!</strong>
|
|
</p>
|
|
<p>
|
|
<i className="fa fa-check" />
|
|
</p>
|
|
|
|
<p>Please restart the application for your changes to take effect.</p>
|
|
|
|
<div className="margin-top">
|
|
<RestartButton />
|
|
</div>
|
|
</>
|
|
);
|
|
|
|
interface FailProps {
|
|
error: Error;
|
|
}
|
|
|
|
const Fail = ({ error }: FailProps) => (
|
|
<>
|
|
<p>
|
|
<strong>Something went wrong!!</strong>
|
|
</p>
|
|
<p>
|
|
<i className="fa fa-exclamation" />
|
|
</p>
|
|
|
|
<div className="wide text-left">
|
|
<p>
|
|
Something went wrong with the migration and all changes made have been reverted. Please
|
|
restart the application.
|
|
</p>
|
|
|
|
{error && (
|
|
<details>
|
|
<summary>Additional information</summary>
|
|
<pre className="pad-top-sm selectable">
|
|
<code className="wide">{error.stack || error}</code>
|
|
</pre>
|
|
</details>
|
|
)}
|
|
</div>
|
|
|
|
<div className="margin-top">
|
|
<RestartButton />
|
|
</div>
|
|
</>
|
|
);
|
|
|
|
const MigrationBody = () => {
|
|
// The migration step does not need to be in redux, but a loading state does need to exist there.
|
|
const [step, setStep] = useState<Step>('options');
|
|
const [error, setError] = useState<Error | null>(null);
|
|
const start = useCallback(async (options: MigrationOptions) => {
|
|
setStep('migrating');
|
|
const { error } = await migrateFromDesigner(options);
|
|
|
|
if (error) {
|
|
setError(error);
|
|
}
|
|
|
|
setStep('results');
|
|
}, []);
|
|
|
|
const reduxDispatch = useDispatch();
|
|
const cancel = useCallback(() => {
|
|
trackEvent('Data', 'Migration', 'Skip');
|
|
reduxDispatch(goToNextActivity());
|
|
}, [reduxDispatch]);
|
|
|
|
switch (step) {
|
|
case 'options':
|
|
return <Options start={start} cancel={cancel} />;
|
|
|
|
case 'migrating':
|
|
return <Migrating />;
|
|
|
|
case 'results':
|
|
return error ? <Fail error={error} /> : <Success />;
|
|
|
|
default:
|
|
throw new Error(`${step} is not recognized as a migration step.`);
|
|
}
|
|
};
|
|
|
|
interface Props {
|
|
wrapperProps: WrapperProps;
|
|
}
|
|
|
|
export const WrapperMigration: FunctionComponent<Props> = ({ wrapperProps }) => (
|
|
<OnboardingContainer
|
|
wrapperProps={wrapperProps}
|
|
header="Migrate from Insomnia Designer"
|
|
subHeader="Insomnia Designer and Core are now Insomnia!"
|
|
>
|
|
<MigrationBody />
|
|
</OnboardingContainer>
|
|
);
|