feat: Move requests between collections #3320

test: update e2e test case for moving request from one collection to another

test: updated the tests

test: added more test cases

test: e2e test updated

test: fixed test case

test: fixed test cause for folder

fix: add grpc-request to clone-folder

fix: removed handleCrossCollectionItemMove method

test: updated e2e test cases

fix: removed cross-collection gurard statement

format: revert format

fix: UX changes for collection drag and drop
This commit is contained in:
jayakrishnancn
2025-08-23 16:57:42 +05:30
committed by sanish-bruno
parent 5b716cbe60
commit 0bce203851
7 changed files with 434 additions and 29 deletions

View File

@@ -0,0 +1,154 @@
import { test, expect } from '../../playwright';
test.describe('Cross-Collection Drag and Drop', () => {
test('Verify request drag and drop', async ({ pageWithUserData: page, createTmpDir }) => {
// Create first collection - click dropdown menu first
await page.locator('.dropdown-icon').click();
await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click();
await page.getByLabel('Name').fill('source-collection');
await page.getByLabel('Location').fill(await createTmpDir('source-collection'));
await page.getByRole('button', { name: 'Create', exact: true }).click();
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' })).toBeVisible();
await page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' }).click();
await page.getByLabel('Safe Mode').check();
await page.getByRole('button', { name: 'Save' }).click();
// Create a request in the first collection
await page.locator('#create-new-tab').getByRole('img').click();
await page.getByPlaceholder('Request Name').fill('test-request');
await page.locator('#new-request-url .CodeMirror').click();
await page.locator('textarea').fill('https://httpbin.org/get');
await page.getByRole('button', { name: 'Create' }).click();
await expect(page.locator('.collection-item-name').filter({ hasText: 'test-request' })).toBeVisible();
// Create second collection - click dropdown menu first
await page.locator('.dropdown-icon').click();
await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click();
await page.getByLabel('Name').fill('target-collection');
await page.getByLabel('Location').fill(await createTmpDir('target-collection'));
await page.getByRole('button', { name: 'Create', exact: true }).click();
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' })).toBeVisible();
await page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' }).click();
await page.getByLabel('Safe Mode').check();
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' })).toBeVisible();
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' })).toBeVisible();
// Locate the request in source collection
const sourceRequest = page.locator('.collection-item-name').filter({ hasText: 'test-request' });
await expect(sourceRequest).toBeVisible();
// Locate the target collection area (the collection name element)
const targetCollection = page.locator('.collection-name').filter({ hasText: 'target-collection' });
await expect(targetCollection).toBeVisible();
// Perform drag and drop operation
await sourceRequest.dragTo(targetCollection);
// Verify the request has been moved to the target collection
// Click on target collection to expand it if needed
await page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' }).click();
// Check that the request now appears under target collection
const targetCollectionContainer = page
.locator('.collection-name')
.filter({ hasText: 'target-collection' })
.locator('..');
await expect(
targetCollectionContainer.locator('.collection-item-name').filter({ hasText: 'test-request' })
).toBeVisible();
// Verify the request is no longer in the source collection
const sourceCollectionContainer = page
.locator('.collection-name')
.filter({ hasText: 'source-collection' })
.locator('..');
await expect(
sourceCollectionContainer.locator('.collection-item-name').filter({ hasText: 'test-request' })
).not.toBeVisible();
});
test('Expected to show error toast message, when duplicate request found in drop location', async ({
pageWithUserData: page,
createTmpDir
}) => {
// Create first collection (source-collection)
await page.locator('.dropdown-icon').click();
await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click();
await page.getByLabel('Name').fill('source-collection');
await page.getByLabel('Location').fill(await createTmpDir('source-collection'));
await page.getByRole('button', { name: 'Create', exact: true }).click();
// Open collection
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' })).toBeVisible();
await page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' }).click();
await page.getByLabel('Safe Mode').check();
await page.getByRole('button', { name: 'Save' }).click();
// Create a request in the first collection (request-1)
await page.locator('#create-new-tab').getByRole('img').click();
await page.getByPlaceholder('Request Name').fill('request-1');
await page.locator('#new-request-url .CodeMirror').click();
await page.locator('textarea').fill('https://httpbin.org/get');
await page.getByRole('button', { name: 'Create' }).click();
// check if request-1 is created and visible in sidebar
await expect(page.locator('.collection-item-name').filter({ hasText: 'request-1' })).toBeVisible();
// Create second collection (target-collection)
await page.locator('.dropdown-icon').click();
await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click();
await page.getByLabel('Name').fill('target-collection');
await page.getByLabel('Location').fill(await createTmpDir('target-collection'));
await page.getByRole('button', { name: 'Create', exact: true }).click();
// Open collection
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' })).toBeVisible();
await page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' }).click();
await page.getByLabel('Safe Mode').check();
await page.getByRole('button', { name: 'Save' }).click();
// Create a request in the target collection with the same name (request-1)
await page.locator('#create-new-tab').getByRole('img').click();
await page.getByPlaceholder('Request Name').fill('request-1');
await page.locator('#new-request-url .CodeMirror').click();
await page.locator('textarea').fill('https://httpbin.org/post');
await page.getByRole('button', { name: 'Create' }).click();
// Go back to source collection to drag the request
await page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' }).click();
const sourceRequest = page.locator('.collection-item-name').filter({ hasText: 'request-1' }).first();
await expect(sourceRequest).toBeVisible();
// Locate the target collection area
const targetCollection = page.locator('.collection-name').filter({ hasText: 'target-collection' });
await expect(targetCollection).toBeVisible();
// Perform drag and drop operation to target-collection
await sourceRequest.dragTo(targetCollection);
// check for error toast notification
await expect(page.getByText(/Error: Cannot copy.*already exists/i)).toBeVisible();
// source and target collection request should remain unchanged
const targetCollectionContainer = page
.locator('.collection-name')
.filter({ hasText: 'target-collection' })
.locator('..');
await expect(
targetCollectionContainer.locator('.collection-item-name').filter({ hasText: 'request-1' })
).toBeVisible();
const sourceCollectionContainer = page
.locator('.collection-name')
.filter({ hasText: 'source-collection' })
.locator('..');
await expect(
sourceCollectionContainer.locator('.collection-item-name').filter({ hasText: 'request-1' })
).toBeVisible();
});
});

