Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added drag-and-drop functionality for Collections. #3755

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const CollectionItem = ({ item, collection, searchText }) => {
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
const isSidebarDragging = useSelector((state) => state.app.isDragging);
const dispatch = useDispatch();
const collectionItemRef = useRef(null);

const [renameItemModalOpen, setRenameItemModalOpen] = useState(false);
const [cloneItemModalOpen, setCloneItemModalOpen] = useState(false);
Expand All @@ -42,28 +43,31 @@ const CollectionItem = ({ item, collection, searchText }) => {
const [itemIsCollapsed, setItemisCollapsed] = useState(item.collapsed);

const [{ isDragging }, drag] = useDrag({
type: `COLLECTION_ITEM_${collection.uid}`,
type: `collection-item-${collection.uid}`,
item: item,
collect: (monitor) => ({
isDragging: monitor.isDragging()
})
}),
options: {
dropEffect: "move"
}
});

const [{ isOver }, drop] = useDrop({
accept: `COLLECTION_ITEM_${collection.uid}`,
accept: `collection-item-${collection.uid}`,
drop: (draggedItem) => {
if (draggedItem.uid !== item.uid) {
dispatch(moveItem(collection.uid, draggedItem.uid, item.uid));
}
dispatch(moveItem(collection.uid, draggedItem.uid, item.uid));
},
canDrop: (draggedItem) => {
return draggedItem.uid !== item.uid;
},
collect: (monitor) => ({
isOver: monitor.isOver()
})
isOver: monitor.isOver(),
}),
});

drag(drop(collectionItemRef));

