mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-24 17:03:47 +01:00
feat: drag and drop for files and folders
This commit is contained in:
parent
c4abe54c3f
commit
21c9c8b4fb
@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { closeTabs } from 'providers/ReduxStore/slices/tabs';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
const RequestNotFound = ({ itemUid }) => {
|
||||
const dispatch = useDispatch();
|
||||
const [showErrorMessage, setShowErrorMessage] = useState(false);
|
||||
|
||||
const closeTab = () => {
|
||||
dispatch(
|
||||
@ -13,6 +14,20 @@ const RequestNotFound = ({ itemUid }) => {
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setShowErrorMessage(true);
|
||||
}, 300);
|
||||
}, []);
|
||||
|
||||
// add a delay component in react that shows a loading spinner
|
||||
// and then shows the error message after a delay
|
||||
// this will prevent the error message from flashing on the screen
|
||||
|
||||
if(!showErrorMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-6 px-6">
|
||||
<div className="p-4 bg-orange-100 border-l-4 border-yellow-500 text-yellow-700 bg-yellow-100 p-4">
|
||||
|
@ -0,0 +1,42 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { IconAlertTriangle } from '@tabler/icons';
|
||||
|
||||
const RequestTabNotFound = ({handleCloseClick}) => {
|
||||
const [showErrorMessage, setShowErrorMessage] = useState(false);
|
||||
|
||||
// add a delay component in react that shows a loading spinner
|
||||
// and then shows the error message after a delay
|
||||
// this will prevent the error message from flashing on the screen
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setShowErrorMessage(true);
|
||||
}, 300);
|
||||
}, []);
|
||||
|
||||
if(!showErrorMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center tab-label pl-2">
|
||||
{showErrorMessage ? (
|
||||
<>
|
||||
<IconAlertTriangle size={18} strokeWidth={1.5} className="text-yellow-600" />
|
||||
<span className="ml-1">Not Found</span>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex px-2 close-icon-container" onClick={(e) => handleCloseClick(e)}>
|
||||
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RequestTabNotFound;
|
@ -4,7 +4,7 @@ import { closeTabs } from 'providers/ReduxStore/slices/tabs';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { findItemInCollection } from 'utils/collections';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { IconAlertTriangle } from '@tabler/icons';
|
||||
import RequestTabNotFound from './RequestTabNotFound';
|
||||
|
||||
const RequestTab = ({ tab, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
@ -61,18 +61,7 @@ const RequestTab = ({ tab, collection }) => {
|
||||
if (!item) {
|
||||
return (
|
||||
<StyledWrapper className="flex items-center justify-between tab-container px-1">
|
||||
<div className="flex items-center tab-label pl-2">
|
||||
<IconAlertTriangle size={18} strokeWidth={1.5} className="text-yellow-600" />
|
||||
<span className="ml-1">Not Found</span>
|
||||
</div>
|
||||
<div className="flex px-2 close-icon-container" onClick={(e) => handleCloseClick(e)}>
|
||||
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<RequestTabNotFound handleCloseClick={handleCloseClick} />
|
||||
</StyledWrapper>
|
||||
);
|
||||
}
|
||||
|
@ -35,28 +35,28 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
||||
const [newFolderModalOpen, setNewFolderModalOpen] = useState(false);
|
||||
const [itemIsCollapsed, setItemisCollapsed] = useState(item.collapsed);
|
||||
|
||||
// const [{ isDragging }, drag] = useDrag({
|
||||
// type: 'COLLECTION_ITEM',
|
||||
// item: item,
|
||||
// collect: (monitor) => ({
|
||||
// isDragging: monitor.isDragging()
|
||||
// })
|
||||
// });
|
||||
const [{ isDragging }, drag] = useDrag({
|
||||
type: 'COLLECTION_ITEM',
|
||||
item: item,
|
||||
collect: (monitor) => ({
|
||||
isDragging: monitor.isDragging()
|
||||
})
|
||||
});
|
||||
|
||||
// const [{ isOver }, drop] = useDrop({
|
||||
// accept: 'COLLECTION_ITEM',
|
||||
// drop: (draggedItem) => {
|
||||
// if (draggedItem.uid !== item.uid) {
|
||||
// dispatch(moveItem(collection.uid, draggedItem.uid, item.uid));
|
||||
// }
|
||||
// },
|
||||
// canDrop: (draggedItem) => {
|
||||
// return draggedItem.uid !== item.uid;
|
||||
// },
|
||||
// collect: (monitor) => ({
|
||||
// isOver: monitor.isOver()
|
||||
// })
|
||||
// });
|
||||
const [{ isOver }, drop] = useDrop({
|
||||
accept: 'COLLECTION_ITEM',
|
||||
drop: (draggedItem) => {
|
||||
if (draggedItem.uid !== item.uid) {
|
||||
dispatch(moveItem(collection.uid, draggedItem.uid, item.uid));
|
||||
}
|
||||
},
|
||||
canDrop: (draggedItem) => {
|
||||
return draggedItem.uid !== item.uid;
|
||||
},
|
||||
collect: (monitor) => ({
|
||||
isOver: monitor.isOver()
|
||||
})
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (searchText && searchText.length) {
|
||||
@ -131,8 +131,18 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const requestItems = filter(item.items, (i) => isItemARequest(i));
|
||||
const folderItems = filter(item.items, (i) => isItemAFolder(i));
|
||||
// we need to sort request items by seq property
|
||||
const sortRequestItems = (items = []) => {
|
||||
return items.sort((a, b) => a.seq - b.seq);
|
||||
};
|
||||
|
||||
// we need to sort folder items by name alphabetically
|
||||
const sortFolderItems = (items = []) => {
|
||||
return items.sort((a, b) => a.name.localeCompare(b.name));
|
||||
};
|
||||
|
||||
const requestItems = sortRequestItems(filter(item.items, (i) => isItemARequest(i)));
|
||||
const folderItems = sortFolderItems(filter(item.items, (i) => isItemAFolder(i)));
|
||||
|
||||
return (
|
||||
<StyledWrapper className={className}>
|
||||
@ -141,7 +151,7 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
||||
{deleteItemModalOpen && <DeleteCollectionItem item={item} collection={collection} onClose={() => setDeleteItemModalOpen(false)} />}
|
||||
{newRequestModalOpen && <NewRequest item={item} collection={collection} onClose={() => setNewRequestModalOpen(false)} />}
|
||||
{newFolderModalOpen && <NewFolder item={item} collection={collection} onClose={() => setNewFolderModalOpen(false)} />}
|
||||
<div className={itemRowClassName}>
|
||||
<div className={itemRowClassName} ref={(node) => drag(drop(node))}>
|
||||
<div className="flex items-center h-full w-full">
|
||||
{indents && indents.length
|
||||
? indents.map((i) => {
|
||||
@ -239,13 +249,13 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
||||
|
||||
{!itemIsCollapsed ? (
|
||||
<div>
|
||||
{requestItems && requestItems.length
|
||||
? requestItems.map((i) => {
|
||||
{folderItems && folderItems.length
|
||||
? folderItems.map((i) => {
|
||||
return <CollectionItem key={i.uid} item={i} collection={collection} searchText={searchText} />;
|
||||
})
|
||||
: null}
|
||||
{folderItems && folderItems.length
|
||||
? folderItems.map((i) => {
|
||||
{requestItems && requestItems.length
|
||||
? requestItems.map((i) => {
|
||||
return <CollectionItem key={i.uid} item={i} collection={collection} searchText={searchText} />;
|
||||
})
|
||||
: null}
|
||||
|
@ -59,28 +59,37 @@ const Collection = ({ collection, searchText }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const requestItems = filter(collection.items, (i) => isItemARequest(i));
|
||||
const folderItems = filter(collection.items, (i) => isItemAFolder(i));
|
||||
|
||||
const handleExportClick = () => {
|
||||
const collectionCopy = cloneDeep(collection);
|
||||
exportCollection(transformCollectionToSaveToIdb(collectionCopy));
|
||||
};
|
||||
|
||||
// const [{ isOver }, drop] = useDrop({
|
||||
// accept: 'COLLECTION_ITEM',
|
||||
// drop: (draggedItem) => {
|
||||
// console.log('drop', draggedItem);
|
||||
// dispatch(moveItemToRootOfCollection(collection.uid, draggedItem.uid));
|
||||
// },
|
||||
// canDrop: (draggedItem) => {
|
||||
// // todo need to make sure that draggedItem belongs to the collection
|
||||
// return true;
|
||||
// },
|
||||
// collect: (monitor) => ({
|
||||
// isOver: monitor.isOver()
|
||||
// })
|
||||
// });
|
||||
const [{ isOver }, drop] = useDrop({
|
||||
accept: 'COLLECTION_ITEM',
|
||||
drop: (draggedItem) => {
|
||||
dispatch(moveItemToRootOfCollection(collection.uid, draggedItem.uid));
|
||||
},
|
||||
canDrop: (draggedItem) => {
|
||||
// todo need to make sure that draggedItem belongs to the collection
|
||||
return true;
|
||||
},
|
||||
collect: (monitor) => ({
|
||||
isOver: monitor.isOver()
|
||||
})
|
||||
});
|
||||
|
||||
// we need to sort request items by seq property
|
||||
const sortRequestItems = (items = []) => {
|
||||
return items.sort((a, b) => a.seq - b.seq);
|
||||
};
|
||||
|
||||
// we need to sort folder items by name alphabetically
|
||||
const sortFolderItems = (items = []) => {
|
||||
return items.sort((a, b) => a.name.localeCompare(b.name));
|
||||
};
|
||||
|
||||
const requestItems = sortRequestItems(filter(collection.items, (i) => isItemARequest(i)));
|
||||
const folderItems = sortFolderItems(filter(collection.items, (i) => isItemAFolder(i)));
|
||||
|
||||
return (
|
||||
<StyledWrapper className="flex flex-col">
|
||||
@ -88,7 +97,7 @@ const Collection = ({ collection, searchText }) => {
|
||||
{showNewFolderModal && <NewFolder collection={collection} onClose={() => setShowNewFolderModal(false)} />}
|
||||
{showRenameCollectionModal && <RenameCollection collection={collection} onClose={() => setShowRenameCollectionModal(false)} />}
|
||||
{showRemoveCollectionModal && <RemoveCollection collection={collection} onClose={() => setShowRemoveCollectionModal(false)} />}
|
||||
<div className="flex py-1 collection-name items-center">
|
||||
<div className="flex py-1 collection-name items-center" ref={drop}>
|
||||
<div className="flex flex-grow items-center" onClick={handleClick}>
|
||||
<IconChevronRight size={16} strokeWidth={2} className={iconClassName} style={{ width: 16, color: 'rgb(160 160 160)' }} />
|
||||
<div className="ml-1" id="sidebar-collection-name">{collection.name}</div>
|
||||
@ -148,14 +157,13 @@ const Collection = ({ collection, searchText }) => {
|
||||
<div>
|
||||
{!collectionIsCollapsed ? (
|
||||
<div>
|
||||
{requestItems && requestItems.length
|
||||
? requestItems.map((i) => {
|
||||
{folderItems && folderItems.length
|
||||
? folderItems.map((i) => {
|
||||
return <CollectionItem key={i.uid} item={i} collection={collection} searchText={searchText} />;
|
||||
})
|
||||
: null}
|
||||
|
||||
{folderItems && folderItems.length
|
||||
? folderItems.map((i) => {
|
||||
{requestItems && requestItems.length
|
||||
? requestItems.map((i) => {
|
||||
return <CollectionItem key={i.uid} item={i} collection={collection} searchText={searchText} />;
|
||||
})
|
||||
: null}
|
||||
|
@ -6,6 +6,7 @@ import cloneDeep from 'lodash/cloneDeep';
|
||||
import {
|
||||
findItemInCollection,
|
||||
moveCollectionItem,
|
||||
getItemsToResequence,
|
||||
moveCollectionItemToRootOfCollection,
|
||||
findCollectionByUid,
|
||||
recursivelyGetAllItemUids,
|
||||
@ -13,6 +14,7 @@ import {
|
||||
transformRequestToSaveToFilesystem,
|
||||
findParentItemInCollection,
|
||||
findEnvironmentInCollection,
|
||||
isItemARequest,
|
||||
isItemAFolder,
|
||||
refreshUidsInItem,
|
||||
interpolateEnvironmentVars
|
||||
@ -30,8 +32,6 @@ import {
|
||||
renameItem as _renameItem,
|
||||
cloneItem as _cloneItem,
|
||||
deleteItem as _deleteItem,
|
||||
moveItem as _moveItem,
|
||||
moveItemToRootOfCollection as _moveItemToRootOfCollection,
|
||||
saveRequest as _saveRequest,
|
||||
selectEnvironment as _selectEnvironment,
|
||||
createCollection as _createCollection,
|
||||
@ -330,27 +330,6 @@ export const moveItem = (collectionUid, draggedItemUid, targetItemUid) => (dispa
|
||||
return reject(new Error('Collection not found'));
|
||||
}
|
||||
|
||||
if (isLocalCollection(collection)) {
|
||||
const draggedItem = findItemInCollection(collection, draggedItemUid);
|
||||
const targetItem = findItemInCollection(collection, targetItemUid);
|
||||
|
||||
if (!draggedItem) {
|
||||
return reject(new Error('Dragged item not found'));
|
||||
}
|
||||
|
||||
if (!targetItem) {
|
||||
return reject(new Error('Target item not found'));
|
||||
}
|
||||
|
||||
const { ipcRenderer } = window;
|
||||
|
||||
ipcRenderer
|
||||
.invoke('renderer:move-item', draggedItem.pathname, targetItem.pathname)
|
||||
.then(() => resolve())
|
||||
.catch((error) => reject(error));
|
||||
return;
|
||||
}
|
||||
|
||||
const collectionCopy = cloneDeep(collection);
|
||||
const draggedItem = findItemInCollection(collectionCopy, draggedItemUid);
|
||||
const targetItem = findItemInCollection(collectionCopy, targetItemUid);
|
||||
@ -363,24 +342,112 @@ export const moveItem = (collectionUid, draggedItemUid, targetItemUid) => (dispa
|
||||
return reject(new Error('Target item not found'));
|
||||
}
|
||||
|
||||
moveCollectionItem(collectionCopy, draggedItem, targetItem);
|
||||
const draggedItemParent = findParentItemInCollection(collectionCopy, draggedItemUid);
|
||||
const targetItemParent = findParentItemInCollection(collectionCopy, targetItemUid);
|
||||
const sameParent = draggedItemParent === targetItemParent;
|
||||
|
||||
const collectionToSave = transformCollectionToSaveToIdb(collectionCopy);
|
||||
// file item dragged onto another file item and both are in the same folder
|
||||
// this is also true when both items are at the root level
|
||||
if (isItemARequest(draggedItem) && isItemARequest(targetItem) && sameParent) {
|
||||
moveCollectionItem(collectionCopy, draggedItem, targetItem);
|
||||
const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy);
|
||||
|
||||
collectionSchema
|
||||
.validate(collectionToSave)
|
||||
.then(() => saveCollectionToIdb(window.__idb, collectionToSave))
|
||||
.then(() => {
|
||||
dispatch(
|
||||
_moveItem({
|
||||
collectionUid: collectionUid,
|
||||
draggedItemUid: draggedItemUid,
|
||||
targetItemUid: targetItemUid
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(() => resolve())
|
||||
.catch((error) => reject(error));
|
||||
return ipcRenderer
|
||||
.invoke('renderer:resequence-items', itemsToResequence)
|
||||
.then(resolve)
|
||||
.catch((error) => reject(error));
|
||||
}
|
||||
|
||||
// file item dragged onto another file item which is at the root level
|
||||
if (isItemARequest(draggedItem) && isItemARequest(targetItem) && !targetItemParent) {
|
||||
const draggedItemPathname = draggedItem.pathname;
|
||||
moveCollectionItem(collectionCopy, draggedItem, targetItem);
|
||||
const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy);
|
||||
const itemsToResequence2 = getItemsToResequence(targetItemParent, collectionCopy);
|
||||
|
||||
return ipcRenderer
|
||||
.invoke('renderer:move-file-item', draggedItemPathname, collectionCopy.pathname)
|
||||
.then(() => ipcRenderer.invoke('renderer:resequence-items', itemsToResequence))
|
||||
.then(() => ipcRenderer.invoke('renderer:resequence-items', itemsToResequence2))
|
||||
.then(resolve)
|
||||
.catch((error) => reject(error));
|
||||
}
|
||||
|
||||
// file item dragged onto another file item and both are in different folders
|
||||
if (isItemARequest(draggedItem) && isItemARequest(targetItem) && !sameParent) {
|
||||
const draggedItemPathname = draggedItem.pathname;
|
||||
moveCollectionItem(collectionCopy, draggedItem, targetItem);
|
||||
const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy);
|
||||
const itemsToResequence2 = getItemsToResequence(targetItemParent, collectionCopy);
|
||||
console.log('itemsToResequence', itemsToResequence);
|
||||
console.log('itemsToResequence2', itemsToResequence2);
|
||||
|
||||
return ipcRenderer
|
||||
.invoke('renderer:move-file-item', draggedItemPathname, targetItemParent.pathname)
|
||||
.then(() => ipcRenderer.invoke('renderer:resequence-items', itemsToResequence))
|
||||
.then(() => ipcRenderer.invoke('renderer:resequence-items', itemsToResequence2))
|
||||
.then(resolve)
|
||||
.catch((error) => reject(error));
|
||||
}
|
||||
|
||||
// file item dragged into its own folder
|
||||
if (isItemARequest(draggedItem) && isItemAFolder(targetItem) && draggedItemParent === targetItem) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
// file item dragged into another folder
|
||||
if (isItemARequest(draggedItem) && isItemAFolder(targetItem) && draggedItemParent !== targetItem) {
|
||||
const draggedItemPathname = draggedItem.pathname;
|
||||
moveCollectionItem(collectionCopy, draggedItem, targetItem);
|
||||
const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy);
|
||||
const itemsToResequence2 = getItemsToResequence(targetItem, collectionCopy);
|
||||
|
||||
return ipcRenderer
|
||||
.invoke('renderer:move-file-item', draggedItemPathname, targetItem.pathname)
|
||||
.then(() => ipcRenderer.invoke('renderer:resequence-items', itemsToResequence))
|
||||
.then(() => ipcRenderer.invoke('renderer:resequence-items', itemsToResequence2))
|
||||
.then(resolve)
|
||||
.catch((error) => reject(error));
|
||||
}
|
||||
|
||||
// end of the file drags, now let's handle folder drags
|
||||
// folder drags are simpler since we don't allow ordering of folders
|
||||
|
||||
// folder dragged into its own folder
|
||||
if (isItemAFolder(draggedItem) && isItemAFolder(targetItem) && draggedItemParent === targetItem) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
// folder dragged into a file which is at the same level
|
||||
// this is also true when both items are at the root level
|
||||
if (isItemAFolder(draggedItem) && isItemARequest(targetItem) && sameParent) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
// folder dragged into a file which is a child of the folder
|
||||
if (isItemAFolder(draggedItem) && isItemARequest(targetItem) && draggedItem === targetItemParent) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
// folder dragged into a file which is at the root level
|
||||
if (isItemAFolder(draggedItem) && isItemARequest(targetItem) && !targetItemParent) {
|
||||
const draggedItemPathname = draggedItem.pathname;
|
||||
|
||||
return ipcRenderer
|
||||
.invoke('renderer:move-folder-item', draggedItemPathname, collectionCopy.pathname)
|
||||
.then(resolve)
|
||||
.catch((error) => reject(error));
|
||||
}
|
||||
|
||||
// folder dragged into another folder
|
||||
if (isItemAFolder(draggedItem) && isItemAFolder(targetItem) && draggedItemParent !== targetItem) {
|
||||
const draggedItemPathname = draggedItem.pathname;
|
||||
|
||||
return ipcRenderer
|
||||
.invoke('renderer:move-folder-item', draggedItemPathname, targetItem.pathname)
|
||||
.then(resolve)
|
||||
.catch((error) => reject(error));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@ -393,45 +460,28 @@ export const moveItemToRootOfCollection = (collectionUid, draggedItemUid) => (di
|
||||
return reject(new Error('Collection not found'));
|
||||
}
|
||||
|
||||
if (isLocalCollection(collection)) {
|
||||
const draggedItem = findItemInCollection(collection, draggedItemUid);
|
||||
|
||||
if (!draggedItem) {
|
||||
return reject(new Error('Dragged item not found'));
|
||||
}
|
||||
|
||||
const { ipcRenderer } = window;
|
||||
|
||||
ipcRenderer
|
||||
.invoke('renderer:move-item-to-root-of-collection', draggedItem.pathname)
|
||||
.then(() => resolve())
|
||||
.catch((error) => reject(error));
|
||||
return;
|
||||
}
|
||||
|
||||
const collectionCopy = cloneDeep(collection);
|
||||
const draggedItem = findItemInCollection(collectionCopy, draggedItemUid);
|
||||
|
||||
if (!draggedItem) {
|
||||
return reject(new Error('Dragged item not found'));
|
||||
}
|
||||
|
||||
const draggedItemParent = findParentItemInCollection(collectionCopy, draggedItemUid);
|
||||
// file item is already at the root level
|
||||
if (!draggedItemParent) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
const draggedItemPathname = draggedItem.pathname;
|
||||
moveCollectionItemToRootOfCollection(collectionCopy, draggedItem);
|
||||
const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy);
|
||||
const itemsToResequence2 = getItemsToResequence(collectionCopy, collectionCopy);
|
||||
|
||||
const collectionToSave = transformCollectionToSaveToIdb(collectionCopy);
|
||||
|
||||
collectionSchema
|
||||
.validate(collectionToSave)
|
||||
.then(() => saveCollectionToIdb(window.__idb, collectionToSave))
|
||||
.then(() => {
|
||||
dispatch(
|
||||
_moveItemToRootOfCollection({
|
||||
collectionUid: collectionUid,
|
||||
draggedItemUid: draggedItemUid
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(() => resolve())
|
||||
return ipcRenderer
|
||||
.invoke('renderer:move-file-item', draggedItemPathname, collectionCopy.pathname)
|
||||
.then(() => ipcRenderer.invoke('renderer:resequence-items', itemsToResequence))
|
||||
.then(() => ipcRenderer.invoke('renderer:resequence-items', itemsToResequence2))
|
||||
.then(resolve)
|
||||
.catch((error) => reject(error));
|
||||
});
|
||||
};
|
||||
|
@ -14,10 +14,11 @@ import {
|
||||
findEnvironmentInCollection,
|
||||
findItemInCollectionByPathname,
|
||||
addDepth,
|
||||
moveCollectionItem,
|
||||
collapseCollection,
|
||||
deleteItemInCollection,
|
||||
isItemARequest
|
||||
deleteItemInCollectionByPathname,
|
||||
isItemARequest,
|
||||
areItemsTheSameExceptSeqUpdate
|
||||
} from 'utils/collections';
|
||||
import { parseQueryParams, stringifyQueryParams } from 'utils/url';
|
||||
import { getSubdirectoriesFromRoot } from 'utils/common/platform';
|
||||
@ -146,38 +147,6 @@ export const collectionsSlice = createSlice({
|
||||
}
|
||||
}
|
||||
},
|
||||
moveItem: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
const draggedItemUid = action.payload.draggedItemUid;
|
||||
const targetItemUid = action.payload.targetItemUid;
|
||||
|
||||
if (collection) {
|
||||
const draggedItem = findItemInCollection(collection, draggedItemUid);
|
||||
const targetItem = findItemInCollection(collection, targetItemUid);
|
||||
|
||||
if (!draggedItem || !targetItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
moveCollectionItem(collection, draggedItem, targetItem);
|
||||
addDepth(collection.items);
|
||||
}
|
||||
},
|
||||
moveItemToRootOfCollection: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
const draggedItemUid = action.payload.draggedItemUid;
|
||||
|
||||
if (collection) {
|
||||
const draggedItem = findItemInCollection(collection, draggedItemUid);
|
||||
|
||||
if (!draggedItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
moveCollectionItemToRootOfCollection(collection, draggedItem);
|
||||
addDepth(collection.items);
|
||||
}
|
||||
},
|
||||
requestSent: (state, action) => {
|
||||
const { itemUid, collectionUid, cancelTokenUid, requestSent } = action.payload;
|
||||
const collection = findCollectionByUid(state.collections, collectionUid);
|
||||
@ -650,7 +619,7 @@ export const collectionsSlice = createSlice({
|
||||
uid: uuid(),
|
||||
pathname: `${currentPath}${PATH_SEPARATOR}${directoryName}`,
|
||||
name: directoryName,
|
||||
collapsed: false,
|
||||
collapsed: true,
|
||||
type: 'folder',
|
||||
items: []
|
||||
};
|
||||
@ -669,6 +638,7 @@ export const collectionsSlice = createSlice({
|
||||
if (currentItem) {
|
||||
currentItem.name = file.data.name;
|
||||
currentItem.type = file.data.type;
|
||||
currentItem.seq = file.data.seq;
|
||||
currentItem.request = file.data.request;
|
||||
currentItem.filename = file.meta.name;
|
||||
currentItem.pathname = file.meta.pathname;
|
||||
@ -678,6 +648,7 @@ export const collectionsSlice = createSlice({
|
||||
uid: file.data.uid,
|
||||
name: file.data.name,
|
||||
type: file.data.type,
|
||||
seq: file.data.seq,
|
||||
request: file.data.request,
|
||||
filename: file.meta.name,
|
||||
pathname: file.meta.pathname,
|
||||
@ -703,7 +674,7 @@ export const collectionsSlice = createSlice({
|
||||
uid: uuid(),
|
||||
pathname: `${currentPath}${PATH_SEPARATOR}${directoryName}`,
|
||||
name: directoryName,
|
||||
collapsed: false,
|
||||
collapsed: true,
|
||||
type: 'folder',
|
||||
items: []
|
||||
};
|
||||
@ -724,12 +695,20 @@ export const collectionsSlice = createSlice({
|
||||
const item = findItemInCollection(collection, file.data.uid);
|
||||
|
||||
if (item) {
|
||||
item.name = file.data.name;
|
||||
item.type = file.data.type;
|
||||
item.request = file.data.request;
|
||||
item.filename = file.meta.name;
|
||||
item.pathname = file.meta.pathname;
|
||||
item.draft = null;
|
||||
// whenever a user attempts to sort a req within the same folder
|
||||
// the seq is updated, but everything else remains the same
|
||||
// we don't want to lose the draft in this case
|
||||
if(areItemsTheSameExceptSeqUpdate(item, file.data)) {
|
||||
item.seq = file.data.seq;
|
||||
} else {
|
||||
item.name = file.data.name;
|
||||
item.type = file.data.type;
|
||||
item.seq = file.data.seq;
|
||||
item.request = file.data.request;
|
||||
item.filename = file.meta.name;
|
||||
item.pathname = file.meta.pathname;
|
||||
item.draft = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -741,7 +720,7 @@ export const collectionsSlice = createSlice({
|
||||
const item = findItemInCollectionByPathname(collection, file.meta.pathname);
|
||||
|
||||
if (item) {
|
||||
deleteItemInCollection(item.uid, collection);
|
||||
deleteItemInCollectionByPathname(file.meta.pathname, collection);
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -753,7 +732,7 @@ export const collectionsSlice = createSlice({
|
||||
const item = findItemInCollectionByPathname(collection, directory.meta.pathname);
|
||||
|
||||
if (item) {
|
||||
deleteItemInCollection(item.uid, collection);
|
||||
deleteItemInCollectionByPathname(directory.meta.pathname, collection);
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -788,8 +767,6 @@ export const {
|
||||
deleteItem,
|
||||
renameItem,
|
||||
cloneItem,
|
||||
moveItem,
|
||||
moveItemToRootOfCollection,
|
||||
requestSent,
|
||||
requestCancelled,
|
||||
responseReceived,
|
||||
|
@ -1,4 +1,3 @@
|
||||
import reckon from 'reckonjs';
|
||||
import get from 'lodash/get';
|
||||
import each from 'lodash/each';
|
||||
import find from 'lodash/find';
|
||||
@ -7,7 +6,14 @@ import isString from 'lodash/isString';
|
||||
import map from 'lodash/map';
|
||||
import filter from 'lodash/filter';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { uuid } from 'utils/common';
|
||||
import path from 'path';
|
||||
|
||||
// although we are not using rekonjs directly
|
||||
// its populating the global string prototype with .reckon method
|
||||
import reckon from 'reckonjs';
|
||||
|
||||
const replaceTabsWithSpaces = (str, numSpaces = 2) => {
|
||||
if (!str || !str.length || !isString(str)) {
|
||||
@ -128,6 +134,7 @@ export const moveCollectionItem = (collection, draggedItem, targetItem) => {
|
||||
|
||||
if (draggedItemParent) {
|
||||
draggedItemParent.items = filter(draggedItemParent.items, (i) => i.uid !== draggedItem.uid);
|
||||
draggedItem.pathname = path.join(draggedItemParent.pathname, draggedItem.filename);
|
||||
} else {
|
||||
collection.items = filter(collection.items, (i) => i.uid !== draggedItem.uid);
|
||||
}
|
||||
@ -135,15 +142,18 @@ export const moveCollectionItem = (collection, draggedItem, targetItem) => {
|
||||
if (targetItem.type === 'folder') {
|
||||
targetItem.items = targetItem.items || [];
|
||||
targetItem.items.push(draggedItem);
|
||||
draggedItem.pathname = path.join(targetItem.pathname, draggedItem.filename);
|
||||
} else {
|
||||
let targetItemParent = findParentItemInCollection(collection, targetItem.uid);
|
||||
|
||||
if (targetItemParent) {
|
||||
let targetItemIndex = findIndex(targetItemParent.items, (i) => i.uid === targetItem.uid);
|
||||
targetItemParent.items.splice(targetItemIndex + 1, 0, draggedItem);
|
||||
draggedItem.pathname = path.join(targetItemParent.pathname, draggedItem.filename);
|
||||
} else {
|
||||
let targetItemIndex = findIndex(collection.items, (i) => i.uid === targetItem.uid);
|
||||
collection.items.splice(targetItemIndex + 1, 0, draggedItem);
|
||||
draggedItem.pathname = path.join(collection.pathname, draggedItem.filename);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -151,13 +161,46 @@ export const moveCollectionItem = (collection, draggedItem, targetItem) => {
|
||||
export const moveCollectionItemToRootOfCollection = (collection, draggedItem) => {
|
||||
let draggedItemParent = findParentItemInCollection(collection, draggedItem.uid);
|
||||
|
||||
if (draggedItemParent) {
|
||||
draggedItemParent.items = filter(draggedItemParent.items, (i) => i.uid !== draggedItem.uid);
|
||||
} else {
|
||||
collection.items = filter(collection.items, (i) => i.uid !== draggedItem.uid);
|
||||
// If the dragged item is already at the root of the collection, do nothing
|
||||
if(!draggedItemParent) {
|
||||
return;
|
||||
}
|
||||
|
||||
draggedItemParent.items = filter(draggedItemParent.items, (i) => i.uid !== draggedItem.uid);
|
||||
collection.items.push(draggedItem);
|
||||
draggedItem.pathname = path.join(collection.pathname, draggedItem.filename);
|
||||
};
|
||||
|
||||
export const getItemsToResequence = (parent, collection) => {
|
||||
let itemsToResequence = [];
|
||||
|
||||
if(!parent) {
|
||||
let index = 1;
|
||||
each(collection.items, (item) => {
|
||||
if(isItemARequest(item)) {
|
||||
itemsToResequence.push({
|
||||
pathname: item.pathname,
|
||||
seq: index++
|
||||
});
|
||||
}
|
||||
});
|
||||
return itemsToResequence;
|
||||
}
|
||||
|
||||
if (parent.items && parent.items.length) {
|
||||
let index = 1;
|
||||
each(parent.items, (item) => {
|
||||
if(isItemARequest(item)) {
|
||||
itemsToResequence.push({
|
||||
pathname: item.pathname,
|
||||
seq: index++
|
||||
});
|
||||
}
|
||||
});
|
||||
return itemsToResequence;
|
||||
}
|
||||
|
||||
return itemsToResequence;
|
||||
};
|
||||
|
||||
export const transformCollectionToSaveToIdb = (collection, options = {}) => {
|
||||
@ -335,7 +378,6 @@ export const deleteItemInCollection = (itemUid, collection) => {
|
||||
collection.items = filter(collection.items, (i) => i.uid !== itemUid);
|
||||
|
||||
let flattenedItems = flattenItems(collection.items);
|
||||
|
||||
each(flattenedItems, (i) => {
|
||||
if (i.items && i.items.length) {
|
||||
i.items = filter(i.items, (i) => i.uid !== itemUid);
|
||||
@ -343,6 +385,17 @@ export const deleteItemInCollection = (itemUid, collection) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteItemInCollectionByPathname = (pathname, collection) => {
|
||||
collection.items = filter(collection.items, (i) => i.pathname !== pathname);
|
||||
|
||||
let flattenedItems = flattenItems(collection.items);
|
||||
each(flattenedItems, (i) => {
|
||||
if (i.items && i.items.length) {
|
||||
i.items = filter(i.items, (i) => i.pathname !== pathname);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const isItemARequest = (item) => {
|
||||
return item.hasOwnProperty('request') && ['http-request', 'graphql-request'].includes(item.type) && !item.items;
|
||||
};
|
||||
@ -449,6 +502,44 @@ export const interpolateEnvironmentVars = (item, variables) => {
|
||||
return request;
|
||||
};
|
||||
|
||||
export const deleteUidsInItem = (item) => {
|
||||
delete item.uid;
|
||||
const params = get(item, 'request.params', []);
|
||||
const headers = get(item, 'request.headers', []);
|
||||
const bodyFormUrlEncoded = get(item, 'request.body.formUrlEncoded', []);
|
||||
const bodyMultipartForm = get(item, 'request.body.multipartForm', []);
|
||||
|
||||
params.forEach((param) => delete param.uid);
|
||||
headers.forEach((header) => delete header.uid);
|
||||
bodyFormUrlEncoded.forEach((param) => delete param.uid);
|
||||
bodyMultipartForm.forEach((param) => delete param.uid);
|
||||
|
||||
return item;
|
||||
};
|
||||
|
||||
export const areItemsTheSameExceptSeqUpdate = (_item1, _item2) => {
|
||||
let item1 = cloneDeep(_item1);
|
||||
let item2 = cloneDeep(_item2);
|
||||
|
||||
// remove seq from both items
|
||||
delete item1.seq;
|
||||
delete item2.seq;
|
||||
|
||||
// remove draft from both items
|
||||
delete item1.draft;
|
||||
delete item2.draft;
|
||||
|
||||
// get projection of both items
|
||||
item1 = transformRequestToSaveToFilesystem(item1);
|
||||
item2 = transformRequestToSaveToFilesystem(item2);
|
||||
|
||||
// delete uids from both items
|
||||
deleteUidsInItem(item1);
|
||||
deleteUidsInItem(item2);
|
||||
|
||||
return isEqual(item1, item2);
|
||||
};
|
||||
|
||||
export const getDefaultRequestPaneTab = (item) => {
|
||||
if(item.type === 'http-request') {
|
||||
return 'params';
|
||||
|
@ -10,7 +10,8 @@ const {
|
||||
envJsonToBru,
|
||||
} = require('@usebruno/bruno-lang');
|
||||
const { itemSchema } = require('@usebruno/schema');
|
||||
const { generateUidBasedOnHash, uuid } = require('../utils/common');
|
||||
const { uuid } = require('../utils/common');
|
||||
const { getRequestUid } = require('../cache/requestUids');
|
||||
|
||||
const isJsonEnvironmentConfig = (pathname, collectionPath) => {
|
||||
const dirname = path.dirname(pathname);
|
||||
@ -28,7 +29,7 @@ const isBruEnvironmentConfig = (pathname, collectionPath) => {
|
||||
};
|
||||
|
||||
const hydrateRequestWithUuid = (request, pathname) => {
|
||||
request.uid = generateUidBasedOnHash(pathname);
|
||||
request.uid = getRequestUid(pathname);
|
||||
|
||||
const params = _.get(request, 'request.params', []);
|
||||
const headers = _.get(request, 'request.headers', []);
|
||||
@ -57,7 +58,7 @@ const addEnvironmentFile = async (win, pathname, collectionUid) => {
|
||||
const bruContent = fs.readFileSync(pathname, 'utf8');
|
||||
file.data = bruToEnvJson(bruContent);
|
||||
file.data.name = basename.substring(0, basename.length - 4);
|
||||
file.data.uid = generateUidBasedOnHash(pathname);
|
||||
file.data.uid = getRequestUid(pathname);
|
||||
|
||||
_.each(_.get(file, 'data.variables', []), (variable) => variable.uid = uuid());
|
||||
win.webContents.send('main:collection-tree-updated', 'addEnvironmentFile', file);
|
||||
@ -80,7 +81,7 @@ const changeEnvironmentFile = async (win, pathname, collectionUid) => {
|
||||
const bruContent = fs.readFileSync(pathname, 'utf8');
|
||||
file.data = bruToEnvJson(bruContent);
|
||||
file.data.name = basename.substring(0, basename.length - 4);
|
||||
file.data.uid = generateUidBasedOnHash(pathname);
|
||||
file.data.uid = getRequestUid(pathname);
|
||||
_.each(_.get(file, 'data.variables', []), (variable) => variable.uid = uuid());
|
||||
|
||||
// we are reusing the addEnvironmentFile event itself
|
||||
@ -101,7 +102,7 @@ const unlinkEnvironmentFile = async (win, pathname, collectionUid) => {
|
||||
name: path.basename(pathname),
|
||||
},
|
||||
data: {
|
||||
uid: generateUidBasedOnHash(pathname),
|
||||
uid: getRequestUid(pathname),
|
||||
name: path.basename(pathname).substring(0, path.basename(pathname).length - 4),
|
||||
}
|
||||
};
|
||||
|
44
packages/bruno-electron/src/cache/requestUids.js
vendored
Normal file
44
packages/bruno-electron/src/cache/requestUids.js
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* we maintain a cache of request uids to ensure that we
|
||||
* preserve the same uid for a request even when the request
|
||||
* moves to a different location
|
||||
*
|
||||
* In the past, we used to generate unique ids based on the
|
||||
* pathname of the request, but we faced problems when implementing
|
||||
* functionality where the user can move the request to a different
|
||||
* location. In that case, the uid would change, and the we would
|
||||
* lose the request's draft state if the user has made some changes
|
||||
*/
|
||||
|
||||
const requestUids = new Map();
|
||||
const { uuid } = require('../utils/common');
|
||||
|
||||
const getRequestUid = (pathname) => {
|
||||
let uid = requestUids.get(pathname);
|
||||
|
||||
if (!uid) {
|
||||
uid = uuid();
|
||||
requestUids.set(pathname, uid);
|
||||
}
|
||||
|
||||
return uid;
|
||||
};
|
||||
|
||||
const moveRequestUid = (oldPathname, newPathname) => {
|
||||
const uid = requestUids.get(oldPathname);
|
||||
|
||||
if (uid) {
|
||||
requestUids.delete(oldPathname);
|
||||
requestUids.set(newPathname, uid);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteRequestUid = (pathname) => {
|
||||
requestUids.delete(pathname);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getRequestUid,
|
||||
moveRequestUid,
|
||||
deleteRequestUid
|
||||
};
|
@ -13,11 +13,13 @@ const {
|
||||
hasBruExtension,
|
||||
isDirectory,
|
||||
browseDirectory,
|
||||
createDirectory
|
||||
createDirectory,
|
||||
searchForBruFiles
|
||||
} = require('../utils/filesystem');
|
||||
const { uuid, stringifyJson } = require('../utils/common');
|
||||
const { stringifyJson } = require('../utils/common');
|
||||
const { openCollectionDialog, openCollection } = require('../app/collections');
|
||||
const { generateUidBasedOnHash } = require('../utils/common');
|
||||
const { moveRequestUid, deleteRequestUid } = require('../cache/requestUids');
|
||||
|
||||
const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollections) => {
|
||||
// browse directory
|
||||
@ -179,6 +181,12 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
||||
|
||||
// if its directory, rename and return
|
||||
if(isDirectory(oldPath)) {
|
||||
const bruFilesAtSource = await searchForBruFiles(oldPath);
|
||||
|
||||
for(let bruFile of bruFilesAtSource) {
|
||||
const newBruFilePath = bruFile.replace(oldPath, newPath);
|
||||
moveRequestUid(bruFile, newBruFilePath);
|
||||
}
|
||||
return fs.renameSync(oldPath, newPath);
|
||||
}
|
||||
|
||||
@ -193,6 +201,8 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
||||
|
||||
jsonData.name = newName;
|
||||
|
||||
moveRequestUid(oldPath, newPath);
|
||||
|
||||
const content = jsonToBru(jsonData);
|
||||
await writeFile(newPath, content);
|
||||
await fs.unlinkSync(oldPath);
|
||||
@ -218,9 +228,25 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
||||
ipcMain.handle('renderer:delete-item', async (event, pathname, type) => {
|
||||
try {
|
||||
if(type === 'folder') {
|
||||
await fs.rmSync(pathname, { recursive: true, force: true});
|
||||
if(!fs.existsSync(pathname)) {
|
||||
return Promise.reject(new Error('The directory does not exist'));
|
||||
}
|
||||
|
||||
// delete the request uid mappings
|
||||
const bruFilesAtSource = await searchForBruFiles(pathname);
|
||||
for(let bruFile of bruFilesAtSource) {
|
||||
deleteRequestUid(bruFile);
|
||||
}
|
||||
|
||||
fs.rmSync(pathname, { recursive: true, force: true});
|
||||
} else if (['http-request', 'graphql-request'].includes(type)) {
|
||||
await fs.unlinkSync(pathname);
|
||||
if(!fs.existsSync(pathname)) {
|
||||
return Promise.reject(new Error('The file does not exist'));
|
||||
}
|
||||
|
||||
deleteRequestUid(pathname);
|
||||
|
||||
fs.unlinkSync(pathname);
|
||||
} else {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
@ -308,6 +334,63 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('renderer:resequence-items', async (event, itemsToResequence) => {
|
||||
try {
|
||||
for(let item of itemsToResequence) {
|
||||
const bru = fs.readFileSync(item.pathname, 'utf8');
|
||||
const jsonData = bruToJson(bru);
|
||||
|
||||
if(jsonData.seq !== item.seq) {
|
||||
jsonData.seq = item.seq;
|
||||
const content = jsonToBru(jsonData);
|
||||
await writeFile(item.pathname, content);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('renderer:move-file-item', async (event, itemPath, destinationPath) => {
|
||||
try {
|
||||
const itemContent = fs.readFileSync(itemPath, 'utf8');
|
||||
const newItemPath = path.join(destinationPath, path.basename(itemPath));
|
||||
|
||||
moveRequestUid(itemPath, newItemPath);
|
||||
|
||||
fs.unlinkSync(itemPath);
|
||||
fs.writeFileSync(newItemPath, itemContent);
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('renderer:move-folder-item', async (event, folderPath, destinationPath) => {
|
||||
try {
|
||||
const folderName = path.basename(folderPath);
|
||||
const newFolderPath = path.join(destinationPath, folderName);
|
||||
|
||||
if(!fs.existsSync(folderPath)) {
|
||||
throw new Error(`folder: ${folderPath} does not exist`);
|
||||
}
|
||||
|
||||
if(fs.existsSync(newFolderPath)) {
|
||||
throw new Error(`folder: ${newFolderPath} already exists`);
|
||||
}
|
||||
|
||||
const bruFilesAtSource = await searchForBruFiles(folderPath);
|
||||
|
||||
for(let bruFile of bruFilesAtSource) {
|
||||
const newBruFilePath = bruFile.replace(folderPath, newFolderPath);
|
||||
moveRequestUid(bruFile, newBruFilePath);
|
||||
}
|
||||
|
||||
fs.renameSync(folderPath, newFolderPath);
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('renderer:ready', async (event) => {
|
||||
// reload last opened collections
|
||||
const lastOpened = lastOpenedCollections.getAll();
|
||||
|
@ -95,6 +95,25 @@ const browseDirectory = async (win) => {
|
||||
return isDirectory(resolvedPath) ? resolvedPath : false;
|
||||
};
|
||||
|
||||
const searchForFiles = (dir, extension) => {
|
||||
let results = [];
|
||||
const files = fs.readdirSync(dir);
|
||||
for (const file of files) {
|
||||
const filePath = path.join(dir, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
if (stat.isDirectory()) {
|
||||
results = results.concat(searchForFiles(filePath, extension));
|
||||
} else if (path.extname(file) === extension) {
|
||||
results.push(filePath);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
const searchForBruFiles = (dir) => {
|
||||
return searchForFiles(dir, '.bru');
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
isValidPathname,
|
||||
exists,
|
||||
@ -106,5 +125,7 @@ module.exports = {
|
||||
hasJsonExtension,
|
||||
hasBruExtension,
|
||||
createDirectory,
|
||||
browseDirectory
|
||||
browseDirectory,
|
||||
searchForFiles,
|
||||
searchForBruFiles
|
||||
};
|
||||
|
@ -44,6 +44,7 @@ const bruToJson = (fileContents) => {
|
||||
const json = {
|
||||
type: parsed.type || '',
|
||||
name: parsed.name || '',
|
||||
seq: parsed.seq || 1,
|
||||
request: {
|
||||
method: parsed.method || '',
|
||||
url: parsed.url || '',
|
||||
@ -78,6 +79,7 @@ const jsonToBru = (json) => {
|
||||
const {
|
||||
type,
|
||||
name,
|
||||
seq,
|
||||
request: {
|
||||
method,
|
||||
url,
|
||||
@ -92,6 +94,7 @@ method ${method}
|
||||
url ${url}
|
||||
type ${type}
|
||||
body-mode ${body ? body.mode : 'none'}
|
||||
seq ${seq ? seq : 1}
|
||||
`;
|
||||
|
||||
if(params && params.length) {
|
||||
|
@ -17,6 +17,7 @@ const inlineTag = sequenceOf([
|
||||
str('name'),
|
||||
str('method'),
|
||||
str('url'),
|
||||
str('seq'),
|
||||
str('body-mode')
|
||||
]),
|
||||
whitespace,
|
||||
|
@ -58,6 +58,7 @@ const requestSchema = Yup.object({
|
||||
const itemSchema = Yup.object({
|
||||
uid: uidSchema,
|
||||
type: Yup.string().oneOf(['http-request', 'graphql-request', 'folder']).required('type is required'),
|
||||
seq: Yup.number().min(1),
|
||||
name: Yup.string()
|
||||
.min(1, 'name must be atleast 1 characters')
|
||||
.max(50, 'name must be 100 characters or less')
|
||||
@ -69,7 +70,7 @@ const itemSchema = Yup.object({
|
||||
items: Yup.lazy(() => Yup.array().of(itemSchema)),
|
||||
filename: Yup.string().max(1024, 'filename cannot be more than 1024 characters').nullable(),
|
||||
pathname: Yup.string().max(1024, 'pathname cannot be more than 1024 characters').nullable()
|
||||
}).noUnknown(true).strict();
|
||||
}).noUnknown(true);
|
||||
|
||||
const collectionSchema = Yup.object({
|
||||
version: Yup.string().oneOf(['1']).required('version is required'),
|
||||
|
Loading…
Reference in New Issue
Block a user