mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-07 22:18:33 +00:00
fix: refactor response examples to use MenuDropdown and Editable components (#6382)
* feat: use common dropdown component * fix: update example ui to match v3 * fix: test cases, bugs * fix: review comments * fix: review comments * fix: review * fix: file body/binary table within response examples * fix: file name, close btn not visible issue * fix: unnessary transition for three dots * fix: install missing deps in bruno-app * update example url when param is updated * empty commit * chore: update package-lock.json --------- Co-authored-by: Bijin A B <bijin@usebruno.com>
This commit is contained in:
@@ -5,13 +5,14 @@ const StyledWrapper = styled.div`
|
||||
|
||||
.menu-icon {
|
||||
color: ${(props) => props.theme.sidebar.dropdownIcon.color};
|
||||
visibility: hidden;
|
||||
|
||||
.dropdown {
|
||||
div[aria-expanded='true'] {
|
||||
visibility: visible;
|
||||
}
|
||||
div[aria-expanded='false'] {
|
||||
visibility: hidden;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,11 +37,7 @@ const StyledWrapper = styled.div`
|
||||
&.item-hovered {
|
||||
background: ${(props) => props.theme.sidebar.collection.item.hoverBg};
|
||||
.menu-icon {
|
||||
.dropdown {
|
||||
div[aria-expanded='false'] {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useRef, forwardRef, useEffect } from 'react';
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { addTab, makeTabPermanent } from 'providers/ReduxStore/slices/tabs';
|
||||
import {
|
||||
@@ -8,13 +8,16 @@ import {
|
||||
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { insertTaskIntoQueue } from 'providers/ReduxStore/slices/app';
|
||||
import { uuid } from 'utils/common';
|
||||
import { IconDots } from '@tabler/icons';
|
||||
import { IconDots, IconEdit, IconCopy, IconTrash, IconCode } from '@tabler/icons';
|
||||
import ExampleIcon from 'components/Icons/ExampleIcon';
|
||||
import range from 'lodash/range';
|
||||
import classnames from 'classnames';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import MenuDropdown from 'ui/MenuDropdown';
|
||||
import ActionIcon from 'ui/ActionIcon';
|
||||
import Modal from 'components/Modal';
|
||||
import DeleteResponseExampleModal from './DeleteResponseExampleModal';
|
||||
import GenerateCodeItem from '../GenerateCodeItem';
|
||||
import toast from 'react-hot-toast';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const ExampleItem = ({ example, item, collection }) => {
|
||||
@@ -25,8 +28,9 @@ const ExampleItem = ({ example, item, collection }) => {
|
||||
const [editName, setEditName] = useState(example.name);
|
||||
const [showRenameModal, setShowRenameModal] = useState(false);
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const dropdownTippyRef = useRef(null);
|
||||
const [generateCodeItemModalOpen, setGenerateCodeItemModalOpen] = useState(false);
|
||||
const exampleRef = useRef(null);
|
||||
const menuDropdownRef = useRef(null);
|
||||
|
||||
// Calculate indentation: item depth + 1 for examples
|
||||
const indents = range((item.depth || 0) + 1);
|
||||
@@ -48,9 +52,6 @@ const ExampleItem = ({ example, item, collection }) => {
|
||||
const handleRename = () => {
|
||||
setEditName(example.name); // Set current name when opening modal
|
||||
setShowRenameModal(true);
|
||||
if (dropdownTippyRef.current) {
|
||||
dropdownTippyRef.current.hide();
|
||||
}
|
||||
};
|
||||
|
||||
// Update editName when example changes
|
||||
@@ -93,26 +94,22 @@ const ExampleItem = ({ example, item, collection }) => {
|
||||
itemUid: item.uid,
|
||||
exampleIndex: clonedExampleIndex
|
||||
}));
|
||||
|
||||
if (dropdownTippyRef.current) {
|
||||
dropdownTippyRef.current.hide();
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
setShowDeleteModal(true);
|
||||
if (dropdownTippyRef.current) {
|
||||
dropdownTippyRef.current.hide();
|
||||
}
|
||||
};
|
||||
|
||||
const handleRightClick = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Show the dropdown menu programmatically
|
||||
if (dropdownTippyRef.current) {
|
||||
dropdownTippyRef.current.show();
|
||||
const handleGenerateCode = () => {
|
||||
// Check if example has a request URL
|
||||
if (
|
||||
(example?.request?.url !== '' && example?.request?.url !== undefined)
|
||||
|| (item?.request?.url !== '' && item?.request?.url !== undefined)
|
||||
|| (item?.draft?.request?.url !== undefined && item?.draft?.request?.url !== '')
|
||||
) {
|
||||
setGenerateCodeItemModalOpen(true);
|
||||
} else {
|
||||
toast.error('URL is required');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -130,17 +127,48 @@ const ExampleItem = ({ example, item, collection }) => {
|
||||
setShowRenameModal(false);
|
||||
};
|
||||
|
||||
const onDropdownCreate = (instance) => {
|
||||
dropdownTippyRef.current = instance;
|
||||
// Build menu items for MenuDropdown
|
||||
const buildMenuItems = () => {
|
||||
return [
|
||||
{
|
||||
id: 'rename',
|
||||
leftSection: IconEdit,
|
||||
label: 'Rename',
|
||||
onClick: handleRename,
|
||||
testId: 'response-example-rename-option'
|
||||
},
|
||||
{
|
||||
id: 'clone',
|
||||
leftSection: IconCopy,
|
||||
label: 'Clone',
|
||||
onClick: handleClone,
|
||||
testId: 'response-example-clone-option'
|
||||
},
|
||||
{
|
||||
id: 'generate-code',
|
||||
leftSection: IconCode,
|
||||
label: 'Generate Code',
|
||||
onClick: handleGenerateCode,
|
||||
testId: 'response-example-generate-code-option'
|
||||
},
|
||||
{ id: 'separator-1', type: 'divider' },
|
||||
{
|
||||
id: 'delete',
|
||||
leftSection: IconTrash,
|
||||
label: 'Delete',
|
||||
className: 'delete-item',
|
||||
onClick: handleDelete,
|
||||
testId: 'response-example-delete-option'
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
const MenuIcon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} data-testid="response-example-menu-icon">
|
||||
<IconDots size={22} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
// Handle right-click context menu
|
||||
const handleContextMenu = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
menuDropdownRef.current?.open();
|
||||
};
|
||||
|
||||
const itemRowClassName = classnames('flex collection-item-name relative items-center', {
|
||||
'item-focused-in-tab': isExampleActive
|
||||
@@ -152,7 +180,7 @@ const ExampleItem = ({ example, item, collection }) => {
|
||||
className={itemRowClassName}
|
||||
onClick={handleExampleClick}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
onContextMenu={handleRightClick}
|
||||
onContextMenu={handleContextMenu}
|
||||
>
|
||||
{indents && indents.length
|
||||
? indents.map((i) => (
|
||||
@@ -160,7 +188,6 @@ const ExampleItem = ({ example, item, collection }) => {
|
||||
className="indent-block"
|
||||
key={i}
|
||||
style={{ width: 16, minWidth: 16, height: '100%' }}
|
||||
onContextMenu={handleRightClick}
|
||||
>
|
||||
{/* Indent */}
|
||||
</div>
|
||||
@@ -169,45 +196,20 @@ const ExampleItem = ({ example, item, collection }) => {
|
||||
<div
|
||||
className="flex flex-grow items-center h-full overflow-hidden"
|
||||
style={{ paddingLeft: 8 }}
|
||||
onContextMenu={handleRightClick}
|
||||
>
|
||||
<div style={{ width: 16, minWidth: 16 }}></div>
|
||||
<ExampleIcon size={16} color="currentColor" className="mr-2 text-gray-400 flex-shrink-0" />
|
||||
<span className="item-name truncate text-gray-700 dark:text-gray-300 ">{example.name}</span>
|
||||
</div>
|
||||
<div className="menu-icon pr-2">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<MenuIcon />} placement="bottom-start">
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
dropdownTippyRef.current.hide();
|
||||
handleRename();
|
||||
}}
|
||||
data-testid="response-example-rename-option"
|
||||
>
|
||||
Rename
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
dropdownTippyRef.current.hide();
|
||||
handleClone();
|
||||
}}
|
||||
data-testid="response-example-clone-option"
|
||||
>
|
||||
Clone
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item text-red-600"
|
||||
onClick={(e) => {
|
||||
dropdownTippyRef.current.hide();
|
||||
handleDelete();
|
||||
}}
|
||||
data-testid="response-example-delete-option"
|
||||
>
|
||||
Delete
|
||||
</div>
|
||||
</Dropdown>
|
||||
<MenuDropdown
|
||||
ref={menuDropdownRef}
|
||||
items={buildMenuItems()}
|
||||
placement="bottom-start"
|
||||
data-testid="response-example-menu"
|
||||
>
|
||||
<IconDots size={22} data-testid="response-example-menu-icon" />
|
||||
</MenuDropdown>
|
||||
</div>
|
||||
|
||||
{showRenameModal && (
|
||||
@@ -250,6 +252,16 @@ const ExampleItem = ({ example, item, collection }) => {
|
||||
collection={collection}
|
||||
/>
|
||||
)}
|
||||
|
||||
{generateCodeItemModalOpen && (
|
||||
<GenerateCodeItem
|
||||
collectionUid={collection.uid}
|
||||
item={item}
|
||||
onClose={() => setGenerateCodeItemModalOpen(false)}
|
||||
isExample={true}
|
||||
exampleUid={example.uid}
|
||||
/>
|
||||
)}
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,13 +4,14 @@ const Wrapper = styled.div`
|
||||
position: relative;
|
||||
.menu-icon {
|
||||
color: ${(props) => props.theme.sidebar.dropdownIcon.color};
|
||||
visibility: hidden;
|
||||
|
||||
.dropdown {
|
||||
div[aria-expanded='true'] {
|
||||
visibility: visible;
|
||||
}
|
||||
div[aria-expanded='false'] {
|
||||
visibility: hidden;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,11 +98,7 @@ const Wrapper = styled.div`
|
||||
&.item-hovered {
|
||||
background: ${(props) => props.theme.sidebar.collection.item.hoverBg};
|
||||
.menu-icon {
|
||||
.dropdown {
|
||||
div[aria-expanded='false'] {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useRef, forwardRef, useEffect } from 'react';
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { getEmptyImage } from 'react-dnd-html5-backend';
|
||||
import range from 'lodash/range';
|
||||
import filter from 'lodash/filter';
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
IconCopy,
|
||||
IconClipboard,
|
||||
IconCode,
|
||||
IconPhoto,
|
||||
IconFolder,
|
||||
IconTrash,
|
||||
IconSettings,
|
||||
@@ -28,7 +27,7 @@ import { toggleCollectionItem, addResponseExample } from 'providers/ReduxStore/s
|
||||
import { insertTaskIntoQueue } from 'providers/ReduxStore/slices/app';
|
||||
import { uuid } from 'utils/common';
|
||||
import { copyRequest } from 'providers/ReduxStore/slices/app';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import MenuDropdown from 'ui/MenuDropdown';
|
||||
import NewRequest from 'components/Sidebar/NewRequest';
|
||||
import NewFolder from 'components/Sidebar/NewFolder';
|
||||
import RenameCollectionItem from './RenameCollectionItem';
|
||||
@@ -46,6 +45,7 @@ import NetworkError from 'components/ResponsePane/NetworkError/index';
|
||||
import CollectionItemInfo from './CollectionItemInfo/index';
|
||||
import CollectionItemIcon from './CollectionItemIcon';
|
||||
import ExampleItem from './ExampleItem';
|
||||
import ExampleIcon from 'components/Icons/ExampleIcon';
|
||||
import { scrollToTheActiveTab } from 'utils/tabs';
|
||||
import { isTabForItemActive as isTabForItemActiveSelector, isTabForItemPresent as isTabForItemPresentSelector } from 'src/selectors/tab';
|
||||
import { isEqual } from 'lodash';
|
||||
@@ -68,6 +68,7 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
|
||||
|
||||
// We use a single ref for drag and drop.
|
||||
const ref = useRef(null);
|
||||
const menuDropdownRef = useRef(null);
|
||||
|
||||
const [renameItemModalOpen, setRenameItemModalOpen] = useState(false);
|
||||
const [cloneItemModalOpen, setCloneItemModalOpen] = useState(false);
|
||||
@@ -182,15 +183,6 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
|
||||
})
|
||||
});
|
||||
|
||||
const dropdownTippyRef = useRef();
|
||||
const MenuIcon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<IconDots size={22} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const iconClassName = classnames({
|
||||
'rotate-90': !itemIsCollapsed
|
||||
});
|
||||
@@ -289,19 +281,138 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const handleRightClick = (event) => {
|
||||
const _menuDropdown = dropdownTippyRef.current;
|
||||
if (_menuDropdown) {
|
||||
let menuDropdownBehavior = 'show';
|
||||
if (_menuDropdown.state.isShown) {
|
||||
menuDropdownBehavior = 'hide';
|
||||
}
|
||||
_menuDropdown[menuDropdownBehavior]();
|
||||
}
|
||||
// Handle right-click context menu
|
||||
const handleContextMenu = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
menuDropdownRef.current?.open();
|
||||
};
|
||||
|
||||
let indents = range(item.depth);
|
||||
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||
|
||||
// Build menu items for MenuDropdown
|
||||
const buildMenuItems = () => {
|
||||
const items = [
|
||||
{
|
||||
id: 'info',
|
||||
leftSection: IconInfoCircle,
|
||||
label: 'Info',
|
||||
onClick: () => setItemInfoModalOpen(true)
|
||||
}
|
||||
];
|
||||
|
||||
if (isFolder) {
|
||||
items.push(
|
||||
{
|
||||
id: 'new-request',
|
||||
leftSection: IconFilePlus,
|
||||
label: 'New Request',
|
||||
onClick: () => setNewRequestModalOpen(true)
|
||||
},
|
||||
{
|
||||
id: 'new-folder',
|
||||
leftSection: IconFolderPlus,
|
||||
label: 'New Folder',
|
||||
onClick: () => setNewFolderModalOpen(true)
|
||||
},
|
||||
{
|
||||
id: 'run',
|
||||
leftSection: IconPlayerPlay,
|
||||
label: 'Run',
|
||||
onClick: () => setRunCollectionModalOpen(true)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
items.push(
|
||||
{
|
||||
id: 'clone',
|
||||
leftSection: IconCopy,
|
||||
label: 'Clone',
|
||||
onClick: () => setCloneItemModalOpen(true)
|
||||
},
|
||||
{
|
||||
id: 'copy',
|
||||
leftSection: IconCopy,
|
||||
label: 'Copy',
|
||||
onClick: handleCopyItem
|
||||
}
|
||||
);
|
||||
|
||||
if (isFolder && hasCopiedItems) {
|
||||
items.push({
|
||||
id: 'paste',
|
||||
leftSection: IconClipboard,
|
||||
label: 'Paste',
|
||||
onClick: handlePasteItem
|
||||
});
|
||||
}
|
||||
|
||||
items.push(
|
||||
{
|
||||
id: 'rename',
|
||||
leftSection: IconEdit,
|
||||
label: 'Rename',
|
||||
onClick: () => setRenameItemModalOpen(true)
|
||||
},
|
||||
{
|
||||
id: 'show-in-folder',
|
||||
leftSection: IconFolder,
|
||||
label: 'Show in Folder',
|
||||
onClick: handleShowInFolder
|
||||
}
|
||||
);
|
||||
|
||||
if (!isFolder && (item.type === 'http-request' || item.type === 'graphql-request')) {
|
||||
items.push({
|
||||
id: 'generate-code',
|
||||
leftSection: IconCode,
|
||||
label: 'Generate Code',
|
||||
onClick: handleGenerateCode
|
||||
});
|
||||
}
|
||||
|
||||
if (!isFolder && isItemARequest(item) && item.type === 'http-request') {
|
||||
items.push({
|
||||
id: 'create-example',
|
||||
leftSection: ExampleIcon,
|
||||
label: 'Create Example',
|
||||
onClick: () => setCreateExampleModalOpen(true)
|
||||
});
|
||||
}
|
||||
|
||||
items.push({ id: 'separator-1', type: 'divider' });
|
||||
|
||||
if (isFolder) {
|
||||
items.push(
|
||||
{
|
||||
id: 'settings',
|
||||
leftSection: IconSettings,
|
||||
label: 'Settings',
|
||||
onClick: viewFolderSettings
|
||||
},
|
||||
{
|
||||
id: 'open-terminal',
|
||||
leftSection: IconTerminal2,
|
||||
label: 'Open in Terminal',
|
||||
onClick: async () => {
|
||||
const folderCwd = item.pathname || collectionPathname;
|
||||
await openDevtoolsAndSwitchToTerminal(dispatch, folderCwd);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
items.push({
|
||||
id: 'delete',
|
||||
leftSection: IconTrash,
|
||||
label: 'Delete',
|
||||
className: 'delete-item',
|
||||
onClick: () => setDeleteItemModalOpen(true)
|
||||
});
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
const className = classnames('flex flex-col w-full', {
|
||||
'is-sidebar-dragging': isSidebarDragging
|
||||
@@ -382,9 +493,7 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
|
||||
const folderItems = sortByNameThenSequence(filter(item.items, (i) => isItemAFolder(i)));
|
||||
const requestItems = sortItemsBySequence(filter(item.items, (i) => isItemARequest(i)));
|
||||
|
||||
const handleGenerateCode = (e) => {
|
||||
e.stopPropagation();
|
||||
dropdownTippyRef.current.hide();
|
||||
const handleGenerateCode = () => {
|
||||
if (
|
||||
(item?.request?.url !== '')
|
||||
|| (item?.draft?.request?.url !== undefined && item?.draft?.request?.url !== '')
|
||||
@@ -412,15 +521,12 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
|
||||
};
|
||||
|
||||
const handleCopyItem = () => {
|
||||
dropdownTippyRef.current.hide();
|
||||
dispatch(copyRequest(item));
|
||||
const itemType = isFolder ? 'Folder' : 'Request';
|
||||
toast.success(`${itemType} copied to clipboard`);
|
||||
};
|
||||
|
||||
const handlePasteItem = () => {
|
||||
dropdownTippyRef.current.hide();
|
||||
|
||||
// Only allow paste into folders
|
||||
if (!isFolder) {
|
||||
toast.error('Paste is only available for folders');
|
||||
@@ -504,6 +610,7 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
|
||||
onKeyDown={handleKeyDown}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
onContextMenu={handleContextMenu}
|
||||
data-testid="sidebar-collection-item-row"
|
||||
>
|
||||
<div className="flex items-center h-full w-full">
|
||||
@@ -511,7 +618,6 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
|
||||
? indents.map((i) => (
|
||||
<div
|
||||
onClick={handleClick}
|
||||
onContextMenu={handleRightClick}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
className="indent-block"
|
||||
key={i}
|
||||
@@ -525,7 +631,6 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
|
||||
className="flex flex-grow items-center h-full overflow-hidden"
|
||||
style={{ paddingLeft: 8 }}
|
||||
onClick={handleClick}
|
||||
onContextMenu={handleRightClick}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
>
|
||||
<div style={{ width: 16, minWidth: 16 }}>
|
||||
@@ -559,186 +664,14 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
|
||||
</div>
|
||||
</div>
|
||||
<div className="menu-icon pr-2">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<MenuIcon />} placement="bottom-start">
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
dropdownTippyRef.current.hide();
|
||||
setItemInfoModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<span className="dropdown-icon">
|
||||
<IconInfoCircle size={16} strokeWidth={2} />
|
||||
</span>
|
||||
Info
|
||||
</div>
|
||||
{isFolder && (
|
||||
<>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
dropdownTippyRef.current.hide();
|
||||
setNewRequestModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<span className="dropdown-icon">
|
||||
<IconFilePlus size={16} strokeWidth={2} />
|
||||
</span>
|
||||
New Request
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
dropdownTippyRef.current.hide();
|
||||
setNewFolderModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<span className="dropdown-icon">
|
||||
<IconFolderPlus size={16} strokeWidth={2} />
|
||||
</span>
|
||||
New Folder
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
dropdownTippyRef.current.hide();
|
||||
setRunCollectionModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<span className="dropdown-icon">
|
||||
<IconPlayerPlay size={16} strokeWidth={2} />
|
||||
</span>
|
||||
Run
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
dropdownTippyRef.current.hide();
|
||||
setCloneItemModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<span className="dropdown-icon">
|
||||
<IconCopy size={16} strokeWidth={2} />
|
||||
</span>
|
||||
Clone
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={handleCopyItem}
|
||||
>
|
||||
<span className="dropdown-icon">
|
||||
<IconCopy size={16} strokeWidth={2} />
|
||||
</span>
|
||||
Copy
|
||||
</div>
|
||||
{isFolder && hasCopiedItems && (
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={handlePasteItem}
|
||||
>
|
||||
<span className="dropdown-icon">
|
||||
<IconClipboard size={16} strokeWidth={2} />
|
||||
</span>
|
||||
Paste
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
dropdownTippyRef.current.hide();
|
||||
setRenameItemModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<span className="dropdown-icon">
|
||||
<IconEdit size={16} strokeWidth={2} />
|
||||
</span>
|
||||
Rename
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
dropdownTippyRef.current.hide();
|
||||
handleShowInFolder();
|
||||
}}
|
||||
>
|
||||
<span className="dropdown-icon">
|
||||
<IconFolder size={16} strokeWidth={2} />
|
||||
</span>
|
||||
Show in Folder
|
||||
</div>
|
||||
{!isFolder && (item.type === 'http-request' || item.type === 'graphql-request') && (
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
handleGenerateCode(e);
|
||||
}}
|
||||
>
|
||||
<span className="dropdown-icon">
|
||||
<IconCode size={16} strokeWidth={2} />
|
||||
</span>
|
||||
Generate Code
|
||||
</div>
|
||||
)}
|
||||
{!isFolder && isItemARequest(item) && item.type === 'http-request' && (
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
dropdownTippyRef.current.hide();
|
||||
setCreateExampleModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<span className="dropdown-icon">
|
||||
<IconPhoto size={16} strokeWidth={2} />
|
||||
</span>
|
||||
Create Example
|
||||
</div>
|
||||
)}
|
||||
<div className="dropdown-separator"></div>
|
||||
{isFolder && (
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
dropdownTippyRef.current.hide();
|
||||
viewFolderSettings();
|
||||
}}
|
||||
>
|
||||
<span className="dropdown-icon">
|
||||
<IconSettings size={16} strokeWidth={2} />
|
||||
</span>
|
||||
Settings
|
||||
</div>
|
||||
)}
|
||||
{isFolder && (
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={async (e) => {
|
||||
dropdownTippyRef.current.hide();
|
||||
// Get folder pathname
|
||||
const folderCwd = item.pathname || collectionPathname;
|
||||
await openDevtoolsAndSwitchToTerminal(dispatch, folderCwd);
|
||||
}}
|
||||
>
|
||||
<span className="dropdown-icon">
|
||||
<IconTerminal2 size={16} strokeWidth={2} />
|
||||
</span>
|
||||
Open in Terminal
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="dropdown-item delete-item"
|
||||
onClick={(e) => {
|
||||
dropdownTippyRef.current.hide();
|
||||
setDeleteItemModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<span className="dropdown-icon">
|
||||
<IconTrash size={16} strokeWidth={2} />
|
||||
</span>
|
||||
Delete
|
||||
</div>
|
||||
</Dropdown>
|
||||
<MenuDropdown
|
||||
ref={menuDropdownRef}
|
||||
items={buildMenuItems()}
|
||||
placement="bottom-start"
|
||||
data-testid="collection-item-menu"
|
||||
>
|
||||
<IconDots size={22} />
|
||||
</MenuDropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user