From b89abe1ba64415c2e4df67af494698e0e519100b Mon Sep 17 00:00:00 2001 From: lohxt1 Date: Tue, 2 Jul 2024 20:19:24 +0530 Subject: [PATCH 01/17] draft: folder request sequencing draft --- .../CollectionItem/StyledWrapper.js | 14 ++++ .../Collection/CollectionItem/index.js | 70 ++++++++++++++----- .../Sidebar/Collections/Collection/index.js | 12 +--- .../ReduxStore/slices/collections/actions.js | 2 + .../ReduxStore/slices/collections/index.js | 12 ++++ packages/bruno-electron/src/app/watcher.js | 26 ++++++- packages/bruno-electron/src/bru/index.js | 10 +-- 7 files changed, 113 insertions(+), 33 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js index 14d7432fa2..4d0489c152 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js @@ -45,6 +45,20 @@ const Wrapper = styled.div` } } + &.item-target { + background: #ccc3; + } + + &.item-seperator { + .seperator { + bottom: 0px; + position: absolute; + height: 3px; + width: 100%; + background: #ccc3; + } + } + &.item-focused-in-tab { background: ${(props) => props.theme.sidebar.collection.item.bg}; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js index 9fce06eecf..baebf49a79 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js @@ -41,6 +41,11 @@ const CollectionItem = ({ item, collection, searchText }) => { const [runCollectionModalOpen, setRunCollectionModalOpen] = useState(false); const [itemIsCollapsed, setItemisCollapsed] = useState(item.collapsed); + const isFolder = isItemAFolder(item); + + const [hoverTime, setHoverTime] = useState(0); + const [action, setAction] = useState(null); + const [{ isDragging }, drag] = useDrag({ type: `COLLECTION_ITEM_${collection.uid}`, item: item, @@ -49,12 +54,25 @@ const CollectionItem = ({ item, collection, searchText }) => { }) }); - const [{ isOver }, drop] = useDrop({ + const [{ isOver, canDrop }, drop] = useDrop({ accept: `COLLECTION_ITEM_${collection.uid}`, drop: (draggedItem) => { if (draggedItem.uid !== item.uid) { - dispatch(moveItem(collection.uid, draggedItem.uid, item.uid)); + if (isFolder) { + if (action === 'SHORT_HOVER') { + if (itemIsCollapsed) { + // first item in the folder + } else { + // outside of the folder, but adjacent to the folder + } + } else { + dispatch(moveItem(collection.uid, draggedItem.uid, item.uid)); + } + } else { + dispatch(moveItem(collection.uid, draggedItem.uid, item.uid)); + } } + hoverTime > 0 && setHoverTime(0); }, canDrop: (draggedItem) => { return draggedItem.uid !== item.uid; @@ -64,6 +82,30 @@ const CollectionItem = ({ item, collection, searchText }) => { }) }); + useEffect(() => { + let timer; + if (isOver && !canDrop) { + timer = setInterval(() => { + setHoverTime((prevTime) => prevTime + 100); + }, 100); + } else { + setAction(null); + setHoverTime(0); + timer && clearInterval(timer); + } + return () => clearInterval(timer); + }, [isOver, canDrop]); + + useEffect(() => { + if (hoverTime >= 100 && hoverTime < 1000) { + setAction('SHORT_HOVER'); + } else if (hoverTime >= 1000) { + setAction('LONG_HOVER'); + } else { + setAction(null); + } + }, [hoverTime]); + useEffect(() => { if (searchText && searchText.length) { setItemisCollapsed(false); @@ -85,9 +127,11 @@ const CollectionItem = ({ item, collection, searchText }) => { 'rotate-90': !itemIsCollapsed }); - const itemRowClassName = classnames('flex collection-item-name items-center', { + const itemRowClassName = classnames('flex collection-item-name relative items-center', { 'item-focused-in-tab': item.uid == activeTabUid, - 'item-hovered': isOver + 'item-hovered': isOver && canDrop, + 'item-target': isOver && !canDrop && isFolder && action === 'LONG_HOVER', + 'item-seperator': isOver && !canDrop && (!isFolder || action === 'SHORT_HOVER') }); const scrollToTheActiveTab = () => { @@ -153,7 +197,6 @@ const CollectionItem = ({ item, collection, searchText }) => { let indents = range(item.depth); const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); - const isFolder = isItemAFolder(item); const className = classnames('flex flex-col w-full', { 'is-sidebar-dragging': isSidebarDragging @@ -176,10 +219,6 @@ const CollectionItem = ({ item, collection, searchText }) => { 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 handleGenerateCode = (e) => { e.stopPropagation(); dropdownTippyRef.current.hide(); @@ -199,8 +238,7 @@ const CollectionItem = ({ item, collection, searchText }) => { }) ); }; - const requestItems = sortRequestItems(filter(item.items, (i) => isItemARequest(i))); - const folderItems = sortFolderItems(filter(item.items, (i) => isItemAFolder(i))); + const items = sortRequestItems(filter(item.items, (i) => isItemARequest(i) || isItemAFolder(i))); return ( @@ -370,17 +408,13 @@ const CollectionItem = ({ item, collection, searchText }) => { +
{!itemIsCollapsed ? (
- {folderItems && folderItems.length - ? folderItems.map((i) => { - return ; - }) - : null} - {requestItems && requestItems.length - ? requestItems.map((i) => { + {items && items.length + ? items.map((i) => { return ; }) : null} diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js index 1c758f2716..97c113dc8d 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js @@ -119,8 +119,7 @@ const Collection = ({ collection, searchText }) => { 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))); + const items = sortRequestItems(filter(collection.items, (i) => isItemARequest(i) || isItemAFolder(i))); return ( @@ -235,13 +234,8 @@ const Collection = ({ collection, searchText }) => {
{!collectionIsCollapsed ? (
- {folderItems && folderItems.length - ? folderItems.map((i) => { - return ; - }) - : null} - {requestItems && requestItems.length - ? requestItems.map((i) => { + {items && items.length + ? items.map((i) => { return ; }) : null} diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index 483456a91f..d06fc0bdf9 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -161,6 +161,8 @@ export const saveFolderRoot = (collectionUid, folderUid) => (dispatch, getState) const { ipcRenderer } = window; + folder?.root?.meta?.seq && (folder.root.meta.seq = folder?.seq); + const folderData = { name: folder.name, pathname: folder.pathname, diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index 90927fa8e7..4edc002105 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -1316,6 +1316,7 @@ export const collectionsSlice = createSlice({ const folderItem = findItemInCollectionByPathname(collection, folderPath); if (folderItem) { folderItem.root = file.data; + folderItem.seq = file.data?.meta?.seq; } return; } @@ -1403,6 +1404,7 @@ export const collectionsSlice = createSlice({ collectionChangeFileEvent: (state, action) => { const { file } = action.payload; const collection = findCollectionByUid(state.collections, file.meta.collectionUid); + const isFolderRoot = file?.meta?.folderRoot ? true : false; // check and update collection root if (collection && file.meta.collectionRoot) { @@ -1410,6 +1412,16 @@ export const collectionsSlice = createSlice({ return; } + if (isFolderRoot) { + const folderPath = path.dirname(file?.meta?.pathname); + const folderItem = findItemInCollectionByPathname(collection, folderPath); + if (folderItem) { + folderItem.root = file.data; + folderItem.seq = file?.data?.meta?.seq; + } + return; + } + if (collection) { const item = findItemInCollection(collection, file.data.uid); diff --git a/packages/bruno-electron/src/app/watcher.js b/packages/bruno-electron/src/app/watcher.js index 2fbd4cc98f..6df0aef108 100644 --- a/packages/bruno-electron/src/app/watcher.js +++ b/packages/bruno-electron/src/app/watcher.js @@ -239,9 +239,7 @@ const add = async (win, pathname, collectionUid, collectionPath) => { } } - // Is this a folder.bru file? if (path.basename(pathname) === 'folder.bru') { - console.log('folder.bru file detected'); const file = { meta: { collectionUid, @@ -368,6 +366,30 @@ const change = async (win, pathname, collectionUid, collectionPath) => { } } + if (path.basename(pathname) === 'folder.bru') { + const file = { + meta: { + collectionUid, + pathname, + name: path.basename(pathname), + folderRoot: true + } + }; + + try { + let bruContent = fs.readFileSync(pathname, 'utf8'); + + file.data = collectionBruToJson(bruContent); + + hydrateBruCollectionFileWithUuid(file.data); + win.webContents.send('main:collection-tree-updated', 'change', file); + return; + } catch (err) { + console.error(err); + return; + } + } + if (hasBruExtension(pathname)) { try { const file = { diff --git a/packages/bruno-electron/src/bru/index.js b/packages/bruno-electron/src/bru/index.js index 07041b93b9..53993d779a 100644 --- a/packages/bruno-electron/src/bru/index.js +++ b/packages/bruno-electron/src/bru/index.js @@ -27,10 +27,11 @@ const collectionBruToJson = (bru) => { // add meta if it exists // this is only for folder bru file // in the future, all of this will be replaced by standard bru lang + const sequence = _.get(json, 'meta.seq'); if (json.meta) { transformedJson.meta = { name: json.meta.name, - seq: json.meta.seq + seq: !isNaN(sequence) ? Number(sequence) : 1 }; } @@ -61,10 +62,11 @@ const jsonToCollectionBru = (json) => { // add meta if it exists // this is only for folder bru file // in the future, all of this will be replaced by standard bru lang + const sequence = _.get(json, 'meta.seq'); if (json.meta) { collectionBruJson.meta = { name: json.meta.name, - seq: json.meta.seq + seq: !isNaN(sequence) ? Number(sequence) : 1 }; } @@ -123,7 +125,6 @@ const bruToJson = (bru) => { } const sequence = _.get(json, 'meta.seq'); - const transformedJson = { type: requestType, name: _.get(json, 'meta.name'), @@ -170,11 +171,12 @@ const jsonToBru = (json) => { type = 'http'; } + const sequence = _.get(json, 'seq'); const bruJson = { meta: { name: _.get(json, 'name'), type: type, - seq: _.get(json, 'seq') + seq: !isNaN(sequence) ? Number(sequence) : 1 }, http: { method: _.lowerCase(_.get(json, 'request.method')), From 849e45b0b6299821b69d34ab81ccf0249ae51423 Mon Sep 17 00:00:00 2001 From: lohxt1 Date: Wed, 3 Jul 2024 13:00:45 +0530 Subject: [PATCH 02/17] folders/files resequencing --- .../Collection/CollectionItem/index.js | 14 +- .../ReduxStore/slices/collections/actions.js | 142 ++++++++++++++++-- .../bruno-app/src/utils/collections/index.js | 56 +++---- packages/bruno-electron/src/app/watcher.js | 15 +- packages/bruno-electron/src/ipc/collection.js | 34 ++++- 5 files changed, 206 insertions(+), 55 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js index baebf49a79..42d17b3949 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js @@ -6,7 +6,7 @@ import { useDrag, useDrop } from 'react-dnd'; import { IconChevronRight, IconDots } from '@tabler/icons'; import { useSelector, useDispatch } from 'react-redux'; import { addTab, focusTab } from 'providers/ReduxStore/slices/tabs'; -import { moveItem, sendRequest } from 'providers/ReduxStore/slices/collections/actions'; +import { moveItem, reorderAroundFolderItem, sendRequest } from 'providers/ReduxStore/slices/collections/actions'; import { collectionFolderClicked } from 'providers/ReduxStore/slices/collections'; import Dropdown from 'components/Dropdown'; import NewRequest from 'components/Sidebar/NewRequest'; @@ -59,13 +59,15 @@ const CollectionItem = ({ item, collection, searchText }) => { drop: (draggedItem) => { if (draggedItem.uid !== item.uid) { if (isFolder) { + console.log('herererer'); if (action === 'SHORT_HOVER') { if (itemIsCollapsed) { - // first item in the folder - } else { // outside of the folder, but adjacent to the folder + dispatch(reorderAroundFolderItem(collection.uid, draggedItem.uid, item.uid)); + } else { + // first item in the folder } - } else { + } else if (action === 'LONG_HOVER') { dispatch(moveItem(collection.uid, draggedItem.uid, item.uid)); } } else { @@ -97,9 +99,9 @@ const CollectionItem = ({ item, collection, searchText }) => { }, [isOver, canDrop]); useEffect(() => { - if (hoverTime >= 100 && hoverTime < 1000) { + if (hoverTime >= 0 && hoverTime < 750) { setAction('SHORT_HOVER'); - } else if (hoverTime >= 1000) { + } else if (hoverTime >= 750) { setAction('LONG_HOVER'); } else { setAction(null); diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index d06fc0bdf9..d350f9e5cc 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -168,7 +168,6 @@ export const saveFolderRoot = (collectionUid, folderUid) => (dispatch, getState) pathname: folder.pathname, root: folder.root }; - console.log(folderData); ipcRenderer .invoke('renderer:save-folder-root', folderData) @@ -330,6 +329,8 @@ export const runCollectionFolder = (collectionUid, folderUid, recursive) => (dis export const newFolder = (folderName, collectionUid, itemUid) => (dispatch, getState) => { const state = getState(); const collection = findCollectionByUid(state.collections.collections, collectionUid); + const parentItem = itemUid ? findItemInCollection(collection, itemUid) : collection; + const items = filter(parentItem.items, (i) => isItemAFolder(i) || isItemARequest(i)); return new Promise((resolve, reject) => { if (!collection) { @@ -344,10 +345,22 @@ export const newFolder = (folderName, collectionUid, itemUid) => (dispatch, getS if (!folderWithSameNameExists) { const fullName = `${collection.pathname}${PATH_SEPARATOR}${folderName}`; const { ipcRenderer } = window; - ipcRenderer .invoke('renderer:new-folder', fullName) - .then(() => resolve()) + .then(async () => { + const folderData = { + name: folderName, + pathname: fullName, + root: { meta: { seq: items?.length + 1 } } + }; + ipcRenderer + .invoke('renderer:save-folder-root', folderData) + .then(resolve) + .catch((err) => { + toast.error('Failed to save folder settings!'); + reject(err); + }); + }) .catch((error) => reject(error)); } else { return reject(new Error('Duplicate folder names under same parent folder are not allowed')); @@ -365,7 +378,20 @@ export const newFolder = (folderName, collectionUid, itemUid) => (dispatch, getS ipcRenderer .invoke('renderer:new-folder', fullName) - .then(() => resolve()) + .then(async () => { + const folderData = { + name: folderName, + pathname: fullName, + root: { meta: { seq: items?.length + 1 } } + }; + ipcRenderer + .invoke('renderer:save-folder-root', folderData) + .then(resolve) + .catch((err) => { + toast.error('Failed to save folder settings!'); + reject(err); + }); + }) .catch((error) => reject(error)); } else { return reject(new Error('Duplicate folder names under same parent folder are not allowed')); @@ -628,7 +654,13 @@ export const moveItem = (collectionUid, draggedItemUid, targetItemUid) => (dispa // 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(); + moveCollectionItem(collectionCopy, draggedItem, targetItem); + const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy); + + return ipcRenderer + .invoke('renderer:resequence-items', itemsToResequence) + .then(resolve) + .catch((error) => reject(error)); } // folder dragged into a file which is a child of the folder @@ -636,6 +668,21 @@ export const moveItem = (collectionUid, draggedItemUid, targetItemUid) => (dispa return resolve(); } + // folder dragged into a file which is in a different folder + if (isItemAFolder(draggedItem) && isItemARequest(targetItem) && !sameParent) { + const draggedItemPathname = draggedItem.pathname; + moveCollectionItem(collectionCopy, draggedItem, targetItem); + const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy); + const itemsToResequence2 = getItemsToResequence(targetItemParent, collectionCopy); + + return ipcRenderer + .invoke('renderer:move-folder-item', draggedItemPathname, targetItemParent.pathname) + .then(() => ipcRenderer.invoke('renderer:resequence-items', itemsToResequence)) + .then(() => ipcRenderer.invoke('renderer:resequence-items', itemsToResequence2)) + .then(resolve) + .catch((error) => reject(error)); + } + // folder dragged into a file which is at the root level if (isItemAFolder(draggedItem) && isItemARequest(targetItem) && !targetItemParent) { const draggedItemPathname = draggedItem.pathname; @@ -658,6 +705,83 @@ export const moveItem = (collectionUid, draggedItemUid, targetItemUid) => (dispa }); }; +// cases when the target item is a folder and we want to reorder dragged files and folders around it +export const reorderAroundFolderItem = (collectionUid, draggedItemUid, targetItemUid) => (dispatch, getState) => { + const state = getState(); + const collection = findCollectionByUid(state.collections.collections, collectionUid); + + return new Promise((resolve, reject) => { + if (!collection) { + return reject(new Error('Collection not found')); + } + + const collectionCopy = cloneDeep(collection); + const draggedItem = findItemInCollection(collectionCopy, draggedItemUid); + const targetItem = findItemInCollection(collectionCopy, targetItemUid); + + if (!draggedItem) { + return reject(new Error('Dragged item not found')); + } + + if (!targetItem) { + return reject(new Error('Target item not found')); + } + + const draggedItemParent = findParentItemInCollection(collectionCopy, draggedItemUid); + const targetItemParent = findParentItemInCollection(collectionCopy, targetItemUid); + const sameParent = draggedItemParent === targetItemParent; + + // file/folder item dragged onto another folder item and both are in the same folder + // this is also true when both items are at the root level + if (sameParent) { + moveCollectionItem(collectionCopy, draggedItem, targetItem); + const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy); + return ipcRenderer + .invoke('renderer:resequence-items', itemsToResequence) + .then(resolve) + .catch((error) => reject(error)); + } + + // file/folder item dragged onto another folder item which is at the root level + if (!targetItemParent) { + const draggedItemPathname = draggedItem.pathname; + moveCollectionItem(collectionCopy, draggedItem, targetItem); + const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy); + const itemsToResequence2 = getItemsToResequence(targetItemParent, collectionCopy); + + return ipcRenderer + .invoke( + isItemAFolder(draggedItem) ? 'renderer:move-folder-item' : '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/folder item dragged onto another folder item and both are in different folders + if (!sameParent) { + const draggedItemPathname = draggedItem.pathname; + moveCollectionItem(collectionCopy, draggedItem, targetItem); + const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy); + const itemsToResequence2 = getItemsToResequence(targetItemParent, collectionCopy); + + return ipcRenderer + .invoke( + isItemAFolder(draggedItem) ? 'renderer:move-folder-item' : '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)); + } + }); +}; + export const moveItemToRootOfCollection = (collectionUid, draggedItemUid) => (dispatch, getState) => { const state = getState(); const collection = findCollectionByUid(state.collections.collections, collectionUid); @@ -745,8 +869,8 @@ export const newHttpRequest = (params) => (dispatch, getState) => { collection.items, (i) => i.type !== 'folder' && trim(i.filename) === trim(filename) ); - const requestItems = filter(collection.items, (i) => i.type !== 'folder'); - item.seq = requestItems.length + 1; + const items = filter(collection.items, (i) => isItemAFolder(i) || isItemARequest(i)); + item.seq = items.length + 1; if (!reqWithSameNameExists) { const fullName = `${collection.pathname}${PATH_SEPARATOR}${filename}`; @@ -772,8 +896,8 @@ export const newHttpRequest = (params) => (dispatch, getState) => { currentItem.items, (i) => i.type !== 'folder' && trim(i.filename) === trim(filename) ); - const requestItems = filter(currentItem.items, (i) => i.type !== 'folder'); - item.seq = requestItems.length + 1; + const items = filter(currentItem.items, (i) => isItemAFolder(i) || isItemARequest(i)); + item.seq = items.length + 1; if (!reqWithSameNameExists) { const fullName = `${currentItem.pathname}${PATH_SEPARATOR}${filename}`; const { ipcRenderer } = window; diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index 6145ee2006..a992deaa5e 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -133,34 +133,26 @@ export const findEnvironmentInCollection = (collection, envUid) => { export const moveCollectionItem = (collection, draggedItem, targetItem) => { let draggedItemParent = findParentItemInCollection(collection, draggedItem.uid); - if (draggedItemParent) { draggedItemParent.items = sortBy(draggedItemParent.items, (item) => item.seq); draggedItemParent.items = filter(draggedItemParent.items, (i) => i.uid !== draggedItem.uid); - draggedItem.pathname = path.join(draggedItemParent.pathname, draggedItem.filename); + draggedItem.pathname = path.join(draggedItemParent.pathname, path.basename(draggedItem.pathname)); } else { collection.items = sortBy(collection.items, (item) => item.seq); collection.items = filter(collection.items, (i) => i.uid !== draggedItem.uid); } - if (targetItem.type === 'folder') { - targetItem.items = sortBy(targetItem.items || [], (item) => item.seq); - targetItem.items.push(draggedItem); - draggedItem.pathname = path.join(targetItem.pathname, draggedItem.filename); + let targetItemParent = findParentItemInCollection(collection, targetItem.uid); + if (targetItemParent) { + targetItemParent.items = sortBy(targetItemParent.items, (item) => item.seq); + let targetItemIndex = findIndex(targetItemParent.items, (i) => i.uid === targetItem.uid); + targetItemParent.items.splice(targetItemIndex + 1, 0, draggedItem); + draggedItem.pathname = path.join(targetItemParent.pathname, path.basename(draggedItem.pathname)); } else { - let targetItemParent = findParentItemInCollection(collection, targetItem.uid); - - if (targetItemParent) { - targetItemParent.items = sortBy(targetItemParent.items, (item) => item.seq); - 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 { - collection.items = sortBy(collection.items, (item) => item.seq); - 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); - } + collection.items = sortBy(collection.items, (item) => item.seq); + let targetItemIndex = findIndex(collection.items, (i) => i.uid === targetItem.uid); + collection.items.splice(targetItemIndex + 1, 0, draggedItem); + draggedItem.pathname = path.join(collection.pathname, path.basename(draggedItem.pathname)); } }; @@ -189,12 +181,13 @@ export const getItemsToResequence = (parent, collection) => { if (!parent) { let index = 1; each(collection.items, (item) => { - if (isItemARequest(item)) { - itemsToResequence.push({ - pathname: item.pathname, - seq: index++ - }); - } + // if (isItemARequest(item)) { + itemsToResequence.push({ + pathname: item.pathname, + seq: index++, + type: isItemAFolder(item) ? 'folder' : 'request' + }); + // } }); return itemsToResequence; } @@ -202,12 +195,13 @@ export const getItemsToResequence = (parent, collection) => { if (parent.items && parent.items.length) { let index = 1; each(parent.items, (item) => { - if (isItemARequest(item)) { - itemsToResequence.push({ - pathname: item.pathname, - seq: index++ - }); - } + // if (isItemARequest(item)) { + itemsToResequence.push({ + pathname: item.pathname, + seq: index++, + type: isItemAFolder(item) ? 'folder' : 'request' + }); + // } }); return itemsToResequence; } diff --git a/packages/bruno-electron/src/app/watcher.js b/packages/bruno-electron/src/app/watcher.js index 6df0aef108..b0314df30e 100644 --- a/packages/bruno-electron/src/app/watcher.js +++ b/packages/bruno-electron/src/app/watcher.js @@ -3,7 +3,7 @@ const fs = require('fs'); const path = require('path'); const chokidar = require('chokidar'); const { hasBruExtension } = require('../utils/filesystem'); -const { bruToEnvJson, bruToJson, collectionBruToJson } = require('../bru'); +const { bruToEnvJson, bruToJson, collectionBruToJson, jsonToCollectionBru } = require('../bru'); const { dotenvToJson } = require('@usebruno/lang'); const { uuid } = require('../utils/common'); @@ -299,6 +299,19 @@ const addDirectory = (win, pathname, collectionUid, collectionPath) => { name: path.basename(pathname) } }; + + const folderBruFilePath = path.join(pathname, 'folder.bru'); + if (!fs.existsSync(folderBruFilePath)) { + let folderData = { + meta: { + name: path.basename(pathname), + seq: 0 + } + }; + const content = jsonToCollectionBru(folderData); + fs.writeFileSync(folderBruFilePath, content); + } + win.webContents.send('main:collection-tree-updated', 'addDir', directory); }; diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index 0291293081..14cfbc7637 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -20,6 +20,7 @@ const { generateUidBasedOnHash, stringifyJson, safeParseJSON, safeStringifyJSON const { moveRequestUid, deleteRequestUid } = require('../cache/requestUids'); const { deleteCookiesForDomain, getDomainsWithCookies } = require('../utils/cookies'); const EnvironmentSecretsStore = require('../store/env-secrets'); +const { collectionBruToJson } = require('@usebruno/lang'); const environmentSecretsStore = new EnvironmentSecretsStore(); @@ -158,7 +159,8 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection const folderBruFilePath = path.join(folderPathname, 'folder.bru'); folderRoot.meta = { - name: folderName + name: folderName, + ...(folderRoot.meta || {}) }; const content = jsonToCollectionBru(folderRoot); @@ -538,13 +540,29 @@ 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); + if (item?.type === 'folder') { + const folderRootPath = path.join(item.pathname, 'folder.bru'); + if (fs.existsSync(folderRootPath)) { + const bru = fs.readFileSync(folderRootPath, 'utf8'); + const jsonData = collectionBruToJson(bru); + + if (jsonData?.meta?.seq !== item.seq) { + jsonData.meta.seq = item.seq; + const content = jsonToCollectionBru(jsonData); + await writeFile(folderRootPath, content); + } + } + } else { + if (fs.existsSync(item.pathname)) { + 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) { From b0051bd620a04f619c1aacca5131a36b888bd3f8 Mon Sep 17 00:00:00 2001 From: lohxt1 Date: Wed, 3 Jul 2024 20:56:08 +0530 Subject: [PATCH 03/17] file/folder sequencing edge cases --- .../CollectionItem/StyledWrapper.js | 20 ++++++++++++++ .../Collection/CollectionItem/index.js | 6 +++-- .../ReduxStore/slices/collections/actions.js | 9 +++++-- .../bruno-app/src/utils/collections/index.js | 27 ++++++++++++++++--- 4 files changed, 54 insertions(+), 8 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js index 4d0489c152..b82b4a7fa5 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js @@ -57,6 +57,26 @@ const Wrapper = styled.div` width: 100%; background: #ccc3; } + .seperator-blinker { + bottom: 0px; + position: absolute; + height: 3px; + width: 100%; + background: #ccc3; + animation: pulsing 0.25s infinite; + } + } + + @keyframes pulsing { + 0% { + opacity: 0; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0; + } } &.item-focused-in-tab { diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js index 42d17b3949..1a193016aa 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js @@ -59,15 +59,17 @@ const CollectionItem = ({ item, collection, searchText }) => { drop: (draggedItem) => { if (draggedItem.uid !== item.uid) { if (isFolder) { - console.log('herererer'); if (action === 'SHORT_HOVER') { if (itemIsCollapsed) { // outside of the folder, but adjacent to the folder dispatch(reorderAroundFolderItem(collection.uid, draggedItem.uid, item.uid)); } else { // first item in the folder + // could be refactored to seperate this into a seperate function? + dispatch(moveItem(collection.uid, draggedItem.uid, item.uid)); } } else if (action === 'LONG_HOVER') { + // could be refactored to seperate this into a seperate function? dispatch(moveItem(collection.uid, draggedItem.uid, item.uid)); } } else { @@ -410,7 +412,7 @@ const CollectionItem = ({ item, collection, searchText }) => {
-
+
{!itemIsCollapsed ? ( diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index d350f9e5cc..6ba518faf2 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -41,7 +41,7 @@ import { closeAllCollectionTabs } from 'providers/ReduxStore/slices/tabs'; import { resolveRequestFilename } from 'utils/common/platform'; import { parseQueryParams, splitOnFirst } from 'utils/url/index'; import { sendCollectionOauth2Request as _sendCollectionOauth2Request } from 'utils/network/index'; -import { name } from 'file-loader'; +import { moveCollectionItemToFolder } from 'utils/collections/index'; export const renameCollection = (newName, collectionUid) => (dispatch, getState) => { const state = getState(); @@ -631,7 +631,7 @@ export const moveItem = (collectionUid, draggedItemUid, targetItemUid) => (dispa // file item dragged into another folder if (isItemARequest(draggedItem) && isItemAFolder(targetItem) && draggedItemParent !== targetItem) { const draggedItemPathname = draggedItem.pathname; - moveCollectionItem(collectionCopy, draggedItem, targetItem); + moveCollectionItemToFolder(collectionCopy, draggedItem, targetItem); const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy); const itemsToResequence2 = getItemsToResequence(targetItem, collectionCopy); @@ -696,9 +696,14 @@ export const moveItem = (collectionUid, draggedItemUid, targetItemUid) => (dispa // folder dragged into another folder if (isItemAFolder(draggedItem) && isItemAFolder(targetItem) && draggedItemParent !== targetItem) { const draggedItemPathname = draggedItem.pathname; + moveCollectionItemToFolder(collectionCopy, draggedItem, targetItem); + const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy); + const itemsToResequence2 = getItemsToResequence(targetItem, collectionCopy); return ipcRenderer .invoke('renderer:move-folder-item', draggedItemPathname, targetItem.pathname) + .then(() => ipcRenderer.invoke('renderer:resequence-items', itemsToResequence)) + .then(() => ipcRenderer.invoke('renderer:resequence-items', itemsToResequence2)) .then(resolve) .catch((error) => reject(error)); } diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index a992deaa5e..0e87e8d93f 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -133,6 +133,7 @@ export const findEnvironmentInCollection = (collection, envUid) => { export const moveCollectionItem = (collection, draggedItem, targetItem) => { let draggedItemParent = findParentItemInCollection(collection, draggedItem.uid); + if (draggedItemParent) { draggedItemParent.items = sortBy(draggedItemParent.items, (item) => item.seq); draggedItemParent.items = filter(draggedItemParent.items, (i) => i.uid !== draggedItem.uid); @@ -143,6 +144,7 @@ export const moveCollectionItem = (collection, draggedItem, targetItem) => { } let targetItemParent = findParentItemInCollection(collection, targetItem.uid); + if (targetItemParent) { targetItemParent.items = sortBy(targetItemParent.items, (item) => item.seq); let targetItemIndex = findIndex(targetItemParent.items, (i) => i.uid === targetItem.uid); @@ -156,6 +158,27 @@ export const moveCollectionItem = (collection, draggedItem, targetItem) => { } }; +export const moveCollectionItemToFolder = (collection, draggedItem, targetItem) => { + let draggedItemParent = findParentItemInCollection(collection, draggedItem.uid); + if (draggedItemParent) { + draggedItemParent.items = sortBy(draggedItemParent.items, (item) => item.seq); + draggedItemParent.items = filter(draggedItemParent.items, (i) => i.uid !== draggedItem.uid); + draggedItem.pathname = path.join(draggedItemParent.pathname, path.basename(draggedItem.pathname)); + } else { + collection.items = sortBy(collection.items, (item) => item.seq); + collection.items = filter(collection.items, (i) => i.uid !== draggedItem.uid); + } + + targetItem.items = sortBy(targetItem.items || [], (item) => item.seq); + draggedItem.seq = -1; + targetItem.items.splice(0, 0, draggedItem); + targetItem.items = targetItem.items?.map((item, index) => { + item.seq = index + 1; + return item; + }); + draggedItem.pathname = path.join(targetItem.pathname, path.basename(draggedItem.pathname)); +}; + export const moveCollectionItemToRootOfCollection = (collection, draggedItem) => { let draggedItemParent = findParentItemInCollection(collection, draggedItem.uid); @@ -181,13 +204,11 @@ export const getItemsToResequence = (parent, collection) => { if (!parent) { let index = 1; each(collection.items, (item) => { - // if (isItemARequest(item)) { itemsToResequence.push({ pathname: item.pathname, seq: index++, type: isItemAFolder(item) ? 'folder' : 'request' }); - // } }); return itemsToResequence; } @@ -195,13 +216,11 @@ export const getItemsToResequence = (parent, collection) => { if (parent.items && parent.items.length) { let index = 1; each(parent.items, (item) => { - // if (isItemARequest(item)) { itemsToResequence.push({ pathname: item.pathname, seq: index++, type: isItemAFolder(item) ? 'folder' : 'request' }); - // } }); return itemsToResequence; } From 50492201bc38800990f49d021f519c637004d596 Mon Sep 17 00:00:00 2001 From: Pooja Belaramani Date: Wed, 12 Feb 2025 12:03:01 +0530 Subject: [PATCH 04/17] add: border for target item --- .../CollectionItem/StyledWrapper.js | 44 ++++++----- .../Collection/CollectionItem/index.js | 78 +++++++++++++++---- .../Collections/Collection/StyledWrapper.js | 9 +++ .../Sidebar/Collections/Collection/index.js | 25 +++++- packages/bruno-app/src/themes/dark.js | 7 ++ packages/bruno-app/src/themes/light.js | 7 ++ 6 files changed, 131 insertions(+), 39 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js index b82b4a7fa5..d0dcc18032 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js @@ -57,26 +57,6 @@ const Wrapper = styled.div` width: 100%; background: #ccc3; } - .seperator-blinker { - bottom: 0px; - position: absolute; - height: 3px; - width: 100%; - background: #ccc3; - animation: pulsing 0.25s infinite; - } - } - - @keyframes pulsing { - 0% { - opacity: 0; - } - 50% { - opacity: 1; - } - 100% { - opacity: 0; - } } &.item-focused-in-tab { @@ -108,6 +88,30 @@ const Wrapper = styled.div` &.is-sidebar-dragging .collection-item-name { cursor: inherit; } + + .collection-item-name.drop-target { + border: ${(props) => props.theme.dragAndDrop.borderStyle} ${(props) => props.theme.dragAndDrop.border}; + border-radius: 6px; + background-color: ${(props) => props.theme.dragAndDrop.hoverBg}; + transition: ${(props) => props.theme.dragAndDrop.transition}; + box-shadow: 0 0 0 2px ${(props) => props.theme.dragAndDrop.hoverBg}; + } + + .collection-item-name.drop-target-above { + border: none; + border-top: ${(props) => props.theme.dragAndDrop.borderStyle} ${(props) => props.theme.dragAndDrop.border}; + margin-top: -2px; + background: transparent; + transition: ${(props) => props.theme.dragAndDrop.transition}; + } + + .collection-item-name.drop-target-below { + border: none; + border-bottom: ${(props) => props.theme.dragAndDrop.borderStyle} ${(props) => props.theme.dragAndDrop.border}; + margin-bottom: -2px; + background: transparent; + transition: ${(props) => props.theme.dragAndDrop.transition}; + } `; export default Wrapper; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js index 1a193016aa..cf2f88f6d3 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js @@ -45,10 +45,13 @@ const CollectionItem = ({ item, collection, searchText }) => { const [hoverTime, setHoverTime] = useState(0); const [action, setAction] = useState(null); + const [dropPosition, setDropPosition] = useState(null); // 'above', 'below', or 'inside' const [{ isDragging }, drag] = useDrag({ type: `COLLECTION_ITEM_${collection.uid}`, - item: item, + item: () => { + return item; + }, collect: (monitor) => ({ isDragging: monitor.isDragging() }) @@ -56,26 +59,53 @@ const CollectionItem = ({ item, collection, searchText }) => { const [{ isOver, canDrop }, drop] = useDrop({ accept: `COLLECTION_ITEM_${collection.uid}`, - drop: (draggedItem) => { + hover: (draggedItem, monitor) => { if (draggedItem.uid !== item.uid) { - if (isFolder) { - if (action === 'SHORT_HOVER') { - if (itemIsCollapsed) { - // outside of the folder, but adjacent to the folder - dispatch(reorderAroundFolderItem(collection.uid, draggedItem.uid, item.uid)); + const hoverBoundingRect = ref.current?.getBoundingClientRect(); + const clientOffset = monitor.getClientOffset(); + + if (hoverBoundingRect && clientOffset) { + // Get vertical middle + const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; + // Get mouse position + const clientY = clientOffset.y - hoverBoundingRect.top; + + // Define drop zones + const upperQuarter = hoverBoundingRect.height * 0.25; + const lowerQuarter = hoverBoundingRect.height * 0.75; + + // More precise position detection + if (clientY < upperQuarter) { + setDropPosition('above'); + setAction('SHORT_HOVER'); + } else if (clientY > lowerQuarter) { + setDropPosition('below'); + setAction('SHORT_HOVER'); + } else { + if (isFolder) { + setDropPosition('inside'); + setAction('LONG_HOVER'); } else { - // first item in the folder - // could be refactored to seperate this into a seperate function? - dispatch(moveItem(collection.uid, draggedItem.uid, item.uid)); + // If not a folder, default to above/below based on middle point + setDropPosition(clientY < hoverMiddleY ? 'above' : 'below'); + setAction('SHORT_HOVER'); } - } else if (action === 'LONG_HOVER') { - // could be refactored to seperate this into a seperate function? - dispatch(moveItem(collection.uid, draggedItem.uid, item.uid)); } - } else { + } + } + }, + drop: (draggedItem) => { + if (draggedItem.uid !== item.uid) { + if (isFolder && dropPosition === 'inside') { + // Move inside folder dispatch(moveItem(collection.uid, draggedItem.uid, item.uid)); + } else { + // Move above or below + dispatch(reorderAroundFolderItem(collection.uid, draggedItem.uid, item.uid)); } } + setDropPosition(null); + setAction(null); hoverTime > 0 && setHoverTime(0); }, canDrop: (draggedItem) => { @@ -86,6 +116,12 @@ const CollectionItem = ({ item, collection, searchText }) => { }) }); + useEffect(() => { + if (!isOver) { + setDropPosition(null); + } + }, [isOver]); + useEffect(() => { let timer; if (isOver && !canDrop) { @@ -131,9 +167,14 @@ const CollectionItem = ({ item, collection, searchText }) => { 'rotate-90': !itemIsCollapsed }); + const ref = useRef(null); + const itemRowClassName = classnames('flex collection-item-name relative items-center', { 'item-focused-in-tab': item.uid == activeTabUid, 'item-hovered': isOver && canDrop, + 'drop-target': isOver && dropPosition === 'inside', + 'drop-target-above': isOver && dropPosition === 'above', + 'drop-target-below': isOver && dropPosition === 'below', 'item-target': isOver && !canDrop && isFolder && action === 'LONG_HOVER', 'item-seperator': isOver && !canDrop && (!isFolder || action === 'SHORT_HOVER') }); @@ -267,7 +308,13 @@ const CollectionItem = ({ item, collection, searchText }) => { {generateCodeItemModalOpen && ( setGenerateCodeItemModalOpen(false)} /> )} -
drag(drop(node))}> +
{ + ref.current = node; + drag(drop(node)); + }} + >
{indents && indents.length ? indents.map((i) => { @@ -412,7 +459,6 @@ const CollectionItem = ({ item, collection, searchText }) => {
-
{!itemIsCollapsed ? ( diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js index b8e0d21fd7..1e710b95b5 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js @@ -53,6 +53,15 @@ 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; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js index 97c113dc8d..88eabaaa2e 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js @@ -30,6 +30,7 @@ const Collection = ({ collection, searchText }) => { const [showExportCollectionModal, setShowExportCollectionModal] = useState(false); const [showRemoveCollectionModal, setShowRemoveCollectionModal] = useState(false); const [collectionIsCollapsed, setCollectionIsCollapsed] = useState(collection.collapsed); + const [isDropTarget, setIsDropTarget] = useState(false); const dispatch = useDispatch(); const menuDropdownTippyRef = useRef(); @@ -89,9 +90,15 @@ const Collection = ({ collection, searchText }) => { ); }; - const [{ isOver }, drop] = useDrop({ + const [{ isOver, canDrop }, drop] = useDrop({ accept: `COLLECTION_ITEM_${collection.uid}`, + hover: (draggedItem, monitor) => { + if (draggedItem.collectionUid !== collection.uid) { + setIsDropTarget(true); + } + }, drop: (draggedItem) => { + setIsDropTarget(false); dispatch(moveItemToRootOfCollection(collection.uid, draggedItem.uid)); }, canDrop: (draggedItem) => { @@ -99,10 +106,22 @@ const Collection = ({ collection, searchText }) => { return true; }, collect: (monitor) => ({ - isOver: monitor.isOver() + isOver: monitor.isOver(), + canDrop: monitor.canDrop() }) }); + const collectionNameClassName = classnames('flex py-1 collection-name items-center', { + 'drop-target': isDropTarget && isOver + }); + + // Clean up drop target state when drag ends + useEffect(() => { + if (!isOver) { + setIsDropTarget(false); + } + }, [isOver]); + if (searchText && searchText.length) { if (!doesCollectionHaveItemsMatchingSearchText(collection, searchText)) { return null; @@ -137,7 +156,7 @@ const Collection = ({ collection, searchText }) => { {showCloneCollectionModalOpen && ( setShowCloneCollectionModalOpen(false)} /> )} -
+
Date: Mon, 24 Feb 2025 11:35:16 +0530 Subject: [PATCH 05/17] fix: drpoing folder in first --- .../Collection/CollectionItem/index.js | 16 ++-- .../Collections/Collection/StyledWrapper.js | 9 +++ .../Sidebar/Collections/Collection/index.js | 47 ++++++----- .../ReduxStore/slices/collections/actions.js | 79 +++++++------------ 4 files changed, 73 insertions(+), 78 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js index e7fbad2a8c..cdfce67ba7 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js @@ -73,13 +73,16 @@ const CollectionItem = ({ item, collection, searchText }) => { // Get vertical middle and mouse position relative to the element const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; const clientY = clientOffset.y - hoverBoundingRect.top; - // Define drop zones - const upperQuarter = hoverBoundingRect.height * 0.25; - const lowerQuarter = hoverBoundingRect.height * 0.75; - if (clientY < upperQuarter) { + + // Define drop zones - adjust the thresholds to make it easier to drop at the top + const upperThreshold = hoverBoundingRect.height * 0.35; // Increased from 0.25 + const lowerThreshold = hoverBoundingRect.height * 0.65; // Decreased from 0.75 + + // Determine drop position based on mouse location + if (clientY < upperThreshold) { setDropPosition('above'); setAction('SHORT_HOVER'); - } else if (clientY > lowerQuarter) { + } else if (clientY > lowerThreshold) { setDropPosition('below'); setAction('SHORT_HOVER'); } else { @@ -87,6 +90,7 @@ const CollectionItem = ({ item, collection, searchText }) => { setDropPosition('inside'); setAction('LONG_HOVER'); } else { + // For non-folder items, use the middle point to determine above/below setDropPosition(clientY < hoverMiddleY ? 'above' : 'below'); setAction('SHORT_HOVER'); } @@ -101,7 +105,7 @@ const CollectionItem = ({ item, collection, searchText }) => { dispatch(moveItem(collection.uid, draggedItem.uid, item.uid)); } else { // Reorder above or below - dispatch(reorderAroundFolderItem(collection.uid, draggedItem.uid, item.uid)); + dispatch(reorderAroundFolderItem(collection.uid, draggedItem.uid, item.uid, dropPosition)); } } setDropPosition(null); diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js index 67f461c865..f9f2eafe10 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js @@ -62,6 +62,15 @@ const Wrapper = styled.div` color: white; } } + + &.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}; + } } .collection-name.drop-target { diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js index 06e6bdd032..dbcb62da63 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js @@ -142,30 +142,30 @@ const Collection = ({ collection, searchText }) => { }); const [{ isOver, canDrop }, drop] = useDrop({ - accept: ["collection", `collection-item-${collection.uid}`], + accept: `COLLECTION_ITEM_${collection.uid}`, hover: (draggedItem, monitor) => { - if (draggedItem.collectionUid) { - if (draggedItem.collectionUid !== collection.uid) { - setIsDropTarget(true); - } else { - setIsDropTarget(false); - } - } else { - setIsDropTarget(false); + // Only show drop target styling when hovering over the collection name area + const hoverBoundingRect = collectionRef.current?.getBoundingClientRect(); + const clientOffset = monitor.getClientOffset(); + + if (hoverBoundingRect && clientOffset) { + const hoverY = clientOffset.y - hoverBoundingRect.top; + // Only set as drop target if hovering over the collection name area + setIsDropTarget(hoverY < 30); // Approximate height of collection name area } }, drop: (draggedItem, monitor) => { - setIsDropTarget(false); - const itemType = monitor.getItemType(); - if (isCollectionItem(itemType)) { - dispatch(moveItemToRootOfCollection(collection.uid, draggedItem.uid)); - } else { - dispatch(moveCollectionAndPersist({ draggedItem, targetItem: collection })); + // Only handle drop if it's on the collection name area + const hoverBoundingRect = collectionRef.current?.getBoundingClientRect(); + const clientOffset = monitor.getClientOffset(); + + if (hoverBoundingRect && clientOffset) { + const hoverY = clientOffset.y - hoverBoundingRect.top; + if (hoverY < 30) { + dispatch(moveItemToRootOfCollection(collection.uid, draggedItem.uid)); + } } }, - canDrop: (draggedItem) => { - return draggedItem.uid !== collection.uid; - }, collect: (monitor) => ({ isOver: monitor.isOver(), canDrop: monitor.canDrop() @@ -182,8 +182,7 @@ const Collection = ({ collection, searchText }) => { }, [isOver]); const collectionRowClassName = classnames('flex py-1 collection-name items-center', { - 'drop-target': isDropTarget && isOver, - 'item-hovered': isOver && !isDropTarget + 'drop-target': isDropTarget && isOver }); if (searchText && searchText.length) { @@ -222,7 +221,13 @@ const Collection = ({ collection, searchText }) => { {showCloneCollectionModalOpen && ( setShowCloneCollectionModalOpen(false)} /> )} -
+
{ + collectionRef.current = node; + drop(node); + }} + >
(dispa }; // cases when the target item is a folder and we want to reorder dragged files and folders around it -export const reorderAroundFolderItem = (collectionUid, draggedItemUid, targetItemUid) => (dispatch, getState) => { +export const reorderAroundFolderItem = (collectionUid, draggedItemUid, targetItemUid, dropPosition) => (dispatch, getState) => { const state = getState(); const collection = findCollectionByUid(state.collections.collections, collectionUid); @@ -742,68 +742,45 @@ export const reorderAroundFolderItem = (collectionUid, draggedItemUid, targetIte const collectionCopy = cloneDeep(collection); const draggedItem = findItemInCollection(collectionCopy, draggedItemUid); const targetItem = findItemInCollection(collectionCopy, targetItemUid); - - if (!draggedItem) { - return reject(new Error('Dragged item not found')); - } - - if (!targetItem) { - return reject(new Error('Target item not found')); - } - const draggedItemParent = findParentItemInCollection(collectionCopy, draggedItemUid); const targetItemParent = findParentItemInCollection(collectionCopy, targetItemUid); const sameParent = draggedItemParent === targetItemParent; - // file/folder item dragged onto another folder item and both are in the same folder - // this is also true when both items are at the root level - if (sameParent) { - moveCollectionItem(collectionCopy, draggedItem, targetItem); - const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy); - console.log('sameParent', draggedItem, targetItem, itemsToResequence); - return ipcRenderer - .invoke('renderer:resequence-items', itemsToResequence) - .then(resolve) - .catch((error) => reject(error)); - } + // Update moveCollectionItem to handle position + const moveCollectionItemWithPosition = (collection, draggedItem, targetItem, position) => { + const items = draggedItemParent ? draggedItemParent.items : collection.items; + const targetIndex = items.findIndex(i => i.uid === targetItem.uid); + const draggedIndex = items.findIndex(i => i.uid === draggedItem.uid); - // file/folder item dragged onto another folder item which is at the root level - if (!targetItemParent) { - const draggedItemPathname = draggedItem.pathname; - moveCollectionItem(collectionCopy, draggedItem, targetItem); - const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy); - const itemsToResequence2 = getItemsToResequence(targetItemParent, collectionCopy); + // Remove dragged item + items.splice(draggedIndex, 1); - return ipcRenderer - .invoke( - isItemAFolder(draggedItem) ? 'renderer:move-folder-item' : '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)); - } + // Calculate new index based on position + let newIndex = position === 'above' ? targetIndex : targetIndex + 1; + if (draggedIndex < targetIndex) { + newIndex--; + } - // file/folder item dragged onto another folder item and both are in different folders - if (!sameParent) { - const draggedItemPathname = draggedItem.pathname; - moveCollectionItem(collectionCopy, draggedItem, targetItem); - const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy); - const itemsToResequence2 = getItemsToResequence(targetItemParent, collectionCopy); + // Insert at new position + items.splice(newIndex, 0, draggedItem); + + // Update sequences + items.forEach((item, index) => { + item.seq = index + 1; + }); + }; + if (sameParent) { + moveCollectionItemWithPosition(collectionCopy, draggedItem, targetItem, dropPosition); + const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy); + return ipcRenderer - .invoke( - isItemAFolder(draggedItem) ? 'renderer:move-folder-item' : 'renderer:move-file-item', - draggedItemPathname, - targetItemParent.pathname - ) - .then(() => ipcRenderer.invoke('renderer:resequence-items', itemsToResequence)) - .then(() => ipcRenderer.invoke('renderer:resequence-items', itemsToResequence2)) + .invoke('renderer:resequence-items', itemsToResequence) .then(resolve) .catch((error) => reject(error)); } + + // ... rest of the existing code for different parent cases ... }); }; From c32a204f52940a33732fcad25a06929b0babae9d Mon Sep 17 00:00:00 2001 From: Pooja Belaramani Date: Mon, 24 Feb 2025 11:51:21 +0530 Subject: [PATCH 06/17] fix: request resequencing --- .../ReduxStore/slices/collections/actions.js | 84 +++++++++++++++---- packages/bruno-electron/src/ipc/collection.js | 6 +- 2 files changed, 74 insertions(+), 16 deletions(-) diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index b9abb0f72b..fc5528cca3 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -742,10 +742,29 @@ export const reorderAroundFolderItem = (collectionUid, draggedItemUid, targetIte const collectionCopy = cloneDeep(collection); const draggedItem = findItemInCollection(collectionCopy, draggedItemUid); const targetItem = findItemInCollection(collectionCopy, targetItemUid); + + if (!draggedItem) { + return reject(new Error('Dragged item not found')); + } + + if (!targetItem) { + return reject(new Error('Target item not found')); + } + const draggedItemParent = findParentItemInCollection(collectionCopy, draggedItemUid); const targetItemParent = findParentItemInCollection(collectionCopy, targetItemUid); const sameParent = draggedItemParent === targetItemParent; + // Helper function to prepare items for resequencing + const prepareItemsForResequence = (items = []) => { + return items.map((item, index) => ({ + uid: item.uid, + pathname: item.pathname, + type: item.type, + seq: index + 1 + })); + }; + // Update moveCollectionItem to handle position const moveCollectionItemWithPosition = (collection, draggedItem, targetItem, position) => { const items = draggedItemParent ? draggedItemParent.items : collection.items; @@ -764,23 +783,60 @@ export const reorderAroundFolderItem = (collectionUid, draggedItemUid, targetIte // Insert at new position items.splice(newIndex, 0, draggedItem); - // Update sequences - items.forEach((item, index) => { - item.seq = index + 1; - }); + return prepareItemsForResequence(items); }; - if (sameParent) { - moveCollectionItemWithPosition(collectionCopy, draggedItem, targetItem, dropPosition); - const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy); - - return ipcRenderer - .invoke('renderer:resequence-items', itemsToResequence) - .then(resolve) - .catch((error) => reject(error)); - } + try { + // Same parent case + if (sameParent) { + const itemsToResequence = moveCollectionItemWithPosition(collectionCopy, draggedItem, targetItem, dropPosition); + + return ipcRenderer + .invoke('renderer:resequence-items', itemsToResequence) + .then(resolve) + .catch((error) => reject(error)); + } - // ... rest of the existing code for different parent cases ... + // Target is at root level + if (!targetItemParent) { + const draggedItemPathname = draggedItem.pathname; + const itemsToResequence = moveCollectionItemWithPosition(collectionCopy, draggedItem, targetItem, dropPosition); + const rootItems = filter(collectionCopy.items, i => !isItemAFolder(i)); + const itemsToResequence2 = prepareItemsForResequence(rootItems); + + return ipcRenderer + .invoke( + isItemAFolder(draggedItem) ? 'renderer:move-folder-item' : '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)); + } + + // Different parent case + if (!sameParent) { + const draggedItemPathname = draggedItem.pathname; + const itemsToResequence = moveCollectionItemWithPosition(collectionCopy, draggedItem, targetItem, dropPosition); + const targetItems = filter(targetItemParent.items, i => !isItemAFolder(i)); + const itemsToResequence2 = prepareItemsForResequence(targetItems); + + return ipcRenderer + .invoke( + isItemAFolder(draggedItem) ? 'renderer:move-folder-item' : '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)); + } + } catch (error) { + reject(error); + } }); }; diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index 02db013a7d..a2512cce49 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -692,17 +692,19 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection } else { if (fs.existsSync(item.pathname)) { const bru = fs.readFileSync(item.pathname, 'utf8'); - const jsonData = bruToJson(bru); + const jsonData = await bruToJson(bru); if (jsonData.seq !== item.seq) { jsonData.seq = item.seq; - const content = jsonToBru(jsonData); + const content = await jsonToBru(jsonData); await writeFile(item.pathname, content); } } } } + return true; } catch (error) { + console.error('Error in resequence-items:', error); return Promise.reject(error); } }); From 2578f1b187b804f82ac4de9b4f0234a852287c91 Mon Sep 17 00:00:00 2001 From: Pooja Belaramani Date: Mon, 24 Feb 2025 12:02:35 +0530 Subject: [PATCH 07/17] rm: console --- .../Collection/CollectionItem/RunCollectionItem/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RunCollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RunCollectionItem/index.js index cfd236f8cb..2e3e5ee2f1 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RunCollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RunCollectionItem/index.js @@ -34,8 +34,6 @@ const RunCollectionItem = ({ collection, item, onClose }) => { const recursiveRunLength = getRequestsCount(flattenedItems); const isFolderLoading = areItemsLoading(item); - console.log(item); - console.log(isFolderLoading); return ( From 8de8fdc6e5936d63b05ec943746631f4ed58a76c Mon Sep 17 00:00:00 2001 From: Pooja Belaramani Date: Mon, 24 Feb 2025 12:05:31 +0530 Subject: [PATCH 08/17] add: collection indicator --- .../Collections/Collection/StyledWrapper.js | 20 ++++++++++++++---- .../Sidebar/Collections/Collection/index.js | 21 +++++++++++++------ 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js index f9f2eafe10..0378d9ad9d 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js @@ -64,12 +64,24 @@ const Wrapper = styled.div` } &.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}; + } + + &.drop-target-above { + border: none; + border-top: ${(props) => props.theme.dragAndDrop.borderStyle} ${(props) => props.theme.dragAndDrop.border}; + margin-top: -2px; + background: transparent; + transition: ${(props) => props.theme.dragAndDrop.transition}; + } + + &.drop-target-below { + border: none; + border-bottom: ${(props) => props.theme.dragAndDrop.borderStyle} ${(props) => props.theme.dragAndDrop.border}; + margin-bottom: -2px; + background: transparent; + transition: ${(props) => props.theme.dragAndDrop.transition}; } } diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js index dbcb62da63..301d1884f5 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js @@ -33,6 +33,7 @@ const Collection = ({ collection, searchText }) => { // const [collectionIsCollapsed, setCollectionIsCollapsed] = useState(collection.collapsed); const collectionIsCollapsed = Boolean(collection.collapsed); const [isDropTarget, setIsDropTarget] = useState(false); + const [dropPosition, setDropPosition] = useState(null); const tabs = useSelector((state) => state.tabs.tabs); const dispatch = useDispatch(); const isLoading = areItemsLoading(collection); @@ -144,27 +145,33 @@ const Collection = ({ collection, searchText }) => { const [{ isOver, canDrop }, drop] = useDrop({ accept: `COLLECTION_ITEM_${collection.uid}`, hover: (draggedItem, monitor) => { - // Only show drop target styling when hovering over the collection name area const hoverBoundingRect = collectionRef.current?.getBoundingClientRect(); const clientOffset = monitor.getClientOffset(); if (hoverBoundingRect && clientOffset) { const hoverY = clientOffset.y - hoverBoundingRect.top; - // Only set as drop target if hovering over the collection name area - setIsDropTarget(hoverY < 30); // Approximate height of collection name area + // Show drop target styling for the entire collection name area + setIsDropTarget(true); + // Set drop position based on hover location + if (hoverY < hoverBoundingRect.height / 2) { + setDropPosition('above'); + } else { + setDropPosition('below'); + } } }, drop: (draggedItem, monitor) => { - // Only handle drop if it's on the collection name area const hoverBoundingRect = collectionRef.current?.getBoundingClientRect(); const clientOffset = monitor.getClientOffset(); if (hoverBoundingRect && clientOffset) { const hoverY = clientOffset.y - hoverBoundingRect.top; - if (hoverY < 30) { + if (hoverY < hoverBoundingRect.height) { dispatch(moveItemToRootOfCollection(collection.uid, draggedItem.uid)); } } + setIsDropTarget(false); + setDropPosition(null); }, collect: (monitor) => ({ isOver: monitor.isOver(), @@ -182,7 +189,9 @@ const Collection = ({ collection, searchText }) => { }, [isOver]); const collectionRowClassName = classnames('flex py-1 collection-name items-center', { - 'drop-target': isDropTarget && isOver + 'drop-target': isDropTarget && isOver, + 'drop-target-above': isOver && dropPosition === 'above', + 'drop-target-below': isOver && dropPosition === 'below' }); if (searchText && searchText.length) { From c1eacc210bef7197fbf5c47b3b83209c75bf4218 Mon Sep 17 00:00:00 2001 From: Pooja Belaramani Date: Mon, 24 Feb 2025 15:05:12 +0530 Subject: [PATCH 09/17] fix --- .../Sidebar/Collections/Collection/CollectionItem/index.js | 2 +- packages/bruno-electron/src/ipc/collection.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js index cdfce67ba7..f4c259d9f3 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js @@ -44,7 +44,7 @@ const CollectionItem = ({ item, collection, searchText }) => { const [newFolderModalOpen, setNewFolderModalOpen] = useState(false); const [runCollectionModalOpen, setRunCollectionModalOpen] = useState(false); - const hasSearchText = searchText && searchText.trim().length; + const hasSearchText = searchText && searchText?.trim()?.length; const itemIsCollapsed = hasSearchText ? false : item.collapsed; const isFolder = isItemAFolder(item); diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index a2512cce49..02d5d7aaee 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -692,11 +692,11 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection } else { if (fs.existsSync(item.pathname)) { const bru = fs.readFileSync(item.pathname, 'utf8'); - const jsonData = await bruToJson(bru); + const jsonData = await bruToJsonViaWorker(bru); if (jsonData.seq !== item.seq) { jsonData.seq = item.seq; - const content = await jsonToBru(jsonData); + const content = await bruToJsonViaWorker(jsonData); await writeFile(item.pathname, content); } } From 974ed4e8f0488460e16ce8df48e5c59c7a3257b4 Mon Sep 17 00:00:00 2001 From: Pooja Belaramani Date: Mon, 24 Feb 2025 16:09:31 +0530 Subject: [PATCH 10/17] fix --- .../ReduxStore/slices/collections/actions.js | 43 ++----------------- 1 file changed, 4 insertions(+), 39 deletions(-) diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index fc5528cca3..e188ab373e 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -789,51 +789,16 @@ export const reorderAroundFolderItem = (collectionUid, draggedItemUid, targetIte try { // Same parent case if (sameParent) { - const itemsToResequence = moveCollectionItemWithPosition(collectionCopy, draggedItem, targetItem, dropPosition); - + moveCollectionItemWithPosition(collectionCopy, draggedItem, targetItem, dropPosition); + const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy); + return ipcRenderer .invoke('renderer:resequence-items', itemsToResequence) .then(resolve) .catch((error) => reject(error)); } - // Target is at root level - if (!targetItemParent) { - const draggedItemPathname = draggedItem.pathname; - const itemsToResequence = moveCollectionItemWithPosition(collectionCopy, draggedItem, targetItem, dropPosition); - const rootItems = filter(collectionCopy.items, i => !isItemAFolder(i)); - const itemsToResequence2 = prepareItemsForResequence(rootItems); - - return ipcRenderer - .invoke( - isItemAFolder(draggedItem) ? 'renderer:move-folder-item' : '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)); - } - - // Different parent case - if (!sameParent) { - const draggedItemPathname = draggedItem.pathname; - const itemsToResequence = moveCollectionItemWithPosition(collectionCopy, draggedItem, targetItem, dropPosition); - const targetItems = filter(targetItemParent.items, i => !isItemAFolder(i)); - const itemsToResequence2 = prepareItemsForResequence(targetItems); - - return ipcRenderer - .invoke( - isItemAFolder(draggedItem) ? 'renderer:move-folder-item' : '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)); - } + } catch (error) { reject(error); } From 3579661168493ad6f6813797042f187efb2dbf3b Mon Sep 17 00:00:00 2001 From: naman-bruno Date: Tue, 25 Feb 2025 14:39:03 +0530 Subject: [PATCH 11/17] Fixed function name --- packages/bruno-electron/src/ipc/collection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index 02d5d7aaee..17fd7bcf09 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -696,7 +696,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection if (jsonData.seq !== item.seq) { jsonData.seq = item.seq; - const content = await bruToJsonViaWorker(jsonData); + const content = await jsonToBruViaWorker(jsonData); await writeFile(item.pathname, content); } } From bbce987d21f4dc3fd2f4e4b8537ebe946e992cfd Mon Sep 17 00:00:00 2001 From: naman-bruno Date: Tue, 25 Feb 2025 14:39:28 +0530 Subject: [PATCH 12/17] added new action for itemsorder --- .../src/providers/ReduxStore/slices/collections/index.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index 65810412b9..8b7ef37c8a 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -56,6 +56,12 @@ export const collectionsSlice = createSlice({ state.collections.push(collection); } }, + updateCollectionItemsOrder: (state, action) => { + const { collectionUid, newOrder } = action.payload; + const collection = findCollectionByUid(state.collections, collectionUid); + if (!collection) return; + collection.items = newOrder.items; + }, updateCollectionMountStatus: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload.collectionUid); if (collection) { @@ -2005,11 +2011,12 @@ export const collectionsSlice = createSlice({ } } } - } + }, }); export const { createCollection, + updateCollectionItemsOrder, updateCollectionMountStatus, setCollectionSecurityConfig, brunoConfigUpdateEvent, From b9d800fdc71376cfbb0efd9abe46155698928da4 Mon Sep 17 00:00:00 2001 From: naman-bruno Date: Tue, 25 Feb 2025 14:40:56 +0530 Subject: [PATCH 13/17] added new action in reorder function --- .../ReduxStore/slices/collections/actions.js | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index e188ab373e..572404acb7 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -47,6 +47,7 @@ import { parsePathParams, parseQueryParams, splitOnFirst } from 'utils/url/index import { sendCollectionOauth2Request as _sendCollectionOauth2Request } from 'utils/network/index'; import slash from 'utils/common/slash'; import { getGlobalEnvironmentVariables, moveCollectionItemToFolder, findCollectionByPathname, findEnvironmentInCollectionByName } from 'utils/collections/index'; +import { updateCollectionItemsOrder } from './index'; export const renameCollection = (newName, collectionUid) => (dispatch, getState) => { const state = getState(); @@ -746,7 +747,6 @@ export const reorderAroundFolderItem = (collectionUid, draggedItemUid, targetIte if (!draggedItem) { return reject(new Error('Dragged item not found')); } - if (!targetItem) { return reject(new Error('Target item not found')); } @@ -755,7 +755,7 @@ export const reorderAroundFolderItem = (collectionUid, draggedItemUid, targetIte const targetItemParent = findParentItemInCollection(collectionCopy, targetItemUid); const sameParent = draggedItemParent === targetItemParent; - // Helper function to prepare items for resequencing + // Prepare items for resequence → (uid, pathname, type, seq) const prepareItemsForResequence = (items = []) => { return items.map((item, index) => ({ uid: item.uid, @@ -765,22 +765,23 @@ export const reorderAroundFolderItem = (collectionUid, draggedItemUid, targetIte })); }; - // Update moveCollectionItem to handle position const moveCollectionItemWithPosition = (collection, draggedItem, targetItem, position) => { - const items = draggedItemParent ? draggedItemParent.items : collection.items; + // items comes from either the parent folder or the root collection + let items = draggedItemParent ? draggedItemParent.items : collection.items; + + // ↓↓↓ NEW: Ensure items are sorted by seq so the indexes match the on-screen order + items = items.slice().sort((a, b) => (a.seq || 0) - (b.seq || 0)); + const targetIndex = items.findIndex(i => i.uid === targetItem.uid); const draggedIndex = items.findIndex(i => i.uid === draggedItem.uid); - // Remove dragged item items.splice(draggedIndex, 1); - // Calculate new index based on position let newIndex = position === 'above' ? targetIndex : targetIndex + 1; if (draggedIndex < targetIndex) { newIndex--; } - // Insert at new position items.splice(newIndex, 0, draggedItem); return prepareItemsForResequence(items); @@ -789,16 +790,26 @@ export const reorderAroundFolderItem = (collectionUid, draggedItemUid, targetIte try { // Same parent case if (sameParent) { - moveCollectionItemWithPosition(collectionCopy, draggedItem, targetItem, dropPosition); - const itemsToResequence = getItemsToResequence(draggedItemParent, collectionCopy); - + const itemsToResequence = moveCollectionItemWithPosition( + collectionCopy, + draggedItem, + targetItem, + dropPosition + ); + return ipcRenderer .invoke('renderer:resequence-items', itemsToResequence) - .then(resolve) - .catch((error) => reject(error)); + .then(() => { + // ★★★ Commit updated ordering to Redux after successful resequence + dispatch(updateCollectionItemsOrder({ collectionUid, newOrder: collectionCopy })); + resolve(); + }) + .catch((error) => { + reject(error); + }); } - + } catch (error) { reject(error); } From dc4c5ef5cb85dedebfed315aa29dde41adda5d78 Mon Sep 17 00:00:00 2001 From: naman-bruno Date: Tue, 25 Feb 2025 14:41:05 +0530 Subject: [PATCH 14/17] fix --- .../src/providers/ReduxStore/slices/collections/actions.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index 572404acb7..c6e245fde1 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -755,7 +755,7 @@ export const reorderAroundFolderItem = (collectionUid, draggedItemUid, targetIte const targetItemParent = findParentItemInCollection(collectionCopy, targetItemUid); const sameParent = draggedItemParent === targetItemParent; - // Prepare items for resequence → (uid, pathname, type, seq) + // Helper function to prepare items for resequencing const prepareItemsForResequence = (items = []) => { return items.map((item, index) => ({ uid: item.uid, @@ -769,7 +769,7 @@ export const reorderAroundFolderItem = (collectionUid, draggedItemUid, targetIte // items comes from either the parent folder or the root collection let items = draggedItemParent ? draggedItemParent.items : collection.items; - // ↓↓↓ NEW: Ensure items are sorted by seq so the indexes match the on-screen order + // Ensure items are sorted by seq so the indexes match the on-screen order items = items.slice().sort((a, b) => (a.seq || 0) - (b.seq || 0)); const targetIndex = items.findIndex(i => i.uid === targetItem.uid); @@ -800,7 +800,6 @@ export const reorderAroundFolderItem = (collectionUid, draggedItemUid, targetIte return ipcRenderer .invoke('renderer:resequence-items', itemsToResequence) .then(() => { - // ★★★ Commit updated ordering to Redux after successful resequence dispatch(updateCollectionItemsOrder({ collectionUid, newOrder: collectionCopy })); resolve(); }) @@ -809,7 +808,7 @@ export const reorderAroundFolderItem = (collectionUid, draggedItemUid, targetIte }); } - + } catch (error) { reject(error); } From 56ab846d809c40c2122c8b23de5d1dd7a80375a8 Mon Sep 17 00:00:00 2001 From: naman-bruno Date: Tue, 25 Feb 2025 14:55:39 +0530 Subject: [PATCH 15/17] rm: console.log --- .../src/components/Sidebar/Collections/Collection/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js index 301d1884f5..4600dc5009 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js @@ -212,8 +212,6 @@ const Collection = ({ collection, searchText }) => { const items = sortRequestItems(filter(collection.items, (i) => isItemARequest(i) || isItemAFolder(i))); - console.log('items', items); - return ( {showNewRequestModal && setShowNewRequestModal(false)} />} From 591c0db811244faa951d653f4abeba84dcde598d Mon Sep 17 00:00:00 2001 From: naman-bruno Date: Tue, 25 Feb 2025 17:10:36 +0530 Subject: [PATCH 16/17] removed old logic of hoverTime & actions --- .../Collection/CollectionItem/index.js | 43 +------------------ 1 file changed, 1 insertion(+), 42 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js index f4c259d9f3..a25dc0115c 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js @@ -48,8 +48,6 @@ const CollectionItem = ({ item, collection, searchText }) => { const itemIsCollapsed = hasSearchText ? false : item.collapsed; const isFolder = isItemAFolder(item); - const [hoverTime, setHoverTime] = useState(0); - const [action, setAction] = useState(null); const [dropPosition, setDropPosition] = useState(null); // 'above', 'below', or 'inside' const [{ isDragging }, drag] = useDrag({ @@ -81,18 +79,13 @@ const CollectionItem = ({ item, collection, searchText }) => { // Determine drop position based on mouse location if (clientY < upperThreshold) { setDropPosition('above'); - setAction('SHORT_HOVER'); } else if (clientY > lowerThreshold) { setDropPosition('below'); - setAction('SHORT_HOVER'); } else { if (isFolder) { setDropPosition('inside'); - setAction('LONG_HOVER'); } else { - // For non-folder items, use the middle point to determine above/below setDropPosition(clientY < hoverMiddleY ? 'above' : 'below'); - setAction('SHORT_HOVER'); } } } @@ -109,8 +102,6 @@ const CollectionItem = ({ item, collection, searchText }) => { } } setDropPosition(null); - setAction(null); - if (hoverTime > 0) setHoverTime(0); }, canDrop: (draggedItem) => draggedItem.uid !== item.uid, collect: (monitor) => ({ @@ -118,36 +109,6 @@ const CollectionItem = ({ item, collection, searchText }) => { }), }); - useEffect(() => { - if (!isOver) { - setDropPosition(null); - } - }, [isOver]); - - useEffect(() => { - let timer; - if (isOver && !canDrop) { - timer = setInterval(() => { - setHoverTime((prevTime) => prevTime + 100); - }, 100); - } else { - setAction(null); - setHoverTime(0); - if (timer) clearInterval(timer); - } - return () => clearInterval(timer); - }, [isOver, canDrop]); - - useEffect(() => { - if (hoverTime >= 0 && hoverTime < 750) { - setAction('SHORT_HOVER'); - } else if (hoverTime >= 750) { - setAction('LONG_HOVER'); - } else { - setAction(null); - } - }, [hoverTime]); - const dropdownTippyRef = useRef(); const MenuIcon = forwardRef((props, ref) => { return ( @@ -166,9 +127,7 @@ const CollectionItem = ({ item, collection, searchText }) => { 'item-hovered': isOver && canDrop, 'drop-target': isOver && dropPosition === 'inside', 'drop-target-above': isOver && dropPosition === 'above', - 'drop-target-below': isOver && dropPosition === 'below', - 'item-target': isOver && !canDrop && isFolder && action === 'LONG_HOVER', - 'item-seperator': isOver && !canDrop && (!isFolder || action === 'SHORT_HOVER') + 'drop-target-below': isOver && dropPosition === 'below' }); const handleRun = async () => { From abe4dcdbb86e280c0dee20c14104bea6e280d4e9 Mon Sep 17 00:00:00 2001 From: naman-bruno Date: Tue, 25 Feb 2025 17:22:31 +0530 Subject: [PATCH 17/17] fixed styles --- .../CollectionItem/StyledWrapper.js | 84 +++++++++++++------ 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js index a278e20f13..db19d2da32 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js @@ -22,6 +22,66 @@ const Wrapper = styled.div` height: 1.875rem; cursor: pointer; user-select: none; + position: relative; + + /* Common styles for drop indicators */ + &::before, + &::after { + content: ''; + position: absolute; + left: 0; + right: 0; + height: 2px; + background: ${(props) => props.theme.dragAndDrop.border}; + opacity: 0; + transition: ${(props) => props.theme.dragAndDrop.transition}; + pointer-events: none; + } + + &::before { + top: 0; + } + + &::after { + bottom: 0; + } + + /* Drop target styles */ + &.drop-target { + background-color: ${(props) => props.theme.dragAndDrop.hoverBg}; + + &::before, + &::after { + opacity: 0; + } + } + + &.drop-target-above { + &::before { + opacity: 1; + height: 2px; + } + } + + &.drop-target-below { + &::after { + opacity: 1; + height: 2px; + } + } + + /* Inside drop target style */ + &.drop-target { + &::before { + top: 0; + bottom: 0; + height: 100%; + opacity: 1; + background: ${(props) => props.theme.dragAndDrop.hoverBg}; + border: ${(props) => props.theme.dragAndDrop.borderStyle} ${(props) => props.theme.dragAndDrop.border}; + border-radius: 4px; + } + } .rotate-90 { transform: rotateZ(90deg); @@ -88,30 +148,6 @@ const Wrapper = styled.div` &.is-sidebar-dragging .collection-item-name { cursor: inherit; } - - .collection-item-name.drop-target { - border: ${(props) => props.theme.dragAndDrop.borderStyle} ${(props) => props.theme.dragAndDrop.border}; - border-radius: 6px; - background-color: ${(props) => props.theme.dragAndDrop.hoverBg}; - transition: ${(props) => props.theme.dragAndDrop.transition}; - box-shadow: 0 0 0 2px ${(props) => props.theme.dragAndDrop.hoverBg}; - } - - .collection-item-name.drop-target-above { - border: none; - border-top: ${(props) => props.theme.dragAndDrop.borderStyle} ${(props) => props.theme.dragAndDrop.border}; - margin-top: -2px; - background: transparent; - transition: ${(props) => props.theme.dragAndDrop.transition}; - } - - .collection-item-name.drop-target-below { - border: none; - border-bottom: ${(props) => props.theme.dragAndDrop.borderStyle} ${(props) => props.theme.dragAndDrop.border}; - margin-bottom: -2px; - background: transparent; - transition: ${(props) => props.theme.dragAndDrop.transition}; - } `; export default Wrapper;