mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-24 05:05:39 +00:00
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:
committed by
sanish-bruno
parent
5b716cbe60
commit
0bce203851
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}}
|
||||
|
||||
@@ -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 ({
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user