feat: delete collections

This commit is contained in:
Anoop M D 2022-10-11 03:20:50 +05:30
parent adc6be031d
commit 02ff85cc57
10 changed files with 157 additions and 47 deletions

View File

@ -0,0 +1,15 @@
import styled from 'styled-components';
const Wrapper = styled.div`
button.submit {
color: white;
background-color: var(--color-background-danger) !important;
border: inherit !important;
&:hover {
border: inherit !important;
}
}
`;
export default Wrapper;

View File

@ -0,0 +1,33 @@
import React from 'react';
import toast from 'react-hot-toast';
import Modal from 'components/Modal';
import { useDispatch } from 'react-redux';
import { deleteCollection } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
const DeleteCollection = ({onClose, collection}) => {
const dispatch = useDispatch();
const onConfirm = () =>{
dispatch(deleteCollection(collection.uid))
.then(() => {
toast.success("Collection deleted");
})
.catch(() => toast.error("An error occured while deleting the collection"));
};
return (
<StyledWrapper>
<Modal
size="sm"
title="Delete Collection"
confirmText="Delete"
handleConfirm={onConfirm}
handleCancel={onClose}
>
Are you sure you want to delete the collection <span className="font-semibold">{collection.name}</span> ?
</Modal>
</StyledWrapper>
);
};
export default DeleteCollection;

View File

