diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveLocalCollection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveLocalCollection/index.js new file mode 100644 index 00000000..bde7335a --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveLocalCollection/index.js @@ -0,0 +1,32 @@ +import React from 'react'; +import toast from 'react-hot-toast'; +import Modal from 'components/Modal'; +import { useDispatch } from 'react-redux'; +import { removeLocalCollection } from 'providers/ReduxStore/slices/collections/actions'; + +const RemoveLocalCollection = ({onClose, collection}) => { + const dispatch = useDispatch(); + + const onConfirm = () =>{ + dispatch(removeLocalCollection(collection.uid)) + .then(() => { + toast.success("Collection removed"); + onClose(); + }) + .catch(() => toast.error("An error occured while removing the collection")); + }; + + return ( + + Are you sure you want to remove this collection? + + ); +}; + +export default RemoveLocalCollection; 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 3510dbc1..b512f6e2 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js @@ -10,6 +10,7 @@ import NewRequest from 'components/Sidebar/NewRequest'; import NewFolder from 'components/Sidebar/NewFolder'; import CollectionItem from './CollectionItem'; import RemoveCollectionFromWorkspace from './RemoveCollectionFromWorkspace'; +import RemoveLocalCollection from './RemoveLocalCollection'; import { doesCollectionHaveItemsMatchingSearchText } from 'utils/collections/search'; import { isItemAFolder, isItemARequest, transformCollectionToSaveToIdb, isLocalCollection } from 'utils/collections'; import exportCollection from 'utils/collections/export'; @@ -23,6 +24,7 @@ const Collection = ({collection, searchText}) => { const [showNewRequestModal, setShowNewRequestModal] = useState(false); const [showRenameCollectionModal, setShowRenameCollectionModal] = useState(false); const [showRemoveCollectionFromWSModal, setShowRemoveCollectionFromWSModal] = useState(false); + const [showRemoveLocalCollectionModal, setShowRemoveLocalCollectionModal] = useState(false); const [showDeleteCollectionModal, setShowDeleteCollectionModal] = useState(false); const [collectionIsCollapsed, setCollectionIsCollapsed] = useState(collection.collapsed); const dispatch = useDispatch(); @@ -76,6 +78,7 @@ const Collection = ({collection, searchText}) => { {showRenameCollectionModal && setShowRenameCollectionModal(false)}/>} {showRemoveCollectionFromWSModal && setShowRemoveCollectionFromWSModal(false)}/>} {showDeleteCollectionModal && setShowDeleteCollectionModal(false)}/>} + {showRemoveLocalCollectionModal && setShowRemoveLocalCollectionModal(false)}/>}
@@ -111,12 +114,21 @@ const Collection = ({collection, searchText}) => { }}> Export
-
{ - menuDropdownTippyRef.current.hide(); - setShowRemoveCollectionFromWSModal(true); - }}> - Remove from Workspace -
+ {!isLocal ? ( +
{ + menuDropdownTippyRef.current.hide(); + setShowRemoveCollectionFromWSModal(true); + }}> + Remove from Workspace +
+ ) : ( +
{ + menuDropdownTippyRef.current.hide(); + setShowRemoveLocalCollectionModal(true); + }}> + Remove +
+ )} {!isLocal ? (
{ menuDropdownTippyRef.current.hide(); diff --git a/packages/bruno-app/src/components/Sidebar/Collections/SelectCollection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/SelectCollection/index.js index 78751128..73294cec 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/SelectCollection/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/SelectCollection/index.js @@ -1,11 +1,14 @@ import React from "react"; +import filter from "lodash/filter"; import Modal from "components/Modal/index"; import { IconFiles } from '@tabler/icons'; import { useSelector } from "react-redux"; +import { isLocalCollection } from "utils/collections"; import StyledWrapper from './StyledWrapper'; const SelectCollection = ({onClose, onSelect, title}) => { const { collections } = useSelector((state) => state.collections); + const collectionsToDisplay = filter(collections, (c) => !isLocalCollection(c)); return ( @@ -16,7 +19,7 @@ const SelectCollection = ({onClose, onSelect, title}) => { handleCancel={onClose} >
    - {(collections && collections.length) ? collections.map((c) => ( + {(collectionsToDisplay && collectionsToDisplay.length) ? collectionsToDisplay.map((c) => (
    onSelect(c.uid)}> {c.name}
    diff --git a/packages/bruno-app/src/components/Sidebar/Collections/index.js b/packages/bruno-app/src/components/Sidebar/Collections/index.js index 73d43aa2..8227ac9f 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/index.js @@ -5,6 +5,7 @@ import filter from 'lodash/filter'; import Collection from './Collection'; import CreateOrAddCollection from './CreateOrAddCollection'; import { findCollectionInWorkspace } from 'utils/workspaces'; +import { isLocalCollection } from 'utils/collections'; const Collections = ({searchText}) => { const { collections } = useSelector((state) => state.collections); @@ -15,7 +16,7 @@ const Collections = ({searchText}) => { return null; } - const collectionToDisplay = filter(collections, (c) => findCollectionInWorkspace(activeWorkspace, c.uid)); + const collectionToDisplay = filter(collections, (c) => findCollectionInWorkspace(activeWorkspace, c.uid) && !isLocalCollection(c)); if(!collectionToDisplay || !collectionToDisplay.length) { return ; diff --git a/packages/bruno-app/src/components/Sidebar/LocalCollections/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/LocalCollections/StyledWrapper.js new file mode 100644 index 00000000..c2d9a1ff --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/LocalCollections/StyledWrapper.js @@ -0,0 +1,21 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + .current-workspace { + margin-inline: .5rem; + background: #e1e1e1; + border-radius: 5px; + + .caret { + margin-left: 0.25rem; + color: rgb(140, 140, 140); + fill: rgb(140, 140, 140); + } + } + + div[data-tippy-root] { + width: calc(100% - 1rem); + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/Sidebar/LocalCollections/index.js b/packages/bruno-app/src/components/Sidebar/LocalCollections/index.js new file mode 100644 index 00000000..a010b254 --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/LocalCollections/index.js @@ -0,0 +1,73 @@ +import React, { useRef, forwardRef } from 'react'; +import filter from 'lodash/filter'; +import { useSelector } from 'react-redux'; +import Dropdown from 'components/Dropdown'; +import { IconArrowForwardUp, IconCaretDown, IconFolders, IconPlus } from '@tabler/icons'; +import Collection from '../Collections/Collection'; +import { isLocalCollection } from 'utils/collections'; +import StyledWrapper from './StyledWrapper'; + +const LocalCollections = ({searchText}) => { + const dropdownTippyRef = useRef(); + const { collections } = useSelector((state) => state.collections); + + const collectionToDisplay = filter(collections, (c) => isLocalCollection(c)); + + if(!collectionToDisplay || !collectionToDisplay.length) { + return null; + } + + const Icon = forwardRef((props, ref) => { + return ( +
    +
    + + + + + Local Collections + +
    + +
    + ); + }); + + const onDropdownCreate = (ref) => dropdownTippyRef.current = ref; + + return ( + +
    + } placement='bottom-end'> +
    {}}> +
    + +
    + Create Collection +
    +
    {}}> +
    + +
    + Open Collection +
    + +
    + Note: Local collections are not tied to a workspace +
    +
    +
    +
    + {collectionToDisplay && collectionToDisplay.length ? collectionToDisplay.map((c) => { + return + }) : null} +
    +
    + ); +}; + +export default LocalCollections; diff --git a/packages/bruno-app/src/components/Sidebar/index.js b/packages/bruno-app/src/components/Sidebar/index.js index d738da08..ae8282e0 100644 --- a/packages/bruno-app/src/components/Sidebar/index.js +++ b/packages/bruno-app/src/components/Sidebar/index.js @@ -1,6 +1,7 @@ import React, { useState, useEffect} from 'react'; import { useSelector, useDispatch } from 'react-redux'; import Collections from './Collections'; +import LocalCollections from './LocalCollections'; import TitleBar from './TitleBar'; import MenuBar from './MenuBar'; import { IconSearch, IconChevronsRight } from '@tabler/icons'; @@ -94,6 +95,7 @@ const Sidebar = () => {
+
diff --git a/packages/bruno-app/src/components/Workspaces/WorkspaceSelector/StyledWrapper.js b/packages/bruno-app/src/components/Workspaces/WorkspaceSelector/StyledWrapper.js index 615e17d5..c2d9a1ff 100644 --- a/packages/bruno-app/src/components/Workspaces/WorkspaceSelector/StyledWrapper.js +++ b/packages/bruno-app/src/components/Workspaces/WorkspaceSelector/StyledWrapper.js @@ -3,7 +3,7 @@ import styled from 'styled-components'; const Wrapper = styled.div` .current-workspace { margin-inline: .5rem; - background: #fff; + background: #e1e1e1; border-radius: 5px; .caret { @@ -12,6 +12,10 @@ const Wrapper = styled.div` fill: rgb(140, 140, 140); } } + + div[data-tippy-root] { + width: calc(100% - 1rem); + } `; export default Wrapper; diff --git a/packages/bruno-app/src/components/Workspaces/WorkspaceSelector/index.js b/packages/bruno-app/src/components/Workspaces/WorkspaceSelector/index.js index b9406c93..16f8b323 100644 --- a/packages/bruno-app/src/components/Workspaces/WorkspaceSelector/index.js +++ b/packages/bruno-app/src/components/Workspaces/WorkspaceSelector/index.js @@ -1,6 +1,6 @@ import React, { useRef, forwardRef, useState, useEffect } from 'react'; import Dropdown from 'components/Dropdown'; -import { IconAdjustmentsHorizontal, IconCaretDown, IconBox } from '@tabler/icons'; +import { IconCaretDown, IconBox, IconSwitch3, IconSettings, IconFolders } from '@tabler/icons'; import WorkspaceConfigurer from "../WorkspaceConfigurer"; import { useDispatch, useSelector } from 'react-redux'; import { selectWorkspace } from 'providers/ReduxStore/slices/workspaces'; @@ -42,21 +42,25 @@ const WorkspaceSelector = () => { return ( -
+
} placement='bottom-end'> - {workspaces && workspaces.length && workspaces.map((workspace) => ( + {/* {workspaces && workspaces.length && workspaces.map((workspace) => (
handleSelectWorkspace(workspace)} key={workspace.uid}> {workspace.name}
- ))} - -
{ - setOpenWorkspacesModal(true); - }}> + ))} */} +
handleSelectWorkspace(workspace)}>
- +
- Configure + Switch Workspace +
+ +
handleSelectWorkspace(workspace)}> +
+ +
+ Configure Workspaces
diff --git a/packages/bruno-app/src/pageComponents/Collections/index.js b/packages/bruno-app/src/pageComponents/Collections/index.js index 84f2558a..0dd9b322 100644 --- a/packages/bruno-app/src/pageComponents/Collections/index.js +++ b/packages/bruno-app/src/pageComponents/Collections/index.js @@ -1,19 +1,24 @@ import React from 'react'; +import filter from "lodash/filter"; import { useSelector } from 'react-redux'; import CollectionItem from './CollectionItem'; import StyledWrapper from './StyledWrapper'; +import { isLocalCollection } from 'utils/collections'; export default function Collections() { const collections = useSelector((state) => state.collections.collections); + const collectionsToDisplay = filter(collections, (c) => !isLocalCollection(c)); return (

Collections

- {collections && collections.length ? collections.map((collection) => { + {(collectionsToDisplay && collectionsToDisplay.length) ? collectionsToDisplay.map((collection) => { return ; - }): null} + }): ( +
No collections found
+ )}
); diff --git a/packages/bruno-app/src/pageComponents/Index/index.js b/packages/bruno-app/src/pageComponents/Index/index.js index 1b8ba531..dc7516a6 100644 --- a/packages/bruno-app/src/pageComponents/Index/index.js +++ b/packages/bruno-app/src/pageComponents/Index/index.js @@ -35,6 +35,8 @@ export default function Main() { const isDragging = useSelector((state) => state.app.isDragging); const showHomePage = useSelector((state) => state.app.showHomePage); + console.log(useSelector((state) => state.collections.collections)); + const className = classnames({ 'is-dragging': isDragging }); diff --git a/packages/bruno-app/src/providers/App/useLocalCollectionTreeSync.js b/packages/bruno-app/src/providers/App/useLocalCollectionTreeSync.js index 19510b3b..baeccabe 100644 --- a/packages/bruno-app/src/providers/App/useLocalCollectionTreeSync.js +++ b/packages/bruno-app/src/providers/App/useLocalCollectionTreeSync.js @@ -1,16 +1,14 @@ import { useEffect } from 'react'; import { useDispatch } from 'react-redux'; import { - removeCollectionEvent, localCollectionAddDirectoryEvent, localCollectionAddFileEvent, localCollectionChangeFileEvent, localCollectionUnlinkFileEvent, localCollectionUnlinkDirectoryEvent } from 'providers/ReduxStore/slices/collections'; -import { - openLocalCollectionEvent -} from 'providers/ReduxStore/slices/collections/actions'; +import toast from 'react-hot-toast'; +import { openLocalCollectionEvent } from 'providers/ReduxStore/slices/collections/actions'; import { isElectron } from 'utils/common/platform'; const useLocalCollectionTreeSync = () => { @@ -58,15 +56,13 @@ const useLocalCollectionTreeSync = () => { } }; - const _collectionRemoved = (pathname) => { - // dispatch(removeCollectionEvent({ - // pathname - // })); + const _collectionAlreadyOpened = (pathname) => { + toast.success('Collection is already opened under local collections'); }; const removeListener1 = ipcRenderer.on('main:collection-opened', _openCollection); const removeListener2 = ipcRenderer.on('main:collection-tree-updated', _collectionTreeUpdated); - const removeListener3 = ipcRenderer.on('main:collection-removed', _collectionRemoved); + const removeListener3 = ipcRenderer.on('main:collection-already-opened', _collectionAlreadyOpened); return () => { removeListener1(); 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 4ce67b05..5bbefa54 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -59,15 +59,10 @@ export const openLocalCollectionEvent = (uid, pathname) => (dispatch, getState) items: [] }; - const state = getState(); - const { activeWorkspaceUid } = state.workspaces; - return new Promise((resolve, reject) => { collectionSchema .validate(localCollection) .then(() => dispatch(_createCollection(localCollection))) - .then(waitForNextTick) - .then(() => dispatch(addCollectionToWorkspace(activeWorkspaceUid, localCollection.uid))) .then(resolve) .catch(reject); }); @@ -624,6 +619,17 @@ export const removeLocalCollection = (collectionUid) => (dispatch, getState) => const { ipcRenderer } = window; ipcRenderer .invoke('renderer:remove-collection', collection.pathname) + .then(() => { + dispatch(closeTabs({ + tabUids: recursivelyGetAllItemUids(collection.items) + })); + }) + .then(waitForNextTick) + .then(() => { + dispatch(_deleteCollection({ + collectionUid: collectionUid + })); + }) .then(resolve) .catch(reject); }); diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js index 0763741f..dfef6d08 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js @@ -176,7 +176,9 @@ export const removeCollectionFromWorkspace = (workspaceUid, collectionUid) => (d workspaceCopy.collections = filter(workspaceCopy.collections, (c) => c.uid !== collectionUid); } - saveWorkspaceToIdb(window.__idb, workspaceCopy) + workspaceSchema + .validate(workspaceCopy) + .then(() => saveWorkspaceToIdb(window.__idb, workspaceCopy)) .then(() => dispatch(_removeCollectionFromWorkspace({ workspaceUid: workspaceUid, collectionUid: collectionUid diff --git a/packages/bruno-app/src/styles/globals.css b/packages/bruno-app/src/styles/globals.css index 3e7f4098..41dcfd10 100644 --- a/packages/bruno-app/src/styles/globals.css +++ b/packages/bruno-app/src/styles/globals.css @@ -2,7 +2,7 @@ :root { --color-brand: #546de5; --color-sidebar-collection-item-active-indent-border: #d0d0d0; - --color-sidebar-collection-item-active-background: #dddddd; + --color-sidebar-collection-item-active-background: #e1e1e1; --color-sidebar-background: #f3f3f3; --color-request-dragbar-background: #efefef; --color-request-dragbar-background-active: rgb(200, 200, 200); diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index 249ba72b..4d8bbda2 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -83,6 +83,10 @@ export const findCollectionByUid = (collections, collectionUid) => { return find(collections, (c) => c.uid === collectionUid); }; +export const findCollectionByPathname = (collections, pathname) => { + return find(collections, (c) => c.pathname === pathname); +}; + export const findItemByPathname = (items = [], pathname) => { return find(items, (i) => i.pathname === pathname); }; diff --git a/packages/bruno-app/src/utils/common/error.js b/packages/bruno-app/src/utils/common/error.js new file mode 100644 index 00000000..4ffddc3e --- /dev/null +++ b/packages/bruno-app/src/utils/common/error.js @@ -0,0 +1,34 @@ +import toast from 'react-hot-toast'; + +// levels: 'warning, error' +export class BrunoError extends Error { + constructor(message, level) { + super(message); + this.name = "BrunoError"; + this.level = level || "error"; + } +} + +export const parseError = (error) => { + if(error instanceof BrunoError) { + return error.message; + } + + return error.message ? error.message : 'An error occured'; +}; + +export const toastError = (error) => { + if(error instanceof BrunoError) { + if(error.level === 'warning') { + return toast(error.message, { + icon: '⚠️', + duration: 3000 + }); + } + return toast.error(error.message, { + duration: 3000 + }); + } + + return toast.error(error.message || 'An error occured'); +}; diff --git a/packages/bruno-electron/src/app/collections.js b/packages/bruno-electron/src/app/collections.js index a0d12775..ee48a47f 100644 --- a/packages/bruno-electron/src/app/collections.js +++ b/packages/bruno-electron/src/app/collections.js @@ -14,6 +14,8 @@ const openCollection = async (win, watcher) => { const uid = uuid(); win.webContents.send('main:collection-opened', resolvedPath, uid); watcher.addWatcher(win, resolvedPath, uid); + } else { + win.webContents.send('main:collection-already-opened', resolvedPath); } } else { console.error(`[ERROR] Cannot open unknown folder: "${resolvedPath}"`); diff --git a/packages/bruno-electron/src/app/watcher.js b/packages/bruno-electron/src/app/watcher.js index 90d4514d..aa54372d 100644 --- a/packages/bruno-electron/src/app/watcher.js +++ b/packages/bruno-electron/src/app/watcher.js @@ -126,7 +126,6 @@ class Watcher { if(this.watchers[watchPath]) { this.watchers[watchPath].close(); this.watchers[watchPath] = null; - win.webContents.send('main:collection-removed', watchPath); } } };