mirror of
https://github.com/Kong/insomnia.git
synced 2026-04-21 14:47:46 -04:00
consistent dropdown layout (#7536)
* folder actions * request sections * workspace sections * readd icon * default font error * add section to create new request dropdown * rearrange auth options * add file import to create menu * hints
This commit is contained in:
@@ -211,27 +211,27 @@ export const AuthDropdown: FC<Props> = ({ authentication, authTypes = defaultTyp
|
||||
name: string;
|
||||
}[];
|
||||
}[] = [
|
||||
{
|
||||
id: 'Auth Types',
|
||||
name: 'Auth Types',
|
||||
icon: 'lock',
|
||||
items: authTypesItems.filter(item => authTypes.includes(item.id)),
|
||||
},
|
||||
{
|
||||
id: 'Other',
|
||||
name: 'Other',
|
||||
icon: 'ellipsis-h',
|
||||
items: [
|
||||
{
|
||||
id: 'none',
|
||||
name: 'None',
|
||||
},
|
||||
{
|
||||
id: 'inherit',
|
||||
name: 'Inherit from parent',
|
||||
},
|
||||
{
|
||||
id: 'none',
|
||||
name: 'None',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'Auth Types',
|
||||
name: 'Auth Types',
|
||||
icon: 'lock',
|
||||
items: authTypesItems.filter(item => authTypes.includes(item.id)),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { IconName } from '@fortawesome/fontawesome-svg-core';
|
||||
import React, { Fragment, useCallback, useState } from 'react';
|
||||
import { Button, Menu, MenuItem, MenuTrigger, Popover } from 'react-aria-components';
|
||||
import { Button, Collection, Header, Menu, MenuItem, MenuTrigger, Popover, Section } from 'react-aria-components';
|
||||
import { useFetcher, useParams } from 'react-router-dom';
|
||||
|
||||
import { exportHarRequest } from '../../../common/har';
|
||||
@@ -20,6 +20,7 @@ import { getRequestActions } from '../../../plugins';
|
||||
import * as pluginContexts from '../../../plugins/context/index';
|
||||
import { useRequestMetaPatcher, useRequestPatcher } from '../../hooks/use-request';
|
||||
import { useRootLoaderData } from '../../routes/root';
|
||||
import { DropdownHint } from '../base/dropdown/dropdown-hint';
|
||||
import { Icon } from '../icon';
|
||||
import { showError, showModal, showPrompt } from '../modals';
|
||||
import { AlertModal } from '../modals/alert-modal';
|
||||
@@ -48,7 +49,6 @@ export const RequestActionsDropdown = ({
|
||||
const patchRequest = useRequestPatcher();
|
||||
const { hotKeyRegistry } = settings;
|
||||
const [actionPlugins, setActionPlugins] = useState<RequestAction[]>([]);
|
||||
const [loadingActions, setLoadingActions] = useState<Record<string, boolean>>({});
|
||||
const requestFetcher = useFetcher();
|
||||
const { organizationId, projectId, workspaceId } = useParams() as { organizationId: string; projectId: string; workspaceId: string };
|
||||
|
||||
@@ -79,9 +79,7 @@ export const RequestActionsDropdown = ({
|
||||
});
|
||||
};
|
||||
|
||||
const handlePluginClick = async ({ plugin, action, label }: RequestAction) => {
|
||||
setLoadingActions({ ...loadingActions, [label]: true });
|
||||
|
||||
const handlePluginClick = async ({ plugin, action }: RequestAction) => {
|
||||
try {
|
||||
const context = {
|
||||
...(pluginContexts.app.init(RENDER_PURPOSE_NO_RENDER)),
|
||||
@@ -98,7 +96,6 @@ export const RequestActionsDropdown = ({
|
||||
error,
|
||||
});
|
||||
}
|
||||
setLoadingActions({ ...loadingActions, [label]: false });
|
||||
};
|
||||
|
||||
const generateCode = () => {
|
||||
@@ -164,113 +161,160 @@ export const RequestActionsDropdown = ({
|
||||
const canGenerateCode = isRequest(request);
|
||||
|
||||
const codeGenerationActions: {
|
||||
id: string;
|
||||
name: string;
|
||||
id: string;
|
||||
icon: IconName;
|
||||
hint?: PlatformKeyCombinations;
|
||||
action: () => void;
|
||||
}[] = canGenerateCode ? [{
|
||||
id: 'GenerateCode',
|
||||
name: 'Generate Code',
|
||||
action: generateCode,
|
||||
icon: 'code',
|
||||
hint: hotKeyRegistry.request_showGenerateCodeEditor,
|
||||
}, {
|
||||
id: 'CopyAsCurl',
|
||||
name: 'Copy as cURL',
|
||||
action: copyAsCurl,
|
||||
icon: 'copy',
|
||||
}] : [];
|
||||
items: {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: IconName;
|
||||
hint?: PlatformKeyCombinations;
|
||||
action: () => void;
|
||||
}[];
|
||||
}[] = !canGenerateCode ? [] :
|
||||
[{
|
||||
name: 'Export',
|
||||
id: 'export',
|
||||
icon: 'file-export',
|
||||
items: [
|
||||
{
|
||||
id: 'GenerateCode',
|
||||
name: 'Generate Code',
|
||||
action: generateCode,
|
||||
icon: 'code',
|
||||
hint: hotKeyRegistry.request_showGenerateCodeEditor,
|
||||
},
|
||||
{
|
||||
id: 'CopyAsCurl',
|
||||
name: 'Copy as cURL',
|
||||
action: copyAsCurl,
|
||||
icon: 'copy',
|
||||
},
|
||||
],
|
||||
}];
|
||||
|
||||
const requestActionList: {
|
||||
id: string;
|
||||
name: string;
|
||||
id: string;
|
||||
icon: IconName;
|
||||
hint?: PlatformKeyCombinations;
|
||||
action: () => void;
|
||||
items: {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: IconName;
|
||||
hint?: PlatformKeyCombinations;
|
||||
action: () => void;
|
||||
}[];
|
||||
}[] = [
|
||||
{
|
||||
id: 'Duplicate',
|
||||
name: 'Duplicate',
|
||||
action: handleDuplicateRequest,
|
||||
icon: 'copy',
|
||||
},
|
||||
{
|
||||
id: 'Rename',
|
||||
name: 'Rename',
|
||||
action: handleRename,
|
||||
icon: 'edit',
|
||||
},
|
||||
{
|
||||
id: 'Delete',
|
||||
name: 'Delete',
|
||||
action: deleteRequest,
|
||||
icon: 'trash',
|
||||
},
|
||||
{
|
||||
id: 'Pin',
|
||||
name: isPinned ? 'Unpin' : 'Pin',
|
||||
action: togglePin,
|
||||
icon: 'thumbtack',
|
||||
},
|
||||
...codeGenerationActions,
|
||||
...actionPlugins.map((plugin: RequestAction) => ({
|
||||
id: plugin.label,
|
||||
name: plugin.label,
|
||||
icon: loadingActions[plugin.label] ? 'refresh' : plugin.icon as IconName || 'plug',
|
||||
action: () => handlePluginClick(plugin),
|
||||
})),
|
||||
{
|
||||
id: 'Settings',
|
||||
name: 'Settings',
|
||||
icon: 'gear',
|
||||
hint: hotKeyRegistry.request_showSettings,
|
||||
action: () => {
|
||||
setIsSettingsModalOpen(true);
|
||||
},
|
||||
name: 'Actions',
|
||||
id: 'actions',
|
||||
icon: 'cog',
|
||||
items: [
|
||||
{
|
||||
id: 'Pin',
|
||||
name: isPinned ? 'Unpin' : 'Pin',
|
||||
action: togglePin,
|
||||
icon: 'thumbtack',
|
||||
hint: hotKeyRegistry.request_togglePin,
|
||||
},
|
||||
{
|
||||
id: 'Duplicate',
|
||||
name: 'Duplicate',
|
||||
action: handleDuplicateRequest,
|
||||
icon: 'copy',
|
||||
hint: hotKeyRegistry.request_showDuplicate,
|
||||
},
|
||||
{
|
||||
id: 'Rename',
|
||||
name: 'Rename',
|
||||
action: handleRename,
|
||||
icon: 'edit',
|
||||
},
|
||||
{
|
||||
id: 'Delete',
|
||||
name: 'Delete',
|
||||
action: deleteRequest,
|
||||
icon: 'trash',
|
||||
hint: hotKeyRegistry.request_showDelete,
|
||||
},
|
||||
{
|
||||
id: 'Settings',
|
||||
name: 'Settings',
|
||||
icon: 'gear',
|
||||
hint: hotKeyRegistry.request_showSettings,
|
||||
action: () => {
|
||||
setIsSettingsModalOpen(true);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
...(actionPlugins.length > 0 ? [
|
||||
{
|
||||
name: 'Plugins',
|
||||
id: 'plugins',
|
||||
icon: 'plug' as IconName,
|
||||
items: actionPlugins.map(plugin => ({
|
||||
id: plugin.label,
|
||||
name: plugin.label,
|
||||
icon: plugin.icon as IconName || 'plug',
|
||||
action: () =>
|
||||
handlePluginClick(plugin),
|
||||
})),
|
||||
},
|
||||
] : []),
|
||||
];
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<MenuTrigger onOpenChange={isOpen => isOpen && onOpen()}>
|
||||
<Button
|
||||
data-testid={`Dropdown-${toKebabCase(request.name)}`}
|
||||
aria-label="Request Actions"
|
||||
className="opacity-0 items-center hover:opacity-100 focus:opacity-100 data-[pressed]:opacity-100 flex group-focus:opacity-100 group-hover:opacity-100 justify-center h-6 aspect-square aria-pressed:bg-[--hl-sm] rounded-sm text-[--color-font] hover:bg-[--hl-xs] focus:ring-inset ring-1 ring-transparent focus:ring-[--hl-md] transition-all text-sm"
|
||||
>
|
||||
<Icon icon="caret-down" />
|
||||
</Button>
|
||||
<Popover className="min-w-max">
|
||||
<Menu
|
||||
aria-label="Request Actions Menu"
|
||||
selectionMode="single"
|
||||
onAction={key =>
|
||||
requestActionList.find(({ id }) => key === id)?.action()
|
||||
}
|
||||
items={requestActionList}
|
||||
className="border select-none text-sm min-w-max border-solid border-[--hl-sm] shadow-lg bg-[--color-bg] py-2 rounded-md overflow-y-auto max-h-[85vh] focus:outline-none"
|
||||
<MenuTrigger onOpenChange={isOpen => isOpen && onOpen()}>
|
||||
<Button
|
||||
data-testid={`Dropdown-${toKebabCase(request.name)}`}
|
||||
aria-label="Request Actions"
|
||||
className="opacity-0 items-center hover:opacity-100 focus:opacity-100 data-[pressed]:opacity-100 flex group-focus:opacity-100 group-hover:opacity-100 justify-center h-6 aspect-square aria-pressed:bg-[--hl-sm] rounded-sm text-[--color-font] hover:bg-[--hl-xs] focus:ring-inset ring-1 ring-transparent focus:ring-[--hl-md] transition-all text-sm"
|
||||
>
|
||||
{item => (
|
||||
<MenuItem
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
className="flex gap-2 px-[--padding-md] aria-selected:font-bold items-center text-[--color-font] h-[--line-height-xs] w-full text-md whitespace-nowrap bg-transparent hover:bg-[--hl-sm] disabled:cursor-not-allowed focus:bg-[--hl-xs] focus:outline-none transition-colors"
|
||||
aria-label={item.name}
|
||||
>
|
||||
<Icon icon={item.icon} />
|
||||
<span>{item.name}</span>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Menu>
|
||||
</Popover>
|
||||
</MenuTrigger>
|
||||
{isSettingsModalOpen && (
|
||||
<RequestSettingsModal
|
||||
request={request}
|
||||
onHide={() => setIsSettingsModalOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
<Icon icon="caret-down" />
|
||||
</Button>
|
||||
<Popover className="min-w-max">
|
||||
<Menu
|
||||
aria-label="Request Actions Menu"
|
||||
selectionMode="single"
|
||||
onAction={key => requestActionList.find(i => i.items.find(a => a.id === key))?.items.find(a => a.id === key)?.action()}
|
||||
items={requestActionList}
|
||||
className="border select-none text-sm min-w-max border-solid border-[--hl-sm] shadow-lg bg-[--color-bg] py-2 rounded-md overflow-y-auto max-h-[85vh] focus:outline-none"
|
||||
>
|
||||
{section => (
|
||||
<Section className='flex-1 flex flex-col'>
|
||||
<Header className='pl-2 py-1 flex items-center gap-2 text-[--hl] text-xs uppercase'>
|
||||
<Icon icon={section.icon} /> <span>{section.name}</span>
|
||||
</Header>
|
||||
<Collection items={section.items}>
|
||||
{item => (
|
||||
<MenuItem
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
className="flex gap-2 px-[--padding-md] aria-selected:font-bold items-center text-[--color-font] h-[--line-height-xs] w-full text-md whitespace-nowrap bg-transparent hover:bg-[--hl-sm] disabled:cursor-not-allowed focus:bg-[--hl-xs] focus:outline-none transition-colors"
|
||||
aria-label={item.name}
|
||||
>
|
||||
<Icon icon={item.icon} />
|
||||
<span>{item.name}</span>
|
||||
{item.hint && (<DropdownHint keyBindings={item.hint} />)}
|
||||
</MenuItem>
|
||||
)}
|
||||
</Collection>
|
||||
</Section>
|
||||
)}
|
||||
</Menu>
|
||||
</Popover>
|
||||
</MenuTrigger>
|
||||
{
|
||||
isSettingsModalOpen && (
|
||||
<RequestSettingsModal
|
||||
request={request}
|
||||
onHide={() => setIsSettingsModalOpen(false)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</Fragment >
|
||||
);
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ import { CreateRequestType, useRequestGroupPatcher } from '../../hooks/use-reque
|
||||
import { useRootLoaderData } from '../../routes/root';
|
||||
import { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
import { type DropdownHandle, type DropdownProps } from '../base/dropdown';
|
||||
import { DropdownHint } from '../base/dropdown/dropdown-hint';
|
||||
import { Icon } from '../icon';
|
||||
import { showError, showModal, showPrompt } from '../modals';
|
||||
import { AskModal } from '../modals/ask-modal';
|
||||
@@ -151,96 +152,102 @@ export const RequestGroupActionsDropdown = ({
|
||||
hint?: PlatformKeyCombinations;
|
||||
action: () => void;
|
||||
}[];
|
||||
})[] = [
|
||||
})[] =
|
||||
[
|
||||
{
|
||||
name: 'Create',
|
||||
id: 'create',
|
||||
icon: 'plus',
|
||||
items: [
|
||||
{
|
||||
id: 'HTTP',
|
||||
name: 'HTTP Request',
|
||||
icon: 'plus-circle',
|
||||
hint: hotKeyRegistry.request_createHTTP,
|
||||
action: () => createRequest({
|
||||
requestType: 'HTTP',
|
||||
parentId: requestGroup._id,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'Event Stream',
|
||||
name: 'Event Stream Request',
|
||||
icon: 'plus-circle',
|
||||
action: () => createRequest({
|
||||
requestType: 'Event Stream',
|
||||
parentId: requestGroup._id,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'GraphQL Request',
|
||||
name: 'GraphQL Request',
|
||||
icon: 'plus-circle',
|
||||
action: () => createRequest({
|
||||
requestType: 'GraphQL',
|
||||
parentId: requestGroup._id,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'gRPC Request',
|
||||
name: 'gRPC Request',
|
||||
icon: 'plus-circle',
|
||||
action: () => createRequest({
|
||||
requestType: 'gRPC',
|
||||
parentId: requestGroup._id,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'WebSocket Request',
|
||||
name: 'WebSocket Request',
|
||||
icon: 'plus-circle',
|
||||
action: () => createRequest({
|
||||
requestType: 'WebSocket',
|
||||
parentId: requestGroup._id,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'From Curl',
|
||||
name: 'From Curl',
|
||||
icon: 'terminal',
|
||||
action: () => setPasteCurlModalOpen(true),
|
||||
|
||||
},
|
||||
{
|
||||
id: 'New Folder',
|
||||
name: 'New Folder',
|
||||
icon: 'folder',
|
||||
action: () =>
|
||||
showPrompt({
|
||||
title: 'New Folder',
|
||||
defaultValue: 'My Folder',
|
||||
submitName: 'Create',
|
||||
label: 'Name',
|
||||
selectText: true,
|
||||
onComplete: name => requestFetcher.submit({ parentId: requestGroup._id, name },
|
||||
{
|
||||
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/debug/request-group/new`,
|
||||
method: 'post',
|
||||
}),
|
||||
name: 'Create',
|
||||
id: 'create',
|
||||
icon: 'plus',
|
||||
items: [
|
||||
{
|
||||
id: 'HTTP',
|
||||
name: 'HTTP Request',
|
||||
icon: 'plus-circle',
|
||||
hint: hotKeyRegistry.request_createHTTP,
|
||||
action: () => createRequest({
|
||||
requestType: 'HTTP',
|
||||
parentId: requestGroup._id,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'Duplicate',
|
||||
name: 'Duplicate',
|
||||
icon: 'copy',
|
||||
hint: hotKeyRegistry.request_createHTTP,
|
||||
action: () => handleRequestGroupDuplicate(),
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'Event Stream',
|
||||
name: 'Event Stream Request (SSE)',
|
||||
icon: 'plus-circle',
|
||||
action: () => createRequest({
|
||||
requestType: 'Event Stream',
|
||||
parentId: requestGroup._id,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'GraphQL Request',
|
||||
name: 'GraphQL Request',
|
||||
icon: 'plus-circle',
|
||||
action: () => createRequest({
|
||||
requestType: 'GraphQL',
|
||||
parentId: requestGroup._id,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'gRPC Request',
|
||||
name: 'gRPC Request',
|
||||
icon: 'plus-circle',
|
||||
action: () => createRequest({
|
||||
requestType: 'gRPC',
|
||||
parentId: requestGroup._id,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'WebSocket Request',
|
||||
name: 'WebSocket Request',
|
||||
icon: 'plus-circle',
|
||||
action: () => createRequest({
|
||||
requestType: 'WebSocket',
|
||||
parentId: requestGroup._id,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'New Folder',
|
||||
name: 'New Folder',
|
||||
icon: 'folder',
|
||||
action: () =>
|
||||
showPrompt({
|
||||
title: 'New Folder',
|
||||
defaultValue: 'My Folder',
|
||||
submitName: 'Create',
|
||||
label: 'Name',
|
||||
selectText: true,
|
||||
onComplete: name => requestFetcher.submit({ parentId: requestGroup._id, name },
|
||||
{
|
||||
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/debug/request-group/new`,
|
||||
method: 'post',
|
||||
}),
|
||||
}),
|
||||
}],
|
||||
},
|
||||
{
|
||||
name: 'Manage',
|
||||
id: 'manage',
|
||||
name: 'Import',
|
||||
id: 'import',
|
||||
icon: 'file-import',
|
||||
items: [
|
||||
{
|
||||
id: 'From Curl',
|
||||
name: 'From Curl',
|
||||
icon: 'terminal',
|
||||
action: () => setPasteCurlModalOpen(true),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Actions',
|
||||
id: 'actions',
|
||||
icon: 'cog',
|
||||
items: [
|
||||
{
|
||||
id: 'Duplicate',
|
||||
name: 'Duplicate',
|
||||
icon: 'copy',
|
||||
action: () => handleRequestGroupDuplicate(),
|
||||
},
|
||||
{
|
||||
id: 'Rename',
|
||||
name: 'Rename',
|
||||
@@ -248,20 +255,6 @@ export const RequestGroupActionsDropdown = ({
|
||||
action: () =>
|
||||
handleRename(),
|
||||
},
|
||||
{
|
||||
id: 'Delete',
|
||||
name: 'Delete',
|
||||
icon: 'trash',
|
||||
action: () =>
|
||||
handleDeleteFolder(),
|
||||
},
|
||||
...actionPlugins.map(plugin => ({
|
||||
id: plugin.label,
|
||||
name: plugin.label,
|
||||
icon: plugin.icon as IconName || 'plug',
|
||||
action: () =>
|
||||
handlePluginClick(plugin),
|
||||
})),
|
||||
{
|
||||
id: 'Settings',
|
||||
name: 'Settings',
|
||||
@@ -269,9 +262,29 @@ export const RequestGroupActionsDropdown = ({
|
||||
action: () =>
|
||||
setIsSettingsModalOpen(true),
|
||||
},
|
||||
{
|
||||
id: 'Delete',
|
||||
name: 'Delete',
|
||||
icon: 'trash',
|
||||
action: () =>
|
||||
handleDeleteFolder(),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
...(actionPlugins.length > 0 ? [
|
||||
{
|
||||
name: 'Plugins',
|
||||
id: 'plugins',
|
||||
icon: 'plug' as IconName,
|
||||
items: actionPlugins.map(plugin => ({
|
||||
id: plugin.label,
|
||||
name: plugin.label,
|
||||
icon: plugin.icon as IconName || 'plug',
|
||||
action: () =>
|
||||
handlePluginClick(plugin),
|
||||
})),
|
||||
},
|
||||
] : []),
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -288,10 +301,7 @@ export const RequestGroupActionsDropdown = ({
|
||||
<Menu
|
||||
aria-label="Request Group Actions Menu"
|
||||
selectionMode="single"
|
||||
onAction={key => {
|
||||
const item = requestGroupActionItems[0].items.find(a => a.id === key) || requestGroupActionItems[1].items.find(a => a.id === key);
|
||||
item && item.action();
|
||||
}}
|
||||
onAction={key => requestGroupActionItems.find(i => i.items.find(a => a.id === key))?.items.find(a => a.id === key)?.action()}
|
||||
items={requestGroupActionItems}
|
||||
className="border select-none text-sm min-w-max border-solid border-[--hl-sm] shadow-lg bg-[--color-bg] py-2 rounded-md overflow-y-auto max-h-[85vh] focus:outline-none"
|
||||
>
|
||||
@@ -310,21 +320,21 @@ export const RequestGroupActionsDropdown = ({
|
||||
>
|
||||
<Icon icon={item.icon} />
|
||||
<span>{item.name}</span>
|
||||
{item.hint && (<DropdownHint keyBindings={item.hint} />)}
|
||||
</MenuItem>
|
||||
)}
|
||||
</Collection>
|
||||
</Section>
|
||||
)}
|
||||
|
||||
</Menu>
|
||||
</Popover>
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
</Popover>
|
||||
</MenuTrigger>
|
||||
{
|
||||
isSettingsModalOpen && (
|
||||
<RequestGroupSettingsModal
|
||||
requestGroup={requestGroup}
|
||||
onHide={() => setIsSettingsModalOpen(false)}
|
||||
/>
|
||||
<RequestGroupSettingsModal
|
||||
requestGroup={requestGroup}
|
||||
onHide={() => setIsSettingsModalOpen(false)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{isPasteCurlModalOpen && (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { IconName } from '@fortawesome/fontawesome-svg-core';
|
||||
import React, { FC, ReactNode, useCallback, useState } from 'react';
|
||||
import { Button, Dialog, Heading, Menu, MenuItem, MenuTrigger, Modal, ModalOverlay, Popover } from 'react-aria-components';
|
||||
import { Button, Collection, Dialog, Header, Heading, Menu, MenuItem, MenuTrigger, Modal, ModalOverlay, Popover, Section } from 'react-aria-components';
|
||||
import { useFetcher, useParams, useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import { getProductName } from '../../../common/constants';
|
||||
@@ -8,6 +8,7 @@ import { database as db } from '../../../common/database';
|
||||
import { exportMockServerToFile } from '../../../common/export';
|
||||
import { getWorkspaceLabel } from '../../../common/get-workspace-label';
|
||||
import { RENDER_PURPOSE_NO_RENDER } from '../../../common/render';
|
||||
import { PlatformKeyCombinations } from '../../../common/settings';
|
||||
import { isRemoteProject } from '../../../models/project';
|
||||
import { isRequest } from '../../../models/request';
|
||||
import { isRequestGroup } from '../../../models/request-group';
|
||||
@@ -19,6 +20,7 @@ import { invariant } from '../../../utils/invariant';
|
||||
import { useAIContext } from '../../context/app/ai-context';
|
||||
import { useRootLoaderData } from '../../routes/root';
|
||||
import { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
import { DropdownHint } from '../base/dropdown/dropdown-hint';
|
||||
import { Icon } from '../icon';
|
||||
import { InsomniaAI } from '../insomnia-ai-icon';
|
||||
import { useDocBodyKeyboardShortcuts } from '../keydown-binder';
|
||||
@@ -28,13 +30,6 @@ import { ImportModal } from '../modals/import-modal';
|
||||
import { WorkspaceDuplicateModal } from '../modals/workspace-duplicate-modal';
|
||||
import { WorkspaceSettingsModal } from '../modals/workspace-settings-modal';
|
||||
|
||||
interface WorkspaceActionItem {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: ReactNode;
|
||||
action: () => void;
|
||||
}
|
||||
|
||||
export const WorkspaceDropdown: FC = () => {
|
||||
const { organizationId, projectId, workspaceId } = useParams<{ organizationId: string; projectId: string; workspaceId: string }>();
|
||||
invariant(organizationId, 'Expected organizationId');
|
||||
@@ -45,13 +40,11 @@ export const WorkspaceDropdown: FC = () => {
|
||||
activeMockServer,
|
||||
projects,
|
||||
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
const activeWorkspaceName = activeWorkspace.name;
|
||||
|
||||
const [isDuplicateModalOpen, setIsDuplicateModalOpen] = useState(false);
|
||||
const [isImportModalOpen, setIsImportModalOpen] = useState(false);
|
||||
const [isExportModalOpen, setIsExportModalOpen] = useState(false);
|
||||
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false);
|
||||
const workspaceName = activeWorkspace.name;
|
||||
const projectName = activeProject.name ?? getProductName();
|
||||
const fetcher = useFetcher();
|
||||
const [isDeleteRemoteWorkspaceModalOpen, setIsDeleteRemoteWorkspaceModalOpen] = useState(false);
|
||||
const deleteWorkspaceFetcher = useFetcher();
|
||||
@@ -105,78 +98,106 @@ export const WorkspaceDropdown: FC = () => {
|
||||
|
||||
const isScratchpadWorkspace = isScratchpad(activeWorkspace);
|
||||
|
||||
const workspaceActionsList: WorkspaceActionItem[] = [
|
||||
...!isScratchpadWorkspace ? [{
|
||||
id: 'duplicate',
|
||||
name: 'Duplicate',
|
||||
icon: <Icon icon='bars' />,
|
||||
action: () => setIsDuplicateModalOpen(true),
|
||||
},
|
||||
const workspaceActionsList: {
|
||||
name: string;
|
||||
id: string;
|
||||
icon: IconName;
|
||||
items: {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: ReactNode;
|
||||
hint?: PlatformKeyCombinations;
|
||||
action: () => void;
|
||||
}[];
|
||||
}[] = [
|
||||
{
|
||||
id: 'rename',
|
||||
name: 'Rename',
|
||||
icon: <Icon icon='pen-to-square' />,
|
||||
action: () => {
|
||||
showPrompt({
|
||||
title: `Rename ${getWorkspaceLabel(activeWorkspace).singular}`,
|
||||
defaultValue: activeWorkspaceName,
|
||||
submitName: 'Rename',
|
||||
selectText: true,
|
||||
label: 'Name',
|
||||
onComplete: name =>
|
||||
fetcher.submit(
|
||||
{ name, workspaceId: activeWorkspace._id },
|
||||
{
|
||||
action: `/organization/${organizationId}/project/${activeWorkspace.parentId}/workspace/update`,
|
||||
method: 'post',
|
||||
encType: 'application/json',
|
||||
}
|
||||
),
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'delete',
|
||||
name: 'Delete',
|
||||
icon: <Icon icon='trash' />,
|
||||
action: () => {
|
||||
setIsDeleteRemoteWorkspaceModalOpen(true);
|
||||
},
|
||||
}] : [],
|
||||
{
|
||||
id: 'import',
|
||||
name: 'Import',
|
||||
icon: <Icon icon='file-import' />,
|
||||
action: () => setIsImportModalOpen(true),
|
||||
id: 'import',
|
||||
icon: 'cog',
|
||||
items: [{
|
||||
id: 'from-file',
|
||||
name: 'From File',
|
||||
icon: <Icon icon='file-import' />,
|
||||
action: () => setIsImportModalOpen(true),
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'export',
|
||||
name: 'Export',
|
||||
icon: <Icon icon='file-export' />,
|
||||
action: () => activeWorkspace.scope !== 'mock-server'
|
||||
? setIsExportModalOpen(true)
|
||||
: exportMockServerToFile(activeWorkspace),
|
||||
name: 'Actions',
|
||||
id: 'actions',
|
||||
icon: 'cog',
|
||||
items: [
|
||||
{
|
||||
id: 'duplicate',
|
||||
name: 'Duplicate',
|
||||
icon: <Icon icon='bars' />,
|
||||
action: () => setIsDuplicateModalOpen(true),
|
||||
},
|
||||
{
|
||||
id: 'rename',
|
||||
name: 'Rename',
|
||||
icon: <Icon icon='pen-to-square' />,
|
||||
action: () => showPrompt({
|
||||
title: `Rename ${getWorkspaceLabel(activeWorkspace).singular}`,
|
||||
defaultValue: activeWorkspace.name,
|
||||
submitName: 'Rename',
|
||||
selectText: true,
|
||||
label: 'Name',
|
||||
onComplete: name =>
|
||||
fetcher.submit(
|
||||
{ name, workspaceId: activeWorkspace._id },
|
||||
{
|
||||
action: `/organization/${organizationId}/project/${activeWorkspace.parentId}/workspace/update`,
|
||||
method: 'post',
|
||||
encType: 'application/json',
|
||||
}
|
||||
),
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'export',
|
||||
name: 'Export',
|
||||
icon: <Icon icon='file-export' />,
|
||||
action: () => activeWorkspace.scope !== 'mock-server'
|
||||
? setIsExportModalOpen(true)
|
||||
: exportMockServerToFile(activeWorkspace),
|
||||
},
|
||||
{
|
||||
id: 'settings',
|
||||
name: 'Settings',
|
||||
icon: <Icon icon='wrench' />,
|
||||
action: () => setIsSettingsModalOpen(true),
|
||||
},
|
||||
{
|
||||
id: 'delete',
|
||||
name: 'Delete',
|
||||
icon: <Icon icon='trash' />,
|
||||
action: () => setIsDeleteRemoteWorkspaceModalOpen(true),
|
||||
|
||||
},
|
||||
...userSession.id && access.enabled && activeWorkspace.scope === 'design' ? [{
|
||||
id: 'insomnia-ai/generate-test-suite',
|
||||
name: 'Auto-generate Tests For Collection',
|
||||
action: generateTests,
|
||||
icon: <span className='flex items-center py-0 px-[--padding-xs]'>
|
||||
<InsomniaAI />
|
||||
</span>,
|
||||
}] : [],
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'settings',
|
||||
name: 'Settings',
|
||||
icon: <Icon icon='wrench' />,
|
||||
action: () => setIsSettingsModalOpen(true),
|
||||
},
|
||||
...actionPlugins.map((p: WorkspaceAction) => ({
|
||||
id: p.label,
|
||||
name: p.label,
|
||||
icon: <Icon icon={(loadingActions[p.label] ? 'refresh' : p.icon || 'code') as IconName} />,
|
||||
action: () => handlePluginClick(p, activeWorkspace),
|
||||
})),
|
||||
...userSession.id && access.enabled && activeWorkspace.scope === 'design' ? [{
|
||||
id: 'insomnia-ai/generate-test-suite',
|
||||
name: 'Auto-generate Tests For Collection',
|
||||
action: generateTests,
|
||||
icon: <span className='flex items-center py-0 px-[--padding-xs]'>
|
||||
<InsomniaAI />
|
||||
</span>,
|
||||
}] : [],
|
||||
...(actionPlugins.length > 0 ? [
|
||||
{
|
||||
name: 'Plugins',
|
||||
id: 'plugins',
|
||||
icon: 'plug' as IconName,
|
||||
items: actionPlugins.map(plugin => ({
|
||||
id: plugin.label,
|
||||
name: plugin.label,
|
||||
icon: <Icon icon={plugin.icon as IconName || 'plug'} />,
|
||||
action: () =>
|
||||
handlePluginClick(plugin, activeWorkspace),
|
||||
})),
|
||||
},
|
||||
] : []),
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -187,34 +208,37 @@ export const WorkspaceDropdown: FC = () => {
|
||||
data-testid="workspace-context-dropdown"
|
||||
className="px-3 py-1 h-7 flex flex-1 items-center justify-center gap-2 aria-pressed:bg-[--hl-sm] rounded-sm text-[--color-font] hover:bg-[--hl-xs] focus:ring-inset ring-1 ring-transparent focus:ring-[--hl-md] transition-all text-sm truncate"
|
||||
>
|
||||
<span className="truncate" title={activeWorkspaceName}>{activeWorkspaceName}</span>
|
||||
<span className="truncate" title={activeWorkspace.name}>{activeWorkspace.name}</span>
|
||||
<Icon icon="caret-down" />
|
||||
</Button>
|
||||
<Popover className="min-w-max">
|
||||
<Menu
|
||||
aria-label="Create in project actions"
|
||||
selectionMode="single"
|
||||
onAction={key => {
|
||||
const item = workspaceActionsList.find(
|
||||
item => item.id === key
|
||||
);
|
||||
if (item) {
|
||||
item.action();
|
||||
}
|
||||
}}
|
||||
items={workspaceActionsList}
|
||||
onAction={key => workspaceActionsList.find(i => i.items.find(a => a.id === key))?.items.find(a => a.id === key)?.action()}
|
||||
items={isScratchpadWorkspace ? [] : workspaceActionsList}
|
||||
className="border select-none text-sm min-w-max border-solid border-[--hl-sm] shadow-lg bg-[--color-bg] py-2 rounded-md overflow-y-auto max-h-[85vh] focus:outline-none"
|
||||
>
|
||||
{item => (
|
||||
<MenuItem
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
className="flex gap-2 px-[--padding-md] aria-selected:font-bold items-center text-[--color-font] h-[--line-height-xs] w-full text-md whitespace-nowrap bg-transparent hover:bg-[--hl-sm] disabled:cursor-not-allowed focus:bg-[--hl-xs] focus:outline-none transition-colors"
|
||||
aria-label={item.name}
|
||||
>
|
||||
{item.icon}
|
||||
<span>{item.name}</span>
|
||||
</MenuItem>
|
||||
{section => (
|
||||
<Section className='flex-1 flex flex-col'>
|
||||
<Header className='pl-2 py-1 flex items-center gap-2 text-[--hl] text-xs uppercase'>
|
||||
<Icon icon={section.icon} /> <span>{section.name}</span>
|
||||
</Header>
|
||||
<Collection items={section.items}>
|
||||
{item => (
|
||||
<MenuItem
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
className="flex gap-2 px-[--padding-md] aria-selected:font-bold items-center text-[--color-font] h-[--line-height-xs] w-full text-md whitespace-nowrap bg-transparent hover:bg-[--hl-sm] disabled:cursor-not-allowed focus:bg-[--hl-xs] focus:outline-none transition-colors"
|
||||
aria-label={item.name}
|
||||
>
|
||||
{item.icon}
|
||||
<span>{item.name}</span>
|
||||
{item.hint && (<DropdownHint keyBindings={item.hint} />)}
|
||||
</MenuItem>
|
||||
)}
|
||||
</Collection>
|
||||
</Section>
|
||||
)}
|
||||
</Menu>
|
||||
</Popover>
|
||||
@@ -230,8 +254,8 @@ export const WorkspaceDropdown: FC = () => {
|
||||
<ImportModal
|
||||
onHide={() => setIsImportModalOpen(false)}
|
||||
from={{ type: 'file' }}
|
||||
projectName={projectName}
|
||||
workspaceName={workspaceName}
|
||||
projectName={activeProject.name ?? getProductName()}
|
||||
workspaceName={activeWorkspace.name}
|
||||
organizationId={organizationId}
|
||||
defaultProjectId={projectId}
|
||||
defaultWorkspaceId={workspaceId}
|
||||
|
||||
@@ -58,7 +58,7 @@ class SingleErrorBoundary extends PureComponent<Props, State> {
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
return (
|
||||
<div className={this.props.errorClassName ?? ''}>Render Failure: {this.state.error.message}</div>
|
||||
<div className={this.props.errorClassName ?? 'font-error'}>Render Failure: {this.state.error.message}</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,11 @@ import {
|
||||
Breadcrumb,
|
||||
Breadcrumbs,
|
||||
Button,
|
||||
Collection,
|
||||
DropIndicator,
|
||||
GridList,
|
||||
GridListItem,
|
||||
Header,
|
||||
Input,
|
||||
ListBox,
|
||||
ListBoxItem,
|
||||
@@ -17,6 +19,7 @@ import {
|
||||
MenuTrigger,
|
||||
Popover,
|
||||
SearchField,
|
||||
Section,
|
||||
Select,
|
||||
SelectValue,
|
||||
ToggleButton,
|
||||
@@ -36,7 +39,7 @@ import {
|
||||
useSearchParams,
|
||||
} from 'react-router-dom';
|
||||
|
||||
import { DEFAULT_SIDEBAR_SIZE, SORT_ORDERS, SortOrder, sortOrderName } from '../../common/constants';
|
||||
import { DEFAULT_SIDEBAR_SIZE, getProductName, SORT_ORDERS, SortOrder, sortOrderName } from '../../common/constants';
|
||||
import { ChangeBufferEvent, database as db } from '../../common/database';
|
||||
import { generateId } from '../../common/misc';
|
||||
import { PlatformKeyCombinations } from '../../common/settings';
|
||||
@@ -59,6 +62,7 @@ import {
|
||||
WebSocketRequest,
|
||||
} from '../../models/websocket-request';
|
||||
import { invariant } from '../../utils/invariant';
|
||||
import { DropdownHint } from '../components/base/dropdown/dropdown-hint';
|
||||
import { RequestActionsDropdown } from '../components/dropdowns/request-actions-dropdown';
|
||||
import { RequestGroupActionsDropdown } from '../components/dropdowns/request-group-actions-dropdown';
|
||||
import { WorkspaceDropdown } from '../components/dropdowns/workspace-dropdown';
|
||||
@@ -71,6 +75,7 @@ import { showModal, showPrompt } from '../components/modals';
|
||||
import { AskModal } from '../components/modals/ask-modal';
|
||||
import { CookiesModal } from '../components/modals/cookies-modal';
|
||||
import { GenerateCodeModal } from '../components/modals/generate-code-modal';
|
||||
import { ImportModal } from '../components/modals/import-modal';
|
||||
import { PasteCurlModal } from '../components/modals/paste-curl-modal';
|
||||
import { PromptModal } from '../components/modals/prompt-modal';
|
||||
import { RequestSettingsModal } from '../components/modals/request-settings-modal';
|
||||
@@ -200,6 +205,7 @@ export const Debug: FC = () => {
|
||||
const [isRequestSettingsModalOpen, setIsRequestSettingsModalOpen] =
|
||||
useState(false);
|
||||
const [isEnvironmentModalOpen, setEnvironmentModalOpen] = useState(false);
|
||||
const [isImportModalOpen, setIsImportModalOpen] = useState(false);
|
||||
const [isEnvironmentSelectOpen, setIsEnvironmentSelectOpen] = useState(false);
|
||||
const [isCertificatesModalOpen, setCertificatesModalOpen] = useState(false);
|
||||
|
||||
@@ -563,91 +569,113 @@ export const Debug: FC = () => {
|
||||
});
|
||||
|
||||
const createInCollectionActionList: {
|
||||
id: string;
|
||||
name: string;
|
||||
id: string;
|
||||
icon: IconName;
|
||||
hint?: PlatformKeyCombinations;
|
||||
action: () => void;
|
||||
}[] = [
|
||||
items: {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: IconName;
|
||||
hint?: PlatformKeyCombinations;
|
||||
action: () => void;
|
||||
}[];
|
||||
}[] =
|
||||
[
|
||||
{
|
||||
id: 'HTTP',
|
||||
name: 'HTTP Request',
|
||||
icon: 'plus-circle',
|
||||
hint: hotKeyRegistry.request_createHTTP,
|
||||
action: () =>
|
||||
createRequest({
|
||||
requestType: 'HTTP',
|
||||
parentId: workspaceId,
|
||||
}),
|
||||
name: 'Create',
|
||||
id: 'create',
|
||||
icon: 'plus',
|
||||
items: [
|
||||
{
|
||||
id: 'New Folder',
|
||||
name: 'New Folder',
|
||||
icon: 'folder',
|
||||
hint: hotKeyRegistry.request_showCreateFolder,
|
||||
action: () => showPrompt({
|
||||
title: 'New Folder',
|
||||
defaultValue: 'My Folder',
|
||||
submitName: 'Create',
|
||||
label: 'Name',
|
||||
selectText: true,
|
||||
onComplete: name =>
|
||||
requestFetcher.submit(
|
||||
{ parentId: workspaceId, name },
|
||||
{
|
||||
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/debug/request-group/new`,
|
||||
method: 'post',
|
||||
}
|
||||
),
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'HTTP',
|
||||
name: 'HTTP Request',
|
||||
icon: 'plus-circle',
|
||||
hint: hotKeyRegistry.request_createHTTP,
|
||||
action: () =>
|
||||
createRequest({
|
||||
requestType: 'HTTP',
|
||||
parentId: workspaceId,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'Event Stream',
|
||||
name: 'Event Stream Request (SSE)',
|
||||
icon: 'plus-circle',
|
||||
action: () =>
|
||||
createRequest({
|
||||
requestType: 'Event Stream',
|
||||
parentId: workspaceId,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'GraphQL Request',
|
||||
name: 'GraphQL Request',
|
||||
icon: 'plus-circle',
|
||||
action: () =>
|
||||
createRequest({
|
||||
requestType: 'GraphQL',
|
||||
parentId: workspaceId,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'gRPC Request',
|
||||
name: 'gRPC Request',
|
||||
icon: 'plus-circle',
|
||||
action: () =>
|
||||
createRequest({
|
||||
requestType: 'gRPC',
|
||||
parentId: workspaceId,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'WebSocket Request',
|
||||
name: 'WebSocket Request',
|
||||
icon: 'plus-circle',
|
||||
action: () =>
|
||||
createRequest({
|
||||
requestType: 'WebSocket',
|
||||
parentId: workspaceId,
|
||||
}),
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'Event Stream',
|
||||
name: 'Event Stream Request',
|
||||
icon: 'plus-circle',
|
||||
action: () =>
|
||||
createRequest({
|
||||
requestType: 'Event Stream',
|
||||
parentId: workspaceId,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'GraphQL Request',
|
||||
name: 'GraphQL Request',
|
||||
icon: 'plus-circle',
|
||||
action: () =>
|
||||
createRequest({
|
||||
requestType: 'GraphQL',
|
||||
parentId: workspaceId,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'gRPC Request',
|
||||
name: 'gRPC Request',
|
||||
icon: 'plus-circle',
|
||||
action: () =>
|
||||
createRequest({
|
||||
requestType: 'gRPC',
|
||||
parentId: workspaceId,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'WebSocket Request',
|
||||
name: 'WebSocket Request',
|
||||
icon: 'plus-circle',
|
||||
action: () =>
|
||||
createRequest({
|
||||
requestType: 'WebSocket',
|
||||
parentId: workspaceId,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'From Curl',
|
||||
name: 'From Curl',
|
||||
icon: 'terminal',
|
||||
action: () => setPasteCurlModalOpen(true),
|
||||
},
|
||||
{
|
||||
id: 'New Folder',
|
||||
name: 'New Folder',
|
||||
icon: 'folder',
|
||||
action: () =>
|
||||
showPrompt({
|
||||
title: 'New Folder',
|
||||
defaultValue: 'My Folder',
|
||||
submitName: 'Create',
|
||||
label: 'Name',
|
||||
selectText: true,
|
||||
onComplete: name =>
|
||||
requestFetcher.submit(
|
||||
{ parentId: workspaceId, name },
|
||||
{
|
||||
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/debug/request-group/new`,
|
||||
method: 'post',
|
||||
}
|
||||
),
|
||||
}),
|
||||
},
|
||||
];
|
||||
name: 'Import',
|
||||
id: 'import',
|
||||
icon: 'file-import',
|
||||
items: [{
|
||||
id: 'From Curl',
|
||||
name: 'From Curl',
|
||||
icon: 'terminal',
|
||||
action: () => setPasteCurlModalOpen(true),
|
||||
},
|
||||
{
|
||||
id: 'from-file',
|
||||
name: 'From File',
|
||||
icon: 'file-import',
|
||||
action: () => setIsImportModalOpen(true),
|
||||
}],
|
||||
}];
|
||||
|
||||
const environmentsList = [baseEnvironment, ...subEnvironments].map(environment => ({
|
||||
id: environment._id,
|
||||
@@ -978,25 +1006,30 @@ export const Debug: FC = () => {
|
||||
<Menu
|
||||
aria-label="Create a new request"
|
||||
selectionMode="single"
|
||||
onAction={key => {
|
||||
const item = createInCollectionActionList.find(item => item.id === key);
|
||||
if (item) {
|
||||
item.action();
|
||||
}
|
||||
}}
|
||||
onAction={key => createInCollectionActionList.find(i => i.items.find(a => a.id === key))?.items.find(a => a.id === key)?.action()}
|
||||
items={createInCollectionActionList}
|
||||
className="border select-none text-sm min-w-max border-solid border-[--hl-sm] shadow-lg bg-[--color-bg] py-2 rounded-md overflow-y-auto max-h-[85vh] focus:outline-none"
|
||||
>
|
||||
{item => (
|
||||
<MenuItem
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
className="flex gap-2 px-[--padding-md] aria-selected:font-bold items-center text-[--color-font] h-[--line-height-xs] w-full text-md whitespace-nowrap bg-transparent hover:bg-[--hl-sm] disabled:cursor-not-allowed focus:bg-[--hl-xs] focus:outline-none transition-colors"
|
||||
aria-label={item.name}
|
||||
>
|
||||
<Icon icon={item.icon} />
|
||||
<span>{item.name}</span>
|
||||
</MenuItem>
|
||||
{section => (
|
||||
<Section className='flex-1 flex flex-col'>
|
||||
<Header className='pl-2 py-1 flex items-center gap-2 text-[--hl] text-xs uppercase'>
|
||||
<Icon icon={section.icon} /> <span>{section.name}</span>
|
||||
</Header>
|
||||
<Collection items={section.items}>
|
||||
{item => (
|
||||
<MenuItem
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
className="flex gap-2 px-[--padding-md] aria-selected:font-bold items-center text-[--color-font] h-[--line-height-xs] w-full text-md whitespace-nowrap bg-transparent hover:bg-[--hl-sm] disabled:cursor-not-allowed focus:bg-[--hl-xs] focus:outline-none transition-colors"
|
||||
aria-label={item.name}
|
||||
>
|
||||
<Icon icon={item.icon} />
|
||||
<span>{item.name}</span>
|
||||
{item.hint && (<DropdownHint keyBindings={item.hint} />)}
|
||||
</MenuItem>
|
||||
)}
|
||||
</Collection>
|
||||
</Section>
|
||||
)}
|
||||
</Menu>
|
||||
</Popover>
|
||||
@@ -1250,6 +1283,17 @@ export const Debug: FC = () => {
|
||||
onClose={() => setEnvironmentModalOpen(false)}
|
||||
/>
|
||||
)}
|
||||
{isImportModalOpen && (
|
||||
<ImportModal
|
||||
onHide={() => setIsImportModalOpen(false)}
|
||||
from={{ type: 'file' }}
|
||||
projectName={activeProject.name ?? getProductName()}
|
||||
workspaceName={activeWorkspace.name}
|
||||
organizationId={organizationId}
|
||||
defaultProjectId={projectId}
|
||||
defaultWorkspaceId={workspaceId}
|
||||
/>
|
||||
)}
|
||||
{isCookieModalOpen && (
|
||||
<CookiesModal onHide={() => setIsCookieModalOpen(false)} />
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user