View File

@@ -0,0 +1,235 @@
import { test, expect } from '../../playwright';
test.describe('Cross-Collection Drag and Drop for folder', () => {
test('Verify cross-collection folder drag and drop', async ({ pageWithUserData: page, createTmpDir }) => {
// Create first collection - click dropdown menu first
await page.locator('.dropdown-icon').click();
await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click();
await page.getByLabel('Name').fill('source-collection');
await page.getByLabel('Location').fill(await createTmpDir('source-collection'));
await page.getByRole('button', { name: 'Create', exact: true }).click();
// Wait for collection to appear and click on it
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' })).toBeVisible();
await page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' }).click();
await page.getByLabel('Safe Mode').check();
await page.getByRole('button', { name: 'Save' }).click();
// Create a folder in the first collection
// Look for the collection menu button (usually three dots or similar)
await page.locator('.collection-actions').hover();
await page.locator('.collection-actions .icon').click();
await page.locator('.dropdown-item').filter({ hasText: 'New Folder' }).click();
// Fill folder name in the modal
await expect(page.locator('#collection-name')).toBeVisible();
await page.locator('#collection-name').fill('test-folder');
await page.getByRole('button', { name: 'Create' }).click();
// Wait for the folder to be created and appear in the sidebar
await page.waitForTimeout(2000);
await expect(page.locator('.collection-item-name').filter({ hasText: 'test-folder' })).toBeVisible();
// Add a request to the folder to make it more realistic
await page.locator('.collection-item-name').filter({ hasText: 'test-folder' }).click({ button: 'right' });
await page.locator('.dropdown-item').filter({ hasText: 'New Request' }).click();
await page.getByPlaceholder('Request Name').fill('test-request-in-folder');
await page.locator('#new-request-url .CodeMirror').click();
await page.locator('textarea').fill('https://httpbin.org/get');
await page.getByRole('button', { name: 'Create' }).click();
// Wait for the request to be created
await page.waitForTimeout(1000);
// Expand the folder to see the request inside
await page.locator('.collection-item-name').filter({ hasText: 'test-folder' }).click();
await page.waitForTimeout(500);
await expect(page.locator('.collection-item-name').filter({ hasText: 'test-request-in-folder' })).toBeVisible();
// Create second collection - click dropdown menu first
await page.locator('.dropdown-icon').click();
await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click();
await page.getByLabel('Name').fill('target-collection');
await page.getByLabel('Location').fill(await createTmpDir('target-collection'));
await page.getByRole('button', { name: 'Create', exact: true }).click();
// Wait for second collection to appear and click on it
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' })).toBeVisible();
await page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' }).click();
await page.getByLabel('Safe Mode').check();
await page.getByRole('button', { name: 'Save' }).click();
// Wait for both collections to be visible in sidebar
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' })).toBeVisible();
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' })).toBeVisible();
// Locate the folder in source collection
const sourceFolder = page.locator('.collection-item-name').filter({ hasText: 'test-folder' });
await expect(sourceFolder).toBeVisible();
// Locate the target collection area (the collection name element)
const targetCollection = page.locator('.collection-name').filter({ hasText: 'target-collection' });
await expect(targetCollection).toBeVisible();
// Perform drag and drop operation
await sourceFolder.dragTo(targetCollection);
// Wait for the operation to complete
await page.waitForTimeout(3000);
// Verify the folder has been moved to the target collection
// Click on target collection to expand it if needed
await page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' }).click();
await page.waitForTimeout(1000);
// Check that the folder now appears under target collection
const targetCollectionContainer = page
.locator('.collection-name')
.filter({ hasText: 'target-collection' })
.locator('..');
await expect(
targetCollectionContainer.locator('.collection-item-name').filter({ hasText: 'test-folder' })
).toBeVisible();
// Expand the moved folder to verify the request inside is also moved
await targetCollectionContainer.locator('.collection-item-name').filter({ hasText: 'test-folder' }).click();
await page.waitForTimeout(500);
await expect(
targetCollectionContainer.locator('.collection-item-name').filter({ hasText: 'test-request-in-folder' })
).toBeVisible();
// Verify the folder is no longer in the source collection
const sourceCollectionContainer = page
.locator('.collection-name')
.filter({ hasText: 'source-collection' })
.locator('..');
await expect(
sourceCollectionContainer.locator('.collection-item-name').filter({ hasText: 'test-folder' })
).not.toBeVisible();
// Verify the request is also no longer in the source collection
await expect(
sourceCollectionContainer.locator('.collection-item-name').filter({ hasText: 'test-request-in-folder' })
).not.toBeVisible();
});
test('Verify cross-collection folder drag and drop, a duplicate folder exist. expected to throw error toast', async ({
pageWithUserData: page,
createTmpDir
}) => {
// Create first collection (source) - use unique names for this test
await page.locator('.dropdown-icon').click();
await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click();
await page.getByLabel('Name').fill('source-collection');
await page.getByLabel('Location').fill(await createTmpDir('source-collection'));
await page.getByRole('button', { name: 'Create', exact: true }).click();
// Wait for collection to appear and click on it
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' })).toBeVisible();
await page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' }).click();
await page.getByLabel('Safe Mode').check();
await page.getByRole('button', { name: 'Save' }).click();
// Create a folder in the first collection
await page
.locator('.collection-name')
.filter({ hasText: 'source-collection' })
.locator('..')
.locator('.collection-actions')
.hover();
await page
.locator('.collection-name')
.filter({ hasText: 'source-collection' })
.locator('..')
.locator('.collection-actions .icon')
.click();
await page.locator('.dropdown-item').filter({ hasText: 'New Folder' }).click();
await expect(page.locator('#collection-name')).toBeVisible();
await page.locator('#collection-name').fill('folder-1');
await page.getByRole('button', { name: 'Create' }).click();
await expect(page.locator('.collection-item-name').filter({ hasText: 'folder-1' })).toBeVisible();
// Add a request to the folder to make it more realistic
await page.locator('.collection-item-name').filter({ hasText: 'folder-1' }).click({ button: 'right' });
await page.locator('.dropdown-item').filter({ hasText: 'New Request' }).click();
await page.getByPlaceholder('Request Name').fill('http-request');
await page.locator('#new-request-url .CodeMirror').click();
await page.locator('textarea').fill('https://httpbin.org/get');
await page.getByRole('button', { name: 'Create' }).click();
// Expand the folder to see the request inside
await page.locator('.collection-item-name').filter({ hasText: 'folder-1' }).click();
await expect(page.locator('.collection-item-name').filter({ hasText: 'http-request' })).toBeVisible();
// Create second collection (target)
await page.locator('.dropdown-icon').click();
await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click();
await page.getByLabel('Name').fill('target-collection');
await page.getByLabel('Location').fill(await createTmpDir('target-collection'));
await page.getByRole('button', { name: 'Create', exact: true }).click();
// Wait for second collection to appear and click on it
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' })).toBeVisible();
await page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' }).click();
await page.getByLabel('Safe Mode').check();
await page.getByRole('button', { name: 'Save' }).click();
// Create a folder with the same name in the target collection
await page
.locator('.collection-name')
.filter({ hasText: 'target-collection' })
.locator('..')
.locator('.collection-actions')
.hover();
await page
.locator('.collection-name')
.filter({ hasText: 'target-collection' })
.locator('..')
.locator('.collection-actions .icon')
.click();
await page.locator('.dropdown-item').filter({ hasText: 'New Folder' }).click();
await expect(page.locator('#collection-name')).toBeVisible();
await page.locator('#collection-name').fill('folder-1');
await page.getByRole('button', { name: 'Create' }).click();
// Go back to source collection to drag the folder
await page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' }).click();
// Verify we have the folder to drag in the source collection
const sourceFolder = page.locator('.collection-item-name').filter({ hasText: 'folder-1' }).first();
await expect(sourceFolder).toBeVisible();
// Locate the target collection area
const targetCollection = page.locator('.collection-name').filter({ hasText: 'target-collection' });
await expect(targetCollection).toBeVisible();
// Perform drag and drop operation
await sourceFolder.dragTo(targetCollection);
// check for error toast notification
await expect(page.getByText(/Error: Cannot copy.*already exists/i)).toBeVisible();
// source and target collection request should remain unchanged
const sourceCollectionContainer = page
.locator('.collection-name')
.filter({ hasText: 'source-collection' })
.locator('..');
await expect(
sourceCollectionContainer.locator('.collection-item-name').filter({ hasText: 'folder-1' })
).toBeVisible();
await expect(
sourceCollectionContainer.locator('.collection-item-name').filter({ hasText: 'http-request' })
).toBeVisible();
const targetCollectionContainer = page
.locator('.collection-name')
.filter({ hasText: 'target-collection' })
.locator('..');
await expect(
targetCollectionContainer.locator('.collection-item-name').filter({ hasText: 'folder-1' })
).toBeVisible();
await expect(
targetCollectionContainer.locator('.collection-item-name').filter({ hasText: 'http-request' })
).not.toBeVisible();
});
});

