fix: cross-collection drag and drop tab and format issues (#7584)

Close the open tab when a request is moved to a different collection
via drag and drop, preventing the "Request no longer exists" error.

Add format conversion when dragging requests between collections with
different formats (.bru vs .yml). A new IPC handler parses the source
file and re-serializes it in the target collection's format. Folder
cross-format moves are blocked with a toast error.

Co-authored-by: Chirag Chandrashekhar <cchirag85@gmail.com>
This commit is contained in:
Chirag Chandrashekhar
2026-03-26 20:32:29 +05:30
committed by GitHub
parent 03dcb6b7b9
commit ff975c44f2
9 changed files with 206 additions and 4 deletions

View File

@@ -1112,6 +1112,10 @@ export const handleCollectionItemDrop
const draggedItemDirectory = findParentItemInCollection(sourceCollection, draggedItemUid) || sourceCollection;
const draggedItemDirectoryItems = cloneDeep(draggedItemDirectory.items);
const sourceFormat = sourceCollection?.format || 'bru';
const targetFormat = collection?.format || 'bru';
const isCrossFormatMove = isCrossCollectionMove && sourceFormat !== targetFormat;
const handleMoveToNewLocation = async ({
draggedItem,
draggedItemDirectoryItems,
@@ -1124,10 +1128,22 @@ export const handleCollectionItemDrop
const { pathname: draggedItemPathname, uid: draggedItemUid } = draggedItem;
const newDirname = path.dirname(newPathname);
await dispatch(moveItem({
targetDirname: newDirname,
sourcePathname: draggedItemPathname
}));
if (isCrossFormatMove && isItemARequest(draggedItem)) {
const { ipcRenderer } = window;
const result = await ipcRenderer.invoke('renderer:move-item-cross-format', {
targetDirname: newDirname,
sourcePathname: draggedItemPathname,
sourceFormat,
targetFormat
});
newPathname = result.newPathname;
} else {
await dispatch(moveItem({
targetDirname: newDirname,
sourcePathname: draggedItemPathname
}));
}
// Update sequences in the source directory
if (draggedItemDirectoryItems?.length) {
@@ -1191,6 +1207,11 @@ export const handleCollectionItemDrop
if (!newPathname) return;
if (targetItemPathname?.startsWith(draggedItemPathname)) return;
if (isCrossFormatMove && isItemAFolder(draggedItem)) {
toast.error('Moving folders between collections with different formats is not supported');
return;
}
// Discard operation if dragging a root item to the collection name (same location)
const isTargetTheCollection = targetItemPathname === collection.pathname;
const isDraggedItemAtRoot = draggedItemDirectory === sourceCollection;
@@ -1210,6 +1231,11 @@ export const handleCollectionItemDrop
} else {
await handleReorderInSameLocation({ draggedItem, targetItemDirectoryItems, targetItem });
}
if (isCrossCollectionMove) {
dispatch(closeTabs({ tabUids: [draggedItemUid] }));
}
resolve();
} catch (error) {
console.error(error);

View File

@@ -1448,6 +1448,40 @@ const registerRendererEventHandlers = (mainWindow, watcher) => {
}
});
ipcMain.handle('renderer:move-item-cross-format', async (event, { targetDirname, sourcePathname, sourceFormat, targetFormat }) => {
try {
if (!fs.existsSync(sourcePathname)) {
throw new Error(`Source path: ${sourcePathname} does not exist`);
}
if (!fs.existsSync(targetDirname)) {
throw new Error(`Target directory: ${targetDirname} does not exist`);
}
const sourceBasename = path.basename(sourcePathname);
const filenameWithoutExt = sourceBasename.replace(/\.(bru|yml|yaml)$/, '');
const targetExt = targetFormat === 'yml' ? 'yml' : 'bru';
const targetFilename = `${filenameWithoutExt}.${targetExt}`;
const targetPathname = path.join(targetDirname, targetFilename);
if (fs.existsSync(targetPathname)) {
throw new Error(`A file with the name "${targetFilename}" already exists in the target location`);
}
const sourceContent = await fs.promises.readFile(sourcePathname, 'utf8');
const parsedRequest = parseRequest(sourceContent, { format: sourceFormat });
const finalContent = stringifyRequest(parsedRequest, { format: targetFormat });
await writeFile(targetPathname, finalContent);
await removePath(sourcePathname);
moveRequestUid(sourcePathname, targetPathname);
return { newPathname: targetPathname };
} catch (error) {
return Promise.reject(error);
}
});
ipcMain.handle('renderer:move-folder-item', async (event, folderPath, destinationPath) => {
try {
const folderName = path.basename(folderPath);