useEffect(() => {
if (searchText && searchText.length) {
setItemisCollapsed(false);
Expand Down Expand Up @@ -253,7 +257,7 @@ const CollectionItem = ({ item, collection, searchText }) => {
{generateCodeItemModalOpen && (
<GenerateCodeItem collection={collection} item={item} onClose={() => setGenerateCodeItemModalOpen(false)} />
)}
<div className={itemRowClassName} ref={(node) => drag(drop(node))}>
<div className={itemRowClassName} ref={collectionItemRef}>
<div className="flex items-center h-full w-full">
{indents && indents.length
? indents.map((i) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ const Wrapper = styled.div`
transform: rotateZ(90deg);
}

&.item-hovered {
background: ${(props) => props.theme.sidebar.collection.item.hoverBg};
.menu-icon {
.dropdown {
div[aria-expanded='false'] {
visibility: visible;
}
}
}
}

.collection-actions {
.dropdown {
div[aria-expanded='true'] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import React, { useState, forwardRef, useRef, useEffect } from 'react';
import classnames from 'classnames';
import { uuid } from 'utils/common';
import filter from 'lodash/filter';
import { useDrop } from 'react-dnd';
import { useDrop, useDrag } from 'react-dnd';
import { IconChevronRight, IconDots } from '@tabler/icons';
import Dropdown from 'components/Dropdown';
import { collectionClicked } from 'providers/ReduxStore/slices/collections';
import { moveItemToRootOfCollection } from 'providers/ReduxStore/slices/collections/actions';
import { moveItemToRootOfCollection, updateAndPersistCollectionSequence } from 'providers/ReduxStore/slices/collections/actions';
import { useDispatch } from 'react-redux';
import { addTab } from 'providers/ReduxStore/slices/tabs';
import NewRequest from 'components/Sidebar/NewRequest';
Expand All @@ -31,6 +31,7 @@ const Collection = ({ collection, searchText }) => {
const [showRemoveCollectionModal, setShowRemoveCollectionModal] = useState(false);
const [collectionIsCollapsed, setCollectionIsCollapsed] = useState(collection.collapsed);
const dispatch = useDispatch();
const collectionRef = useRef(null);

const menuDropdownTippyRef = useRef();
const onMenuDropdownCreate = (ref) => (menuDropdownTippyRef.current = ref);
Expand Down Expand Up @@ -100,26 +101,51 @@ const Collection = ({ collection, searchText }) => {
);
};

const isCollectionItem = (itemType) => {
return itemType.startsWith('collection-item');
};

const [{ isDragging }, drag] = useDrag({
type: "collection",
item: collection,
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
options: {
dropEffect: "move"
}
});

const [{ isOver }, drop] = useDrop({
accept: `COLLECTION_ITEM_${collection.uid}`,
drop: (draggedItem) => {
dispatch(moveItemToRootOfCollection(collection.uid, draggedItem.uid));
accept: ["collection", `collection-item-${collection.uid}`],
drop: (draggedItem, monitor) => {
const itemType = monitor.getItemType();
if (isCollectionItem(itemType)) {
dispatch(moveItemToRootOfCollection(collection.uid, draggedItem.uid))
} else {
dispatch(updateAndPersistCollectionSequence({draggedItem, targetItem: collection}));
}
},
canDrop: (draggedItem) => {
// todo need to make sure that draggedItem belongs to the collection
return true;
return draggedItem.uid !== collection.uid;
},
collect: (monitor) => ({
isOver: monitor.isOver()
})
isOver: monitor.isOver(),
}),
});

drag(drop(collectionRef));

if (searchText && searchText.length) {
if (!doesCollectionHaveItemsMatchingSearchText(collection, searchText)) {
return null;
}
}

const collectionRowClassName = classnames('flex py-1 collection-name items-center', {
'item-hovered': isOver
});

// we need to sort request items by seq property
const sortRequestItems = (items = []) => {
return items.sort((a, b) => a.seq - b.seq);
Expand Down Expand Up @@ -149,7 +175,9 @@ const Collection = ({ collection, searchText }) => {
{showCloneCollectionModalOpen && (
<CloneCollection collection={collection} onClose={() => setShowCloneCollectionModalOpen(false)} />
)}
<div className="flex py-1 collection-name items-center" ref={drop}>
<div className={collectionRowClassName}
ref={collectionRef}
>
<div
className="flex flex-grow items-center overflow-hidden"
>
Expand Down Expand Up @@ -264,4 +292,4 @@ const Collection = ({ collection, searchText }) => {
);
};

export default Collection;
export default Collection;
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@ import {
IconSortDescendingLetters,
IconX
} from '@tabler/icons';
import Collection from '../Collections/Collection';
import Collection from './Collection';
import CreateCollection from '../CreateCollection';
import StyledWrapper from './StyledWrapper';
import CreateOrOpenCollection from './CreateOrOpenCollection';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { sortCollections } from 'providers/ReduxStore/slices/collections/actions';

// todo: move this to a separate folder
Expand Down Expand Up @@ -119,9 +117,7 @@ const Collections = () => {
{collections && collections.length
? collections.map((c) => {
return (
<DndProvider backend={HTML5Backend} key={c.uid}>
<Collection searchText={searchText} collection={c} key={c.uid} />
</DndProvider>
<Collection searchText={searchText} collection={c} key={c.uid} />
);
})
: null}
Expand Down
6 changes: 5 additions & 1 deletion packages/bruno-app/src/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './pages/index';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';

const rootElement = document.getElementById('root');

if (rootElement) {
const root = ReactDOM.createRoot(rootElement);
root.render(
<React.StrictMode>
<App />
<DndProvider backend={HTML5Backend}>
<App />
</DndProvider>
</React.StrictMode>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
removeCollection as _removeCollection,
selectEnvironment as _selectEnvironment,
sortCollections as _sortCollections,
resequenceCollection,
requestCancelled,
resetRunResults,
responseReceived,
Expand Down Expand Up @@ -1148,6 +1149,22 @@ export const importCollection = (collection, collectionLocation) => (dispatch, g
});
};

export const updateAndPersistCollectionSequence = ({ draggedItem, targetItem }) => (dispatch, getState) => {
dispatch(resequenceCollection({ draggedItem, targetItem }));

return new Promise((resolve, reject) => {
const { ipcRenderer } = window;
const state = getState();

const collectionPaths = state.collections.collections.map((collection) => collection.pathname);

ipcRenderer
.invoke('renderer:update-collection-paths', collectionPaths)
.then(resolve)
.catch(reject);
});
};

export const saveCollectionSecurityConfig = (collectionUid, securityConfig) => (dispatch, getState) => {
return new Promise((resolve, reject) => {
const { ipcRenderer } = window;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { uuid } from 'utils/common';
import { find, map, forOwn, concat, filter, each, cloneDeep, get, set } from 'lodash';
import { find, map, forOwn, concat, filter, each, cloneDeep, get, set, findIndex } from 'lodash';
import { createSlice } from '@reduxjs/toolkit';
import {
addDepth,
Expand Down Expand Up @@ -43,6 +43,7 @@ export const collectionsSlice = createSlice({
// for example, when a env is created, we want to auto select it the env modal
collection.importedAt = new Date().getTime();
collection.lastAction = null;
collection.seq = state.collections.length + 1;

collapseCollection(collection);
addDepth(collection.items);
Expand Down Expand Up @@ -88,6 +89,13 @@ export const collectionsSlice = createSlice({
break;
}
},
resequenceCollection: (state, action) => {
const { draggedItem, targetItem } = action.payload;
state.collections.sort((a, b) => a.seq - b.seq); // Sort collections by sequence
state.collections = state.collections.filter((i) => i.uid !== draggedItem.uid); // Remove dragged item
const targetItemIndex = state.collections.findIndex((i) => i.uid === targetItem.uid); // Find target item
state.collections.splice(targetItemIndex, 0, draggedItem); // Insert dragged item
},
updateLastAction: (state, action) => {
const { collectionUid, lastAction } = action.payload;
const collection = findCollectionByUid(state.collections, collectionUid);
Expand Down Expand Up @@ -1848,7 +1856,8 @@ export const {
runFolderEvent,
resetCollectionRunner,
updateRequestDocs,
updateFolderDocs
updateFolderDocs,
resequenceCollection
} = collectionsSlice.actions;

export default collectionsSlice.reducer;
11 changes: 1 addition & 10 deletions packages/bruno-app/src/utils/collections/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,4 @@
import get from 'lodash/get';
import each from 'lodash/each';
import find from 'lodash/find';
import findIndex from 'lodash/findIndex';
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 {cloneDeep, isEqual, sortBy, filter, map, isString, findIndex, find, each, get } from 'lodash';
import { uuid } from 'utils/common';
import path from 'path';
import slash from 'utils/common/slash';
Expand Down
4 changes: 4 additions & 0 deletions packages/bruno-electron/src/ipc/collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,10 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
}
});

ipcMain.handle('renderer:update-collection-paths', async (_, collectionPaths) => {
lastOpenedCollections.update(collectionPaths);
})

ipcMain.handle('renderer:import-collection', async (event, collection, collectionLocation) => {
try {
let collectionName = sanitizeCollectionName(collection.name);
Expand Down
18 changes: 10 additions & 8 deletions packages/bruno-electron/src/store/last-opened-collections.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,20 @@ class LastOpenedCollections {
}

add(collectionPath) {
const collections = this.store.get('lastOpenedCollections') || [];
const collections = this.getAll();

if (isDirectory(collectionPath)) {
if (!collections.includes(collectionPath)) {
collections.push(collectionPath);
this.store.set('lastOpenedCollections', collections);
}
if (isDirectory(collectionPath) && !collections.includes(collectionPath)) {
collections.push(collectionPath);
this.store.set('lastOpenedCollections', collections);
}
}

update(collectionPaths) {
this.store.set('lastOpenedCollections', collectionPaths);
}

remove(collectionPath) {
let collections = this.store.get('lastOpenedCollections') || [];
let collections = this.getAll();

if (collections.includes(collectionPath)) {
collections = _.filter(collections, (c) => c !== collectionPath);
Expand All @@ -36,7 +38,7 @@ class LastOpenedCollections {
}

removeAll() {
return this.store.set('lastOpenedCollections', []);
this.store.set('lastOpenedCollections', []);
}
}

Expand Down
Loading