@ -44,6 +44,14 @@ const Wrapper = styled.div`
top: -0.625rem; top: -0.625rem;
font-weight: 400; font-weight: 400;
} }
div.dropdown-item.delete-collection {
color: var(--color-text-danger);
&:hover {
background-color: var(--color-background-danger);
color: white;
}
}
} }
`; `;

View File

@ -3,7 +3,7 @@ import classnames from 'classnames';
import filter from 'lodash/filter'; import filter from 'lodash/filter';
import { IconChevronRight, IconDots } from '@tabler/icons'; import { IconChevronRight, IconDots } from '@tabler/icons';
import Dropdown from 'components/Dropdown'; import Dropdown from 'components/Dropdown';
import { collectionClicked, removeCollection } from 'providers/ReduxStore/slices/collections'; import { collectionClicked } from 'providers/ReduxStore/slices/collections';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import NewRequest from 'components/Sidebar/NewRequest'; import NewRequest from 'components/Sidebar/NewRequest';
import NewFolder from 'components/Sidebar/NewFolder'; import NewFolder from 'components/Sidebar/NewFolder';
@ -12,23 +12,17 @@ import { doesCollectionHaveItemsMatchingSearchText } from 'utils/collections/sea
import { isItemAFolder, isItemARequest } from 'utils/collections'; import { isItemAFolder, isItemARequest } from 'utils/collections';
import RenameCollection from './RenameCollection'; import RenameCollection from './RenameCollection';
import DeleteCollection from './DeleteCollection';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const Collection = ({collection, searchText}) => { const Collection = ({collection, searchText}) => {
const [showNewFolderModal, setShowNewFolderModal] = useState(false); const [showNewFolderModal, setShowNewFolderModal] = useState(false);
const [showNewRequestModal, setShowNewRequestModal] = useState(false); const [showNewRequestModal, setShowNewRequestModal] = useState(false);
const [showRenameCollectionModal, setShowRenameCollectionModal] = useState(false); const [showRenameCollectionModal, setShowRenameCollectionModal] = useState(false);
const [showDeleteCollectionModal, setShowDeleteCollectionModal] = useState(false);
const [collectionIsCollapsed, setCollectionIsCollapsed] = useState(collection.collapsed); const [collectionIsCollapsed, setCollectionIsCollapsed] = useState(collection.collapsed);
const dispatch = useDispatch(); const dispatch = useDispatch();
useEffect(() => {
if (searchText && searchText.length) {
setCollectionIsCollapsed(false);
} else {
setCollectionIsCollapsed(collection.collapsed);
}
}, [searchText, collection]);
const menuDropdownTippyRef = useRef(); const menuDropdownTippyRef = useRef();
const onMenuDropdownCreate = (ref) => menuDropdownTippyRef.current = ref; const onMenuDropdownCreate = (ref) => menuDropdownTippyRef.current = ref;
const MenuIcon = forwardRef((props, ref) => { const MenuIcon = forwardRef((props, ref) => {
@ -39,6 +33,14 @@ const Collection = ({collection, searchText}) => {
); );
}); });
useEffect(() => {
if (searchText && searchText.length) {
setCollectionIsCollapsed(false);
} else {
setCollectionIsCollapsed(collection.collapsed);
}
}, [searchText, collection]);
const iconClassName = classnames({ const iconClassName = classnames({
'rotate-90': !collectionIsCollapsed 'rotate-90': !collectionIsCollapsed
}); });
@ -61,6 +63,7 @@ const Collection = ({collection, searchText}) => {
{showNewRequestModal && <NewRequest collection={collection} onClose={() => setShowNewRequestModal(false)}/>} {showNewRequestModal && <NewRequest collection={collection} onClose={() => setShowNewRequestModal(false)}/>}
{showNewFolderModal && <NewFolder collection={collection} onClose={() => setShowNewFolderModal(false)}/>} {showNewFolderModal && <NewFolder collection={collection} onClose={() => setShowNewFolderModal(false)}/>}
{showRenameCollectionModal && <RenameCollection collection={collection} onClose={() => setShowRenameCollectionModal(false)}/>} {showRenameCollectionModal && <RenameCollection collection={collection} onClose={() => setShowRenameCollectionModal(false)}/>}
{showDeleteCollectionModal && <DeleteCollection collection={collection} onClose={() => setShowDeleteCollectionModal(false)}/>}
<div className="flex py-1 collection-name items-center"> <div className="flex py-1 collection-name items-center">
<div className="flex flex-grow items-center" onClick={handleClick}> <div className="flex flex-grow items-center" onClick={handleClick}>
<IconChevronRight size={16} strokeWidth={2} className={iconClassName} style={{width:16, color: 'rgb(160 160 160)'}}/> <IconChevronRight size={16} strokeWidth={2} className={iconClassName} style={{width:16, color: 'rgb(160 160 160)'}}/>
@ -85,17 +88,16 @@ const Collection = ({collection, searchText}) => {
New Folder New Folder
</div> </div>
<div className="dropdown-item" onClick={(e) => { <div className="dropdown-item" onClick={(e) => {
dispatch(removeCollection(collection.uid));
menuDropdownTippyRef.current.hide(); menuDropdownTippyRef.current.hide();
setShowRenameCollectionModal(true); setShowRenameCollectionModal(true);
}}> }}>
Rename Rename
</div> </div>
<div className="dropdown-item" onClick={(e) => { <div className="dropdown-item delete-collection" onClick={(e) => {
dispatch(removeCollection(collection.uid));
menuDropdownTippyRef.current.hide(); menuDropdownTippyRef.current.hide();
setShowDeleteCollectionModal(true);
}}> }}>
Remove Delete
</div> </div>
</Dropdown> </Dropdown>
</div> </div>

View File

@ -1,9 +1,9 @@
import React, { useState, forwardRef, useRef } from 'react'; import React, { useState, forwardRef, useRef } from 'react';
import Toast from 'components/Toast'; import toast from 'react-hot-toast';
import Dropdown from 'components/Dropdown'; import Dropdown from 'components/Dropdown';
import Bruno from 'components/Bruno'; import Bruno from 'components/Bruno';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { createCollection } from 'providers/ReduxStore/slices/collections'; import { createCollection } from 'providers/ReduxStore/slices/collections/actions';
import { showHomePage } from 'providers/ReduxStore/slices/app'; import { showHomePage } from 'providers/ReduxStore/slices/app';
import { IconDots } from '@tabler/icons'; import { IconDots } from '@tabler/icons';
import CreateCollection from '../CreateCollection'; import CreateCollection from '../CreateCollection';
@ -11,7 +11,6 @@ import StyledWrapper from './StyledWrapper';
const TitleBar = () => { const TitleBar = () => {
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
const [showToast, setShowToast] = useState({show: false});
const dispatch = useDispatch(); const dispatch = useDispatch();
const menuDropdownTippyRef = useRef(); const menuDropdownTippyRef = useRef();
@ -25,17 +24,19 @@ const TitleBar = () => {
}); });
const handleCancel = () => setModalOpen(false); const handleCancel = () => setModalOpen(false);
const handleCloseToast = () => setShowToast({show: false});
const handleTitleClick = () => dispatch(showHomePage()); const handleTitleClick = () => dispatch(showHomePage());
const handleConfirm = (values) => { const handleConfirm = (values) => {
setModalOpen(false); setModalOpen(false);
dispatch(createCollection(values.collectionName, values.collectionLocation)); dispatch(createCollection(values.collectionName))
.then(() => {
toast.success("Collection created");
})
.catch(() => toast.error("An error occured while creating the collection"));
}; };
return ( return (
<StyledWrapper className="px-2 py-2"> <StyledWrapper className="px-2 py-2">
{showToast.show && <Toast text={showToast.text} type={showToast.type} duration={showToast.duration} handleClose={handleCloseToast}/>}
{modalOpen ? ( {modalOpen ? (
<CreateCollection <CreateCollection
handleCancel={handleCancel} handleCancel={handleCancel}

View File

@ -10,7 +10,7 @@ import {
IconDeviceDesktop IconDeviceDesktop
} from '@tabler/icons'; } from '@tabler/icons';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { createCollection } from 'providers/ReduxStore/slices/collections'; import { createCollection } from 'providers/ReduxStore/slices/collections/actions';
import Bruno from 'components/Bruno'; import Bruno from 'components/Bruno';
import CreateCollection from 'components/Sidebar/CreateCollection'; import CreateCollection from 'components/Sidebar/CreateCollection';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
@ -22,7 +22,11 @@ const Welcome = () => {
const handleCancel = () => setModalOpen(false); const handleCancel = () => setModalOpen(false);
const handleConfirm = (values) => { const handleConfirm = (values) => {
setModalOpen(false); setModalOpen(false);
dispatch(createCollection(values.collectionName, values.collectionLocation)); dispatch(createCollection(values.collectionName))
.then(() => {
toast.success("Collection created");
})
.catch(() => toast.error("An error occured while creating the collection"));
}; };
return ( return (

View File

@ -1,18 +1,21 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { IconEdit, IconTrash } from "@tabler/icons"; import { IconEdit, IconTrash } from "@tabler/icons";
import RenameCollection from 'components/Sidebar/Collections/Collection/RenameCollection'; import RenameCollection from 'components/Sidebar/Collections/Collection/RenameCollection';
import DeleteCollection from 'components/Sidebar/Collections/Collection/DeleteCollection';
export default function CollectionItem({collection}) { export default function CollectionItem({collection}) {
const [showRenameCollectionModal, setShowRenameCollectionModal] = useState(false); const [showRenameCollectionModal, setShowRenameCollectionModal] = useState(false);
const [showDeleteCollectionModal, setShowDeleteCollectionModal] = useState(false);
return ( return (
<> <>
{showRenameCollectionModal && <RenameCollection collection={collection} onClose={() => setShowRenameCollectionModal(false)}/>} {showRenameCollectionModal && <RenameCollection collection={collection} onClose={() => setShowRenameCollectionModal(false)}/>}
{showDeleteCollectionModal && <DeleteCollection collection={collection} onClose={() => setShowDeleteCollectionModal(false)}/>}
<div className="flex justify-between items-baseline mb-2 collection-list-item"> <div className="flex justify-between items-baseline mb-2 collection-list-item">
<li style={{listStyle: 'none'}} className="collection-name">{collection.name}</li> <li style={{listStyle: 'none'}} className="collection-name">{collection.name}</li>
<div className="flex gap-x-4" > <div className="flex gap-x-4" >
<IconEdit className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setShowRenameCollectionModal(true)}/> <IconEdit className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setShowRenameCollectionModal(true)}/>
<IconTrash className="cursor-pointer" size={20} strokeWidth={1.5}/> <IconTrash className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setShowDeleteCollectionModal(true)}/>
</div> </div>
</div> </div>
</> </>

View File

@ -1,15 +1,36 @@
import { uuid } from 'utils/common';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import { import {
findCollectionByUid, findCollectionByUid,
findItemInCollection, recursivelyGetAllItemUids,
transformCollectionToSaveToIdb transformCollectionToSaveToIdb
} from 'utils/collections'; } from 'utils/collections';
import { saveCollectionToIdb } from 'utils/idb'; import { saveCollectionToIdb, deleteCollectionInIdb } from 'utils/idb';
import { import {
_renameCollection createCollection as _createCollection,
renameCollection as _renameCollection,
deleteCollection as _deleteCollection,
} from './index'; } from './index';
import { closeTabs } from 'providers/ReduxStore/slices/tabs';
export const createCollection = (collectionName) => (dispatch) => {
const newCollection = {
uid: uuid(),
name: collectionName,
items: [],
environments: [],
};
return new Promise((resolve, reject) => {
saveCollectionToIdb(window.__idb, newCollection)
.then(() => dispatch(_createCollection(newCollection)))
.then(resolve)
.catch(reject);
});
};
export const renameCollection = (newName, collectionUid) => (dispatch, getState) => { export const renameCollection = (newName, collectionUid) => (dispatch, getState) => {
const state = getState(); const state = getState();
const collection = findCollectionByUid(state.collections.collections, collectionUid); const collection = findCollectionByUid(state.collections.collections, collectionUid);
@ -30,4 +51,27 @@ export const renameCollection = (newName, collectionUid) => (dispatch, getState)
}) })
.catch((err) => console.log(err)); .catch((err) => console.log(err));
} }
};
export const deleteCollection = (collectionUid) => (dispatch, getState) => {
const state = getState();
const collection = findCollectionByUid(state.collections.collections, collectionUid);
return new Promise((resolve, reject) => {
if(!collection) {
return reject('collection not found');
}
deleteCollectionInIdb(window.__idb, collection.uid)
.then(() => {
dispatch(closeTabs({
tabUids: recursivelyGetAllItemUids(collection.items)
}));
dispatch(_deleteCollection({
collectionUid: collectionUid
}));
})
.then(resolve)
.catch(reject);
});
}; };

View File

@ -1,4 +1,3 @@
import path from 'path';
import { uuid } from 'utils/common'; import { uuid } from 'utils/common';
import find from 'lodash/find'; import find from 'lodash/find';
import concat from 'lodash/concat'; import concat from 'lodash/concat';
@ -37,16 +36,19 @@ export const collectionsSlice = createSlice({
each(action.payload.collections, (c) => addDepth(c.items)); each(action.payload.collections, (c) => addDepth(c.items));
state.collections = action.payload.collections; state.collections = action.payload.collections;
}, },
_createCollection: (state, action) => { createCollection: (state, action) => {
state.collections.push(action.payload); state.collections.push(action.payload);
}, },
_renameCollection: (state, action) => { renameCollection: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid); const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
if(collection) { if(collection) {
collection.name = action.payload.newName; collection.name = action.payload.newName;
} }
}, },
deleteCollection: (state, action) => {
state.collections = filter(state.collections, c => c.uid !== action.payload.collectionUid);
},
_newItem: (state, action) => { _newItem: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid); const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
@ -525,8 +527,9 @@ export const collectionsSlice = createSlice({
}); });
export const { export const {
_createCollection, createCollection,
_renameCollection, renameCollection,
deleteCollection,
_loadCollections, _loadCollections,
_newItem, _newItem,
_deleteItem, _deleteItem,
@ -564,19 +567,6 @@ export const loadCollectionsFromIdb = () => (dispatch) => {
.catch((err) => console.log(err)); .catch((err) => console.log(err));
}; };
export const createCollection = (collectionName) => (dispatch) => {
const newCollection = {
uid: uuid(),
name: collectionName,
items: [],
environments: [],
};
saveCollectionToIdb(window.__idb, newCollection)
.then(() => dispatch(_createCollection(newCollection)))
.catch((err) => console.log(err));
};
export const sendRequest = (item, collectionUid) => (dispatch) => { export const sendRequest = (item, collectionUid) => (dispatch) => {
dispatch(_requestSent({ dispatch(_requestSent({
itemUid: item.uid, itemUid: item.uid,
@ -803,8 +793,4 @@ export const cloneItem = (newName, itemUid, collectionUid) => (dispatch, getStat
} }
}; };
export const removeCollection = (collectionPath) => () => {
console.log('removeCollection');
};
export default collectionsSlice.reducer; export default collectionsSlice.reducer;

View File

@ -21,6 +21,20 @@ export const saveCollectionToIdb = (connection, collection) => {
}); });
}; };
export const deleteCollectionInIdb = (connection, collectionUid) => {
return new Promise((resolve, reject) => {
connection
.then((db) => {
let tx = db.transaction(`collection`, 'readwrite');
tx.objectStore('collection').delete(collectionUid);
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
})
.catch((err) => reject(err));
});
};
export const getCollectionsFromIdb = (connection) => { export const getCollectionsFromIdb = (connection) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
connection connection