View File

@@ -60,8 +60,8 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
const [dropType, setDropType] = useState(null); // 'adjacent' or 'inside'
const [{ isDragging }, drag, dragPreview] = useDrag({
type: `collection-item-${collectionUid}`,
item,
type: 'collection-item',
item: { ...item, sourceCollectionUid: collectionUid },
collect: (monitor) => ({
isDragging: monitor.isDragging()
}),
@@ -92,10 +92,15 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
const canItemBeDropped = ({ draggedItem, targetItem, dropType }) => {
const { uid: targetItemUid, pathname: targetItemPathname } = targetItem;
const { uid: draggedItemUid, pathname: draggedItemPathname } = draggedItem;
const { uid: draggedItemUid, pathname: draggedItemPathname, sourceCollectionUid } = draggedItem;
if (draggedItemUid === targetItemUid) return false;
// For cross-collection moves, we allow the drop
if (sourceCollectionUid !== collectionUid) {
return true;
}
const newPathname = calculateDraggedItemNewPathname({ draggedItem, targetItem, dropType, collectionPathname });
if (!newPathname) return false;
@@ -105,7 +110,7 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
};
const [{ isOver, canDrop }, drop] = useDrop({
accept: `collection-item-${collectionUid}`,
accept: 'collection-item',
hover: (draggedItem, monitor) => {
const { uid: targetItemUid } = item;
const { uid: draggedItemUid } = draggedItem;

View File

@@ -7,6 +7,7 @@ const Wrapper = styled.div`
user-select: none;
padding-left: 8px;
font-weight: 600;
border: ${(props) => props.theme.dragAndDrop.borderStyle} transparent;
.rotate-90 {
transform: rotateZ(90deg);
@@ -66,6 +67,7 @@ const Wrapper = styled.div`
}
&.drop-target {
border: ${(props) => props.theme.dragAndDrop.borderStyle} ${(props) => props.theme.dragAndDrop.border};
background-color: ${(props) => props.theme.dragAndDrop.hoverBg};
transition: ${(props) => props.theme.dragAndDrop.transition};
}
@@ -95,15 +97,6 @@ const Wrapper = styled.div`
}
}
.collection-name.drop-target {
border: ${(props) => props.theme.dragAndDrop.borderStyle} ${(props) => props.theme.dragAndDrop.border};
border-radius: 4px;
background-color: ${(props) => props.theme.dragAndDrop.hoverBg};
margin: -2px;
transition: ${(props) => props.theme.dragAndDrop.transition};
box-shadow: 0 0 0 2px ${(props) => props.theme.dragAndDrop.hoverBg};
}
#sidebar-collection-name {
white-space: nowrap;
text-overflow: ellipsis;

View File

@@ -34,6 +34,7 @@ const Collection = ({ collection, searchText }) => {
const [showCloneCollectionModalOpen, setShowCloneCollectionModalOpen] = useState(false);
const [showShareCollectionModal, setShowShareCollectionModal] = useState(false);
const [showRemoveCollectionModal, setShowRemoveCollectionModal] = useState(false);
const [dropType, setDropType] = useState(null);
const dispatch = useDispatch();
const isLoading = areItemsLoading(collection);
const collectionRef = useRef(null);
@@ -42,7 +43,7 @@ const Collection = ({ collection, searchText }) => {
const menuDropdownTippyRef = useRef();
const onMenuDropdownCreate = (ref) => (menuDropdownTippyRef.current = ref);
const MenuIcon = forwardRef((props, ref) => {
const MenuIcon = forwardRef((_props, ref) => {
return (
<div ref={ref} className="pr-2">
<IconDots size={22} />
@@ -101,7 +102,7 @@ const Collection = ({ collection, searchText }) => {
}
};
const handleDoubleClick = (event) => {
const handleDoubleClick = (_event) => {
dispatch(makeTabPermanent({ uid: collection.uid }))
};
@@ -118,7 +119,7 @@ const Collection = ({ collection, searchText }) => {
e.preventDefault();
};
const handleRightClick = (event) => {
const handleRightClick = (_event) => {
const _menuDropdown = menuDropdownTippyRef.current;
if (_menuDropdown) {
let menuDropdownBehavior = 'show';
@@ -140,7 +141,7 @@ const Collection = ({ collection, searchText }) => {
};
const isCollectionItem = (itemType) => {
return itemType.startsWith('collection-item');
return itemType === 'collection-item';
};
const [{ isDragging }, drag, dragPreview] = useDrag({
@@ -155,7 +156,17 @@ const Collection = ({ collection, searchText }) => {
});
const [{ isOver }, drop] = useDrop({
accept: ["collection", `collection-item-${collection.uid}`],
accept: ["collection", "collection-item"],
hover: (_draggedItem, monitor) => {
const itemType = monitor.getItemType();
if (isCollectionItem(itemType)) {
// For collection items, always show full highlight (inside drop)
setDropType('inside');
} else {
// For collections, show line indicator (adjacent drop)
setDropType('adjacent');
}
},
drop: (draggedItem, monitor) => {
const itemType = monitor.getItemType();
if (isCollectionItem(itemType)) {
@@ -163,6 +174,7 @@ const Collection = ({ collection, searchText }) => {
} else {
dispatch(moveCollectionAndPersist({draggedItem, targetItem: collection}));
}
setDropType(null);
},
canDrop: (draggedItem) => {
return draggedItem.uid !== collection.uid;
@@ -183,7 +195,8 @@ const Collection = ({ collection, searchText }) => {
}
const collectionRowClassName = classnames('flex py-1 collection-name items-center', {
'item-hovered': isOver,
'item-hovered': isOver && dropType === 'adjacent', // For collection-to-collection moves (show line)
'drop-target': isOver && dropType === 'inside', // For collection-item drops (highlight full area)
'collection-focused-in-tab': isCollectionFocused
});
@@ -241,7 +254,7 @@ const Collection = ({ collection, searchText }) => {
<Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement="bottom-start">
<div
className="dropdown-item"
onClick={(e) => {
onClick={(_e) => {
menuDropdownTippyRef.current.hide();
setShowNewRequestModal(true);
}}
@@ -250,7 +263,7 @@ const Collection = ({ collection, searchText }) => {
</div>
<div
className="dropdown-item"
onClick={(e) => {
onClick={(_e) => {
menuDropdownTippyRef.current.hide();
setShowNewFolderModal(true);
}}
@@ -259,7 +272,7 @@ const Collection = ({ collection, searchText }) => {
</div>
<div
className="dropdown-item"
onClick={(e) => {
onClick={(_e) => {
menuDropdownTippyRef.current.hide();
setShowCloneCollectionModalOpen(true);
}}
@@ -268,7 +281,7 @@ const Collection = ({ collection, searchText }) => {
</div>
<div
className="dropdown-item"
onClick={(e) => {
onClick={(_e) => {
menuDropdownTippyRef.current.hide();
ensureCollectionIsMounted();
handleRun();
@@ -278,7 +291,7 @@ const Collection = ({ collection, searchText }) => {
</div>
<div
className="dropdown-item"
onClick={(e) => {
onClick={(_e) => {
menuDropdownTippyRef.current.hide();
setShowRenameCollectionModal(true);
}}
@@ -287,7 +300,7 @@ const Collection = ({ collection, searchText }) => {
</div>
<div
className="dropdown-item"
onClick={(e) => {
onClick={(_e) => {
menuDropdownTippyRef.current.hide();
ensureCollectionIsMounted();
setShowShareCollectionModal(true);
@@ -297,7 +310,7 @@ const Collection = ({ collection, searchText }) => {
</div>
<div
className="dropdown-item"
onClick={(e) => {
onClick={(_e) => {
menuDropdownTippyRef.current.hide();
setShowRemoveCollectionModal(true);
}}
@@ -306,7 +319,7 @@ const Collection = ({ collection, searchText }) => {
</div>
<div
className="dropdown-item"
onClick={(e) => {
onClick={(_e) => {
menuDropdownTippyRef.current.hide();
viewCollectionSettings();
}}

View File

@@ -726,11 +726,16 @@ export const handleCollectionItemDrop =
(dispatch, getState) => {
const state = getState();
const collection = findCollectionByUid(state.collections.collections, collectionUid);
// if its withincollection set the source to current collection,
// if its cross collection set the source to the source collection
const sourceCollectionUid = draggedItem.sourceCollectionUid
const isCrossCollectionMove = sourceCollectionUid && collectionUid !== sourceCollectionUid;
const sourceCollection = isCrossCollectionMove ? findCollectionByUid(state.collections.collections, sourceCollectionUid) : collection;
const { uid: draggedItemUid, pathname: draggedItemPathname } = draggedItem;
const { uid: targetItemUid, pathname: targetItemPathname } = targetItem;
const targetItemDirectory = findParentItemInCollection(collection, targetItemUid) || collection;
const targetItemDirectoryItems = cloneDeep(targetItemDirectory.items);
const draggedItemDirectory = findParentItemInCollection(collection, draggedItemUid) || collection;
const draggedItemDirectory = findParentItemInCollection(sourceCollection, draggedItemUid) || sourceCollection;
const draggedItemDirectoryItems = cloneDeep(draggedItemDirectory.items);
const handleMoveToNewLocation = async ({

View File

@@ -699,7 +699,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
// Recursive function to parse the folder and create files/folders
const parseCollectionItems = (items = [], currentPath) => {
items.forEach(async (item) => {
if (['http-request', 'graphql-request'].includes(item.type)) {
if (['http-request', 'graphql-request', 'grpc-request'].includes(item.type)) {
const content = await stringifyRequestViaWorker(item);
const filePath = path.join(currentPath, item.filename);
safeWriteFileSync(filePath, content);