insomnia/packages/insomnia-app/app/ui/components/dropdowns/workspace-dropdown.tsx

213 lines
7.2 KiB
TypeScript

import { autoBindMethodsForReact } from 'class-autobind-decorator';
import classnames from 'classnames';
import { HotKeyRegistry } from 'insomnia-common';
import React, { PureComponent } from 'react';
import { AUTOBIND_CFG } from '../../../common/constants';
import { database as db } from '../../../common/database';
import { getWorkspaceLabel } from '../../../common/get-workspace-label';
import { hotKeyRefs } from '../../../common/hotkeys';
import { executeHotKey } from '../../../common/hotkeys-listener';
import { RENDER_PURPOSE_NO_RENDER } from '../../../common/render';
import { ApiSpec } from '../../../models/api-spec';
import type { Environment } from '../../../models/environment';
import { Project } from '../../../models/project';
import { isRequest } from '../../../models/request';
import { isRequestGroup } from '../../../models/request-group';
import { isDesign, Workspace } from '../../../models/workspace';
import type { WorkspaceAction } from '../../../plugins';
import { ConfigGenerator, getConfigGenerators, getWorkspaceActions } from '../../../plugins';
import * as pluginContexts from '../../../plugins/context';
import { Dropdown } from '../base/dropdown/dropdown';
import { DropdownButton } from '../base/dropdown/dropdown-button';
import { DropdownDivider } from '../base/dropdown/dropdown-divider';
import { DropdownHint } from '../base/dropdown/dropdown-hint';
import { DropdownItem } from '../base/dropdown/dropdown-item';
import { KeydownBinder } from '../keydown-binder';
import { showError, showModal } from '../modals';
import { showGenerateConfigModal } from '../modals/generate-config-modal';
import { SettingsModal, TAB_INDEX_EXPORT } from '../modals/settings-modal';
import { WorkspaceSettingsModal } from '../modals/workspace-settings-modal';
interface Props {
activeEnvironment: Environment | null;
activeWorkspace: Workspace;
activeWorkspaceName?: string;
activeApiSpec: ApiSpec;
activeProject: Project;
hotKeyRegistry: HotKeyRegistry;
isLoading: boolean;
className?: string;
}
interface State {
actionPlugins: WorkspaceAction[];
configGeneratorPlugins: ConfigGenerator[];
loadingActions: Record<string, boolean>;
}
@autoBindMethodsForReact(AUTOBIND_CFG)
export class WorkspaceDropdown extends PureComponent<Props, State> {
_dropdown: Dropdown | null = null;
state: State = {
actionPlugins: [],
configGeneratorPlugins: [],
loadingActions: {},
};
async _handlePluginClick(p: WorkspaceAction) {
this.setState(state => ({
loadingActions: { ...state.loadingActions, [p.label]: true },
}));
const { activeEnvironment, activeWorkspace, activeProject } = this.props;
try {
const activeEnvironmentId = activeEnvironment ? activeEnvironment._id : null;
const context = {
...(pluginContexts.app.init(RENDER_PURPOSE_NO_RENDER) as Record<string, any>),
...pluginContexts.data.init(activeProject._id),
...(pluginContexts.store.init(p.plugin) as Record<string, any>),
...(pluginContexts.network.init(activeEnvironmentId) as Record<string, any>),
};
const docs = await db.withDescendants(activeWorkspace);
const requests = docs
.filter(isRequest)
.filter(doc => (
!doc.isPrivate
));
const requestGroups = docs.filter(isRequestGroup);
await p.action(context, {
requestGroups,
requests,
workspace: activeWorkspace,
});
} catch (err) {
showError({
title: 'Plugin Action Failed',
error: err,
});
}
this.setState(state => ({
loadingActions: { ...state.loadingActions, [p.label]: false },
}));
this._dropdown?.hide();
}
async _handleDropdownOpen() {
const actionPlugins = await getWorkspaceActions();
const configGeneratorPlugins = await getConfigGenerators();
this.setState({
actionPlugins,
configGeneratorPlugins,
});
}
_setDropdownRef(n: Dropdown) {
this._dropdown = n;
}
static _handleShowExport() {
showModal(SettingsModal, TAB_INDEX_EXPORT);
}
static _handleShowWorkspaceSettings() {
showModal(WorkspaceSettingsModal);
}
_handleKeydown(event: KeyboardEvent) {
executeHotKey(event, hotKeyRefs.TOGGLE_MAIN_MENU, () => {
this._dropdown?.toggle(true);
});
}
async _handleGenerateConfig(label: string) {
const { activeApiSpec } = this.props;
showGenerateConfigModal({
apiSpec: activeApiSpec,
activeTabLabel: label,
});
}
render() {
const {
activeWorkspaceName,
className,
activeWorkspace,
isLoading,
hotKeyRegistry,
...other
} = this.props;
const classes = classnames(className, 'wide', 'workspace-dropdown');
const { actionPlugins, loadingActions, configGeneratorPlugins } = this.state;
return (
<KeydownBinder onKeydown={this._handleKeydown}>
<Dropdown
beside
ref={this._setDropdownRef}
className={classes}
onOpen={this._handleDropdownOpen}
// @ts-expect-error -- TSCONVERSION appears to be genuine
onHide={this._handleDropdownHide}
{...(other as Record<string, any>)}
>
<DropdownButton className="row">
<div
className="ellipsis"
style={{
maxWidth: '400px',
}}
title={activeWorkspaceName}
>
{activeWorkspaceName}
</div>
<i className="fa fa-caret-down space-left" />
{isLoading ? <i className="fa fa-refresh fa-spin space-left" /> : null}
</DropdownButton>
<DropdownItem onClick={WorkspaceDropdown._handleShowWorkspaceSettings}>
<i className="fa fa-wrench" /> {getWorkspaceLabel(activeWorkspace).singular} Settings
<DropdownHint keyBindings={hotKeyRegistry[hotKeyRefs.WORKSPACE_SHOW_SETTINGS.id]} />
</DropdownItem>
<DropdownItem onClick={WorkspaceDropdown._handleShowExport}>
<i className="fa fa-share" /> Import/Export
</DropdownItem>
{actionPlugins.length > 0 && <DropdownDivider>Plugins</DropdownDivider>}
{actionPlugins.map((p: WorkspaceAction) => (
<DropdownItem
key={p.label}
onClick={() => this._handlePluginClick(p)}
stayOpenAfterClick
>
{loadingActions[p.label] ? (
<i className="fa fa-refresh fa-spin" />
) : (
<i className={classnames('fa', p.icon || 'fa-code')} />
)}
{p.label}
</DropdownItem>
))}
{isDesign(activeWorkspace) && (
<>
{configGeneratorPlugins.length > 0 && (
<DropdownDivider>Config Generators</DropdownDivider>
)}
{configGeneratorPlugins.map((p: ConfigGenerator) => (
<DropdownItem
key="generateConfig"
onClick={this._handleGenerateConfig}
value={p.label}
>
<i className="fa fa-code" />
{p.label}
</DropdownItem>
))}
</>
)}
</Dropdown>
</KeydownBinder>
);
}
}