diff --git a/renderer/components/Modal/index.js b/renderer/components/Modal/index.js index 6bb444df..783e035a 100644 --- a/renderer/components/Modal/index.js +++ b/renderer/components/Modal/index.js @@ -38,7 +38,7 @@ const ModalFooter = ({confirmText, cancelText, handleSubmit, handleCancel, confi ); } -const ConfirmModal = ({ +const Modal = ({ size, title, confirmText, @@ -91,4 +91,4 @@ const ConfirmModal = ({ ); }; -export default ConfirmModal; +export default Modal; diff --git a/renderer/components/RequestTabPanel/index.js b/renderer/components/RequestTabPanel/index.js index 9242ed75..0e8c1b1c 100644 --- a/renderer/components/RequestTabPanel/index.js +++ b/renderer/components/RequestTabPanel/index.js @@ -83,6 +83,13 @@ const RequestTabPanel = () => { } const item = findItemInCollection(collection, activeTabUid); + if(!item || !item.uid) { + return ( + + Request not found! + + ); + } const onUrlChange = (value) => { dispatch(requestChanged({ diff --git a/renderer/components/RequestTabs/index.js b/renderer/components/RequestTabs/index.js index f031e5c4..14bd63d7 100644 --- a/renderer/components/RequestTabs/index.js +++ b/renderer/components/RequestTabs/index.js @@ -47,7 +47,9 @@ const RequestTabs = () => { const handleCloseClick = (event, tab) => { event.stopPropagation(); event.preventDefault(); - dispatch(closeTab(tab.uid)) + dispatch(closeTab({ + tabUid: tab.uid + })) }; const createNewTab = () => { @@ -71,14 +73,22 @@ const RequestTabs = () => { const collectionRequestTabs = filter(tabs, (t) => t.collectionUid === activeTab.collectionUid); const item = findItemInCollection(activeCollection, activeTab.uid); + if(!item || !item.uid) { + return ( + + Request not found! + + ); + } + const getRequestName = (tab) => { const item = findItemInCollection(activeCollection, tab.uid); - return item.name; + return item ? item.name : ''; } const getRequestMethod = (tab) => { const item = findItemInCollection(activeCollection, tab.uid); - return item.request.name; + return item ? item.request.method : ''; } return ( diff --git a/renderer/components/Sidebar/Collections/Collection/CollectionItem/DeleteCollectionItem/StyledWrapper.js b/renderer/components/Sidebar/Collections/Collection/CollectionItem/DeleteCollectionItem/StyledWrapper.js new file mode 100644 index 00000000..48b87421 --- /dev/null +++ b/renderer/components/Sidebar/Collections/Collection/CollectionItem/DeleteCollectionItem/StyledWrapper.js @@ -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; diff --git a/renderer/components/Sidebar/Collections/Collection/CollectionItem/DeleteCollectionItem/index.js b/renderer/components/Sidebar/Collections/Collection/CollectionItem/DeleteCollectionItem/index.js new file mode 100644 index 00000000..aafaeaae --- /dev/null +++ b/renderer/components/Sidebar/Collections/Collection/CollectionItem/DeleteCollectionItem/index.js @@ -0,0 +1,35 @@ +import React from 'react'; +import Modal from 'components/Modal'; +import { isItemAFolder } from 'utils/tabs'; +import { useDispatch } from 'react-redux'; +import { closeTab } from 'providers/ReduxStore/slices/tabs'; +import { deleteItem } from 'providers/ReduxStore/slices/collections'; +import StyledWrapper from './StyledWrapper'; + +const DeleteCollectionItem = ({onClose, item, collection}) => { + const dispatch = useDispatch(); + const isFolder = isItemAFolder(item); + const onConfirm = () =>{ + dispatch(closeTab({ + tabUid: item.uid + })); + dispatch(deleteItem(item.uid, collection.uid)); + onClose(); + }; + + return ( + + + Are you sure you want to delete {item.name} ? + + + ); +}; + +export default DeleteCollectionItem; diff --git a/renderer/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js b/renderer/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js index 2794f5cc..00168d80 100644 --- a/renderer/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js +++ b/renderer/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js @@ -47,6 +47,14 @@ const Wrapper = styled.div` position: relative; top: -0.625rem; } + + div.dropdown-item.delete-item { + color: var(--color-text-danger); + &:hover { + background-color: var(--color-background-danger); + color: white; + } + } } `; diff --git a/renderer/components/Sidebar/Collections/Collection/CollectionItem/index.js b/renderer/components/Sidebar/Collections/Collection/CollectionItem/index.js index ab7726f3..ea8c73c8 100644 --- a/renderer/components/Sidebar/Collections/Collection/CollectionItem/index.js +++ b/renderer/components/Sidebar/Collections/Collection/CollectionItem/index.js @@ -1,4 +1,4 @@ -import React, { useRef, forwardRef } from 'react'; +import React, { useState, useRef, forwardRef } from 'react'; import range from 'lodash/range'; import get from 'lodash/get'; import { IconChevronRight, IconDots } from '@tabler/icons'; @@ -8,6 +8,7 @@ import { addTab, focusTab } from 'providers/ReduxStore/slices/tabs'; import { isItemARequest, isItemAFolder, itemIsOpenedInTabs } from 'utils/tabs'; import Dropdown from 'components/Dropdown'; import RequestMethod from './RequestMethod'; +import DeleteCollectionItem from './DeleteCollectionItem'; import StyledWrapper from './StyledWrapper'; @@ -16,6 +17,8 @@ const CollectionItem = ({item, collection}) => { const activeTabUid = useSelector((state) => state.tabs.activeTabUid); const dispatch = useDispatch(); + const [deleteItemModalOpen, setDeleteItemModalOpen] = useState(false); + const dropdownTippyRef = useRef(); const MenuIcon = forwardRef((props, ref) => { return ( @@ -62,6 +65,7 @@ const CollectionItem = ({item, collection}) => { return ( + {deleteItemModalOpen && setDeleteItemModalOpen(false)}/>}
{ }}> Rename
-
{ +
{ dropdownTippyRef.current.hide(); + setDeleteItemModalOpen(true); }}> Delete
diff --git a/renderer/components/Sidebar/CreateCollection/index.js b/renderer/components/Sidebar/CreateCollection/index.js index f26f2a1b..fd29caaa 100644 --- a/renderer/components/Sidebar/CreateCollection/index.js +++ b/renderer/components/Sidebar/CreateCollection/index.js @@ -1,7 +1,7 @@ import React, { useRef, useEffect } from 'react'; import { useFormik } from 'formik'; import * as Yup from 'yup'; -import Modal from '../../Modal'; +import Modal from 'components/Modal'; const CreateCollection = ({handleConfirm, handleCancel}) => { const inputRef = useRef(); diff --git a/renderer/providers/ReduxStore/slices/collections.js b/renderer/providers/ReduxStore/slices/collections.js index 3903d95d..444db95b 100644 --- a/renderer/providers/ReduxStore/slices/collections.js +++ b/renderer/providers/ReduxStore/slices/collections.js @@ -4,7 +4,14 @@ import cloneDeep from 'lodash/cloneDeep'; import { createSlice } from '@reduxjs/toolkit' import { getCollectionsFromIdb, saveCollectionToIdb } from 'utils/idb'; import { sendNetworkRequest } from 'utils/network'; -import { findCollectionByUid, findItemInCollection, cloneItem, transformCollectionToSaveToIdb, addDepth } from 'utils/collections'; +import { + findCollectionByUid, + findItemInCollection, + cloneItem, + transformCollectionToSaveToIdb, + addDepth, + deleteItemInCollection +} from 'utils/collections'; // todo: errors should be tracked in each slice and displayed as toasts @@ -78,6 +85,13 @@ export const collectionsSlice = createSlice({ addDepth(collection.items); } }, + _deleteItem: (state, action) => { + const collection = findCollectionByUid(state.collections, action.payload.collectionUid); + + if(collection) { + deleteItemInCollection(action.payload.itemUid, collection); + } + }, collectionClicked: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload); @@ -110,6 +124,7 @@ export const { _saveRequest, _newFolder, _newRequest, + _deleteItem, collectionClicked, requestUrlChanged, } = collectionsSlice.actions; @@ -233,4 +248,24 @@ export const newHttpRequest = (requestName, collectionUid) => (dispatch, getStat }); }; +export const deleteItem = (itemUid, collectionUid) => (dispatch, getState) => { + const state = getState(); + const collection = findCollectionByUid(state.collections.collections, collectionUid); + + if(collection) { + const collectionCopy = cloneDeep(collection); + deleteItemInCollection(itemUid, collectionCopy); + const collectionToSave = transformCollectionToSaveToIdb(collectionCopy); + + saveCollectionToIdb(window.__idb, collectionToSave) + .then(() => { + dispatch(_deleteItem({ + itemUid: itemUid, + collectionUid: collectionUid + })); + }) + .catch((err) => console.log(err)); + } +}; + export default collectionsSlice.reducer; diff --git a/renderer/providers/ReduxStore/slices/tabs.js b/renderer/providers/ReduxStore/slices/tabs.js index 8d89f3e5..7f10ab31 100644 --- a/renderer/providers/ReduxStore/slices/tabs.js +++ b/renderer/providers/ReduxStore/slices/tabs.js @@ -26,7 +26,7 @@ export const tabsSlice = createSlice({ state.activeTabUid = action.payload.uid; }, closeTab: (state, action) => { - state.tabs = filter(state.tabs, (t) => t.uid !== action.payload); + state.tabs = filter(state.tabs, (t) => t.uid !== action.payload.tabUid); if(state.tabs && state.tabs.length) { // todo: closing tab needs to focus on the right adjacent tab diff --git a/renderer/styles/globals.css b/renderer/styles/globals.css index c7cc6bd8..c9ff50f9 100644 --- a/renderer/styles/globals.css +++ b/renderer/styles/globals.css @@ -11,6 +11,8 @@ --color-codemirror-border: #efefef; --color-codemirror-background: rgb(243, 243, 243); --color-text-link: #1663bb; + --color-text-danger: rgb(185, 28, 28); + --color-background-danger: #dc3545; --color-method-get: rgb(5, 150, 105); --color-method-post: #8e44ad; --color-method-put: #8e44ad; diff --git a/renderer/utils/collections/index.js b/renderer/utils/collections/index.js index b76795f8..5cd24a1c 100644 --- a/renderer/utils/collections/index.js +++ b/renderer/utils/collections/index.js @@ -1,5 +1,6 @@ import each from 'lodash/each'; import find from 'lodash/find'; +import filter from 'lodash/filter'; import cloneDeep from 'lodash/cloneDeep'; export const addDepth = (items = []) => { @@ -104,4 +105,17 @@ export const transformCollectionToSaveToIdb = (collection) => { copyItems(collection.items, collectionToSave.items); return collectionToSave; -}; \ No newline at end of file +}; + +// todo: optimize this +export const deleteItemInCollection = (itemUid, collection) => { + collection.items = filter(collection.items, (i) => i.uid !== itemUid); + + let flattenedItems = flattenItems(collection.items); + + each(flattenedItems, (i) => { + if(i.items && i.items.length) { + i.items = filter(i.items, (i) => i.uid !== itemUid); + } + }); +};