forked from extern/bruno
feat: support adding and removing collections from workspaces
This commit is contained in:
parent
f8fbc88239
commit
6b0ccac1bf
@ -40,18 +40,22 @@ const Wrapper = styled.div`
|
|||||||
|
|
||||||
&.modal-sm {
|
&.modal-sm {
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
|
max-width: 500px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.modal-md {
|
&.modal-md {
|
||||||
min-width: 500px;
|
min-width: 500px;
|
||||||
|
max-width: 800px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.modal-lg {
|
&.modal-lg {
|
||||||
min-width: 800px;
|
min-width: 800px;
|
||||||
|
max-width: 1140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.modal-xl {
|
&.modal-xl {
|
||||||
min-width: 1140px;
|
min-width: 1140px;
|
||||||
|
max-width: calc(100% - 30px);
|
||||||
}
|
}
|
||||||
|
|
||||||
animation: fade-and-slide-in-from-top .50s forwards cubic-bezier(.19,1,.22,1);
|
animation: fade-and-slide-in-from-top .50s forwards cubic-bezier(.19,1,.22,1);
|
||||||
|
@ -47,7 +47,8 @@ const Modal = ({
|
|||||||
handleConfirm,
|
handleConfirm,
|
||||||
children,
|
children,
|
||||||
confirmDisabled,
|
confirmDisabled,
|
||||||
hideCancel
|
hideCancel,
|
||||||
|
hideFooter
|
||||||
}) => {
|
}) => {
|
||||||
const [isClosing, setIsClosing] = useState(false);
|
const [isClosing, setIsClosing] = useState(false);
|
||||||
const escFunction = (event) => {
|
const escFunction = (event) => {
|
||||||
@ -60,7 +61,7 @@ const Modal = ({
|
|||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
setIsClosing(true);
|
setIsClosing(true);
|
||||||
setTimeout(() => handleCancel(), 500);
|
setTimeout(() => handleCancel(), 500);
|
||||||
}
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.addEventListener('keydown', escFunction, false);
|
document.addEventListener('keydown', escFunction, false);
|
||||||
@ -79,15 +80,17 @@ const Modal = ({
|
|||||||
<div className={`bruno-modal-card modal-${size}`}>
|
<div className={`bruno-modal-card modal-${size}`}>
|
||||||
<ModalHeader title={title} handleCancel={() => closeModal()} />
|
<ModalHeader title={title} handleCancel={() => closeModal()} />
|
||||||
<ModalContent>{children}</ModalContent>
|
<ModalContent>{children}</ModalContent>
|
||||||
<ModalFooter
|
{!hideFooter ? <ModalFooter
|
||||||
confirmText={confirmText}
|
confirmText={confirmText}
|
||||||
cancelText={cancelText}
|
cancelText={cancelText}
|
||||||
handleCancel={() => closeModal()}
|
handleCancel={() => closeModal()}
|
||||||
handleSubmit={handleConfirm}
|
handleSubmit={handleConfirm}
|
||||||
confirmDisabled={confirmDisabled}
|
confirmDisabled={confirmDisabled}
|
||||||
hideCancel={hideCancel}
|
hideCancel={hideCancel}
|
||||||
/>
|
/> : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Clicking on backdrop closes the modal */}
|
||||||
<div className="bruno-modal-backdrop" onClick={() => closeModal()} />
|
<div className="bruno-modal-backdrop" onClick={() => closeModal()} />
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import Modal from 'components/Modal';
|
||||||
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
import { recursivelyGetAllItemUids } from 'utils/collections';
|
||||||
|
import { removeCollectionFromWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
|
||||||
|
import { closeTabs } from 'providers/ReduxStore/slices/tabs';
|
||||||
|
|
||||||
|
const RemoveCollectionFromWorkspace = ({onClose, collection}) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { activeWorkspaceUid } = useSelector((state) => state.workspaces);
|
||||||
|
|
||||||
|
const onConfirm = () =>{
|
||||||
|
dispatch(removeCollectionFromWorkspace(activeWorkspaceUid, collection.uid))
|
||||||
|
.then(() => {
|
||||||
|
dispatch(closeTabs({
|
||||||
|
tabUids: recursivelyGetAllItemUids(collection.items)
|
||||||
|
}));
|
||||||
|
toast.success("Collection removed from workspace");
|
||||||
|
})
|
||||||
|
.catch((err) => console.log(err) && toast.error("An error occured while removing the collection"));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
size="sm"
|
||||||
|
title="Remove Collection from Workspace"
|
||||||
|
confirmText="Remove"
|
||||||
|
handleConfirm={onConfirm}
|
||||||
|
handleCancel={onClose}
|
||||||
|
>
|
||||||
|
Are you sure you want to remove the collection <span className="font-semibold">{collection.name}</span> from this workspace?
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RemoveCollectionFromWorkspace;
|
@ -8,6 +8,7 @@ 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';
|
||||||
import CollectionItem from './CollectionItem';
|
import CollectionItem from './CollectionItem';
|
||||||
|
import RemoveCollectionFromWorkspace from './RemoveCollectionFromWorkspace';
|
||||||
import { doesCollectionHaveItemsMatchingSearchText } from 'utils/collections/search';
|
import { doesCollectionHaveItemsMatchingSearchText } from 'utils/collections/search';
|
||||||
import { isItemAFolder, isItemARequest } from 'utils/collections';
|
import { isItemAFolder, isItemARequest } from 'utils/collections';
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ 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 [showRemoveCollectionFromWSModal, setShowRemoveCollectionFromWSModal] = useState(false);
|
||||||
const [showDeleteCollectionModal, setShowDeleteCollectionModal] = 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();
|
||||||
@ -63,6 +65,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)}/>}
|
||||||
|
{showRemoveCollectionFromWSModal && <RemoveCollectionFromWorkspace collection={collection} onClose={() => setShowRemoveCollectionFromWSModal(false)}/>}
|
||||||
{showDeleteCollectionModal && <DeleteCollection collection={collection} onClose={() => setShowDeleteCollectionModal(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}>
|
||||||
@ -93,6 +96,12 @@ const Collection = ({collection, searchText}) => {
|
|||||||
}}>
|
}}>
|
||||||
Rename
|
Rename
|
||||||
</div>
|
</div>
|
||||||
|
<div className="dropdown-item" onClick={(e) => {
|
||||||
|
menuDropdownTippyRef.current.hide();
|
||||||
|
setShowRemoveCollectionFromWSModal(true);
|
||||||
|
}}>
|
||||||
|
Remove from Workspace
|
||||||
|
</div>
|
||||||
<div className="dropdown-item delete-collection" onClick={(e) => {
|
<div className="dropdown-item delete-collection" onClick={(e) => {
|
||||||
menuDropdownTippyRef.current.hide();
|
menuDropdownTippyRef.current.hide();
|
||||||
setShowDeleteCollectionModal(true);
|
setShowDeleteCollectionModal(true);
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
import CreateCollection from 'components/Sidebar/CreateCollection';
|
||||||
|
import SelectCollection from 'components/Sidebar/Collections/SelectCollection';
|
||||||
|
import { createCollection } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
|
||||||
|
|
||||||
|
const CreateOrAddCollection = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
|
||||||
|
const [addCollectionToWSModalOpen, setAddCollectionToWSModalOpen] = useState(false);
|
||||||
|
const { activeWorkspaceUid } = useSelector((state) => state.workspaces);
|
||||||
|
|
||||||
|
const handleCreateCollection = (values) => {
|
||||||
|
setCreateCollectionModalOpen(false);
|
||||||
|
dispatch(createCollection(values.collectionName))
|
||||||
|
.then(() => {
|
||||||
|
toast.success("Collection created");
|
||||||
|
})
|
||||||
|
.catch(() => toast.error("An error occured while creating the collection"));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddCollectionToWorkspace = (collectionUid) => {
|
||||||
|
setAddCollectionToWSModalOpen(false);
|
||||||
|
dispatch(addCollectionToWorkspace(activeWorkspaceUid, collectionUid))
|
||||||
|
.then(() => {
|
||||||
|
toast.success("Collection added to workspace");
|
||||||
|
})
|
||||||
|
.catch(() => toast.error("An error occured while adding collection to workspace"));
|
||||||
|
};
|
||||||
|
|
||||||
|
const CreateLink = () => <span className='underline text-link cursor-pointer' onClick={() => setCreateCollectionModalOpen(true)}>Create</span>;
|
||||||
|
const AddLink = () => <span className='underline text-link cursor-pointer' onClick={() => setAddCollectionToWSModalOpen(true)}>Add</span>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='px-2 mt-4 text-gray-600'>
|
||||||
|
{createCollectionModalOpen ? (
|
||||||
|
<CreateCollection
|
||||||
|
handleCancel={() => setCreateCollectionModalOpen(false)}
|
||||||
|
handleConfirm={handleCreateCollection}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{addCollectionToWSModalOpen ? (
|
||||||
|
<SelectCollection
|
||||||
|
title='Add Collection to Workspace'
|
||||||
|
onClose={() => setAddCollectionToWSModalOpen(false)}
|
||||||
|
onSelect={handleAddCollectionToWorkspace}
|
||||||
|
/>
|
||||||
|
): null}
|
||||||
|
|
||||||
|
<div className='text-xs text-center'>
|
||||||
|
<div>No collections found.</div>
|
||||||
|
<div className='mt-2'>
|
||||||
|
<CreateLink /> or <AddLink /> Collection to Workspace.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateOrAddCollection;
|
@ -0,0 +1,18 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
div.collection {
|
||||||
|
padding: 4px 6px;
|
||||||
|
padding-left: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
@ -0,0 +1,30 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Modal from "components/Modal/index";
|
||||||
|
import { IconFiles } from '@tabler/icons';
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const SelectCollection = ({onClose, onSelect, title}) => {
|
||||||
|
const { collections } = useSelector((state) => state.collections);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper>
|
||||||
|
<Modal
|
||||||
|
size="sm"
|
||||||
|
title={title || "Select Collection"}
|
||||||
|
hideFooter={true}
|
||||||
|
handleCancel={onClose}
|
||||||
|
>
|
||||||
|
<ul className="mb-2" >
|
||||||
|
{collections && collections.length && collections.map((c) => (
|
||||||
|
<div className="collection" key={c.uid} onClick={() => onSelect(c.uid)}>
|
||||||
|
<IconFiles size={18} strokeWidth={1.5}/> <span className="ml-2">{c.name}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</Modal>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelectCollection;
|
@ -1,14 +1,29 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
import find from 'lodash/find';
|
||||||
|
import filter from 'lodash/filter';
|
||||||
import Collection from './Collection';
|
import Collection from './Collection';
|
||||||
|
import CreateOrAddCollection from './CreateOrAddCollection';
|
||||||
|
|
||||||
const Collections = ({searchText}) => {
|
const Collections = ({searchText}) => {
|
||||||
const collections = useSelector((state) => state.collections.collections);
|
const { collections } = useSelector((state) => state.collections);
|
||||||
console.log(collections);
|
const { workspaces, activeWorkspaceUid } = useSelector((state) => state.workspaces);
|
||||||
|
const activeWorkspace = find(workspaces, w => w.uid === activeWorkspaceUid);
|
||||||
|
|
||||||
|
if(!activeWorkspace) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { collectionUids } = activeWorkspace;
|
||||||
|
const collectionToDisplay = filter(collections, (c) => collectionUids.includes(c.uid));
|
||||||
|
|
||||||
|
if(!collectionToDisplay || !collectionToDisplay.length) {
|
||||||
|
return <CreateOrAddCollection />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-4 flex flex-col">
|
<div className="mt-4 flex flex-col">
|
||||||
{collections && collections.length ? collections.map((c) => {
|
{collectionToDisplay && collectionToDisplay.length ? collectionToDisplay.map((c) => {
|
||||||
return <Collection
|
return <Collection
|
||||||
searchText={searchText}
|
searchText={searchText}
|
||||||
collection={c}
|
collection={c}
|
||||||
|
@ -2,15 +2,19 @@ import React, { useState, forwardRef, useRef } from 'react';
|
|||||||
import toast from 'react-hot-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 { useSelector, useDispatch } from 'react-redux';
|
||||||
import { createCollection } from 'providers/ReduxStore/slices/collections/actions';
|
import { createCollection } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/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';
|
||||||
|
import SelectCollection from 'components/Sidebar/Collections/SelectCollection';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
const TitleBar = () => {
|
const TitleBar = () => {
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
|
||||||
|
const [addCollectionToWSModalOpen, setAddCollectionToWSModalOpen] = useState(false);
|
||||||
|
const { activeWorkspaceUid } = useSelector((state) => state.workspaces);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const menuDropdownTippyRef = useRef();
|
const menuDropdownTippyRef = useRef();
|
||||||
@ -23,11 +27,10 @@ const TitleBar = () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleCancel = () => setModalOpen(false);
|
|
||||||
const handleTitleClick = () => dispatch(showHomePage());
|
const handleTitleClick = () => dispatch(showHomePage());
|
||||||
|
|
||||||
const handleConfirm = (values) => {
|
const handleCreateCollection = (values) => {
|
||||||
setModalOpen(false);
|
setCreateCollectionModalOpen(false);
|
||||||
dispatch(createCollection(values.collectionName))
|
dispatch(createCollection(values.collectionName))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success("Collection created");
|
toast.success("Collection created");
|
||||||
@ -35,14 +38,31 @@ const TitleBar = () => {
|
|||||||
.catch(() => toast.error("An error occured while creating the collection"));
|
.catch(() => toast.error("An error occured while creating the collection"));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAddCollectionToWorkspace = (collectionUid) => {
|
||||||
|
setAddCollectionToWSModalOpen(false);
|
||||||
|
dispatch(addCollectionToWorkspace(activeWorkspaceUid, collectionUid))
|
||||||
|
.then(() => {
|
||||||
|
toast.success("Collection added to workspace");
|
||||||
|
})
|
||||||
|
.catch(() => toast.error("An error occured while adding collection to workspace"));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="px-2 py-2">
|
<StyledWrapper className="px-2 py-2">
|
||||||
{modalOpen ? (
|
{createCollectionModalOpen ? (
|
||||||
<CreateCollection
|
<CreateCollection
|
||||||
handleCancel={handleCancel}
|
handleCancel={() => setCreateCollectionModalOpen(false)}
|
||||||
handleConfirm={handleConfirm}
|
handleConfirm={handleCreateCollection}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{addCollectionToWSModalOpen ? (
|
||||||
|
<SelectCollection
|
||||||
|
title='Add Collection to Workspace'
|
||||||
|
onClose={() => setAddCollectionToWSModalOpen(false)}
|
||||||
|
onSelect={handleAddCollectionToWorkspace}
|
||||||
|
/>
|
||||||
|
): null}
|
||||||
|
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="flex items-center cursor-pointer" onClick={handleTitleClick}>
|
<div className="flex items-center cursor-pointer" onClick={handleTitleClick}>
|
||||||
@ -59,7 +79,7 @@ const TitleBar = () => {
|
|||||||
<Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement='bottom-start'>
|
<Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement='bottom-start'>
|
||||||
<div className="dropdown-item" onClick={(e) => {
|
<div className="dropdown-item" onClick={(e) => {
|
||||||
menuDropdownTippyRef.current.hide();
|
menuDropdownTippyRef.current.hide();
|
||||||
setModalOpen(true);
|
setCreateCollectionModalOpen(true);
|
||||||
}}>
|
}}>
|
||||||
Create Collection
|
Create Collection
|
||||||
</div>
|
</div>
|
||||||
@ -70,6 +90,7 @@ const TitleBar = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="dropdown-item" onClick={(e) => {
|
<div className="dropdown-item" onClick={(e) => {
|
||||||
menuDropdownTippyRef.current.hide();
|
menuDropdownTippyRef.current.hide();
|
||||||
|
setAddCollectionToWSModalOpen(true);
|
||||||
}}>
|
}}>
|
||||||
Add Collection to Workspace
|
Add Collection to Workspace
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
import {
|
import {
|
||||||
IconPlus,
|
IconPlus,
|
||||||
IconUpload,
|
IconUpload,
|
||||||
@ -9,19 +10,22 @@ import {
|
|||||||
IconSpeakerphone,
|
IconSpeakerphone,
|
||||||
IconDeviceDesktop
|
IconDeviceDesktop
|
||||||
} from '@tabler/icons';
|
} from '@tabler/icons';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { createCollection } from 'providers/ReduxStore/slices/collections/actions';
|
import { createCollection } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
|
||||||
import Bruno from 'components/Bruno';
|
import Bruno from 'components/Bruno';
|
||||||
import CreateCollection from 'components/Sidebar/CreateCollection';
|
import CreateCollection from 'components/Sidebar/CreateCollection';
|
||||||
|
import SelectCollection from 'components/Sidebar/Collections/SelectCollection';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
const Welcome = () => {
|
const Welcome = () => {
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
|
||||||
|
const [addCollectionToWSModalOpen, setAddCollectionToWSModalOpen] = useState(false);
|
||||||
|
const { activeWorkspaceUid } = useSelector((state) => state.workspaces);
|
||||||
|
|
||||||
const handleCancel = () => setModalOpen(false);
|
const handleCreateCollection = (values) => {
|
||||||
const handleConfirm = (values) => {
|
setCreateCollectionModalOpen(false);
|
||||||
setModalOpen(false);
|
|
||||||
dispatch(createCollection(values.collectionName))
|
dispatch(createCollection(values.collectionName))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success("Collection created");
|
toast.success("Collection created");
|
||||||
@ -29,14 +33,32 @@ const Welcome = () => {
|
|||||||
.catch(() => toast.error("An error occured while creating the collection"));
|
.catch(() => toast.error("An error occured while creating the collection"));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAddCollectionToWorkspace = (collectionUid) => {
|
||||||
|
setAddCollectionToWSModalOpen(false);
|
||||||
|
dispatch(addCollectionToWorkspace(activeWorkspaceUid, collectionUid))
|
||||||
|
.then(() => {
|
||||||
|
toast.success("Collection added to workspace");
|
||||||
|
})
|
||||||
|
.catch(() => toast.error("An error occured while adding collection to workspace"));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="pb-4 px-6 mt-6">
|
<StyledWrapper className="pb-4 px-6 mt-6">
|
||||||
{modalOpen ? (
|
{createCollectionModalOpen ? (
|
||||||
<CreateCollection
|
<CreateCollection
|
||||||
handleCancel={handleCancel}
|
handleCancel={() => setCreateCollectionModalOpen(false)}
|
||||||
handleConfirm={handleConfirm}
|
handleConfirm={handleCreateCollection}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{addCollectionToWSModalOpen ? (
|
||||||
|
<SelectCollection
|
||||||
|
title='Add Collection to Workspace'
|
||||||
|
onClose={() => setAddCollectionToWSModalOpen(false)}
|
||||||
|
onSelect={handleAddCollectionToWorkspace}
|
||||||
|
/>
|
||||||
|
): null}
|
||||||
|
|
||||||
<div className="">
|
<div className="">
|
||||||
<Bruno width={50} />
|
<Bruno width={50} />
|
||||||
</div>
|
</div>
|
||||||
@ -44,12 +66,12 @@ const Welcome = () => {
|
|||||||
<div className="mt-4">Opensource API Client.</div>
|
<div className="mt-4">Opensource API Client.</div>
|
||||||
|
|
||||||
<div className="uppercase font-semibold create-request mt-10">Collections</div>
|
<div className="uppercase font-semibold create-request mt-10">Collections</div>
|
||||||
<div className="mt-4 flex items-center collection-options">
|
<div className="mt-4 flex items-center collection-options select-none">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<IconPlus size={18} strokeWidth={2}/><span className="label ml-2" onClick={() => setModalOpen(true)}>Create Collection</span>
|
<IconPlus size={18} strokeWidth={2}/><span className="label ml-2" onClick={() => setCreateCollectionModalOpen(true)}>Create Collection</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center ml-6">
|
<div className="flex items-center ml-6">
|
||||||
<IconFiles size={18} strokeWidth={2}/><span className="label ml-2">Add Collection to Workspace</span>
|
<IconFiles size={18} strokeWidth={2}/><span className="label ml-2" onClick={() => setAddCollectionToWSModalOpen(true)}>Add Collection to Workspace</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center ml-6">
|
<div className="flex items-center ml-6">
|
||||||
<IconUpload size={18} strokeWidth={2}/><span className="label ml-2">Import Collection</span>
|
<IconUpload size={18} strokeWidth={2}/><span className="label ml-2">Import Collection</span>
|
||||||
@ -60,9 +82,9 @@ const Welcome = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="uppercase font-semibold create-request mt-10 pt-6">Local Collections</div>
|
<div className="uppercase font-semibold create-request mt-10 pt-6">Local Collections</div>
|
||||||
<div className="mt-4 flex items-center collection-options">
|
<div className="mt-4 flex items-center collection-options select-none">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<IconPlus size={18} strokeWidth={2}/><span className="label ml-2" onClick={() => setModalOpen(true)}>Create Collection</span>
|
<IconPlus size={18} strokeWidth={2}/><span className="label ml-2" onClick={() => setCreateCollectionModalOpen(true)}>Create Collection</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center ml-6">
|
<div className="flex items-center ml-6">
|
||||||
<IconFolders size={18} strokeWidth={2}/><span className="label ml-2">Open Collection</span>
|
<IconFolders size={18} strokeWidth={2}/><span className="label ml-2">Open Collection</span>
|
||||||
@ -70,7 +92,7 @@ const Welcome = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="uppercase font-semibold create-request mt-10 pt-6">Links</div>
|
<div className="uppercase font-semibold create-request mt-10 pt-6">Links</div>
|
||||||
<div className="mt-4 flex flex-col collection-options">
|
<div className="mt-4 flex flex-col collection-options select-none">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<IconBrandChrome size={18} strokeWidth={2}/><span className="label ml-2">Chrome Extension</span>
|
<IconBrandChrome size={18} strokeWidth={2}/><span className="label ml-2">Chrome Extension</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,8 +24,7 @@ const WorkspaceConfigurer = ({onClose}) => {
|
|||||||
</ul>
|
</ul>
|
||||||
{openAddModal && <AddWorkspace onClose={() => setOpenAddModal(false)}/>}
|
{openAddModal && <AddWorkspace onClose={() => setOpenAddModal(false)}/>}
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WorkspaceConfigurer;
|
export default WorkspaceConfigurer;
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
recursivelyGetAllItemUids,
|
recursivelyGetAllItemUids,
|
||||||
transformCollectionToSaveToIdb
|
transformCollectionToSaveToIdb
|
||||||
} from 'utils/collections';
|
} from 'utils/collections';
|
||||||
|
import { waitForNextTick } from 'utils/common';
|
||||||
import { saveCollectionToIdb, deleteCollectionInIdb } from 'utils/idb';
|
import { saveCollectionToIdb, deleteCollectionInIdb } from 'utils/idb';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -13,9 +14,10 @@ import {
|
|||||||
deleteCollection as _deleteCollection,
|
deleteCollection as _deleteCollection,
|
||||||
} from './index';
|
} from './index';
|
||||||
|
|
||||||
import { closeTabs } from 'providers/ReduxStore/slices/tabs';
|
import { closeTabs, addTab } from 'providers/ReduxStore/slices/tabs';
|
||||||
|
import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
|
||||||
|
|
||||||
export const createCollection = (collectionName) => (dispatch) => {
|
export const createCollection = (collectionName) => (dispatch, getState) => {
|
||||||
const newCollection = {
|
const newCollection = {
|
||||||
uid: uuid(),
|
uid: uuid(),
|
||||||
name: collectionName,
|
name: collectionName,
|
||||||
@ -23,9 +25,40 @@ export const createCollection = (collectionName) => (dispatch) => {
|
|||||||
environments: [],
|
environments: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const requestItem = {
|
||||||
|
uid: uuid(),
|
||||||
|
type: 'http-request',
|
||||||
|
name: 'Untitled',
|
||||||
|
request: {
|
||||||
|
method: 'GET',
|
||||||
|
url: '',
|
||||||
|
headers: [],
|
||||||
|
body: {
|
||||||
|
mode: 'none',
|
||||||
|
json: null,
|
||||||
|
text: null,
|
||||||
|
xml: null,
|
||||||
|
multipartForm: null,
|
||||||
|
formUrlEncoded: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
newCollection.items.push(requestItem)
|
||||||
|
|
||||||
|
const state = getState();
|
||||||
|
const { activeWorkspaceUid } = state.workspaces;
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
saveCollectionToIdb(window.__idb, newCollection)
|
saveCollectionToIdb(window.__idb, newCollection)
|
||||||
.then(() => dispatch(_createCollection(newCollection)))
|
.then(() => dispatch(_createCollection(newCollection)))
|
||||||
|
.then(waitForNextTick)
|
||||||
|
.then(() => dispatch(addCollectionToWorkspace(activeWorkspaceUid, newCollection.uid)))
|
||||||
|
.then(waitForNextTick)
|
||||||
|
.then(() => dispatch(addTab({
|
||||||
|
uid: requestItem.uid,
|
||||||
|
collectionUid: newCollection.uid
|
||||||
|
})))
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
});
|
});
|
||||||
|
@ -693,10 +693,7 @@ export const newHttpRequest = (params) => (dispatch, getState) => {
|
|||||||
.then((val) => resolve(val))
|
.then((val) => resolve(val))
|
||||||
.catch((err) => reject(err));
|
.catch((err) => reject(err));
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(reject);
|
||||||
reject(err);
|
|
||||||
console.log(err)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
import find from 'lodash/find';
|
import find from 'lodash/find';
|
||||||
|
import filter from 'lodash/filter';
|
||||||
import { uuid } from 'utils/common';
|
import { uuid } from 'utils/common';
|
||||||
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import { getWorkspacesFromIdb, saveWorkspaceToIdb, deleteWorkspaceInIdb } from 'utils/idb/workspaces';
|
import { getWorkspacesFromIdb, saveWorkspaceToIdb, deleteWorkspaceInIdb } from 'utils/idb/workspaces';
|
||||||
import {
|
import {
|
||||||
loadWorkspaces,
|
loadWorkspaces,
|
||||||
addWorkspace as _addWorkspace,
|
addWorkspace as _addWorkspace,
|
||||||
renameWorkspace as _renameWorkspace,
|
renameWorkspace as _renameWorkspace,
|
||||||
deleteWorkspace as _deleteWorkspace
|
deleteWorkspace as _deleteWorkspace,
|
||||||
|
addCollectionToWorkspace as _addCollectionToWorkspace,
|
||||||
|
removeCollectionFromWorkspace as _removeCollectionFromWorkspace
|
||||||
} from './index';
|
} from './index';
|
||||||
|
|
||||||
const seedWorkpace = () => {
|
const seedWorkpace = () => {
|
||||||
@ -75,7 +79,7 @@ export const renameWorkspace = (newName, uid) => (dispatch, getState) => {
|
|||||||
name: newName
|
name: newName
|
||||||
})))
|
})))
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch((err) => console.log(err));
|
.catch(reject);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -98,6 +102,76 @@ export const deleteWorkspace = (workspaceUid) => (dispatch, getState) => {
|
|||||||
workspaceUid: workspaceUid
|
workspaceUid: workspaceUid
|
||||||
})))
|
})))
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch((err) => console.log(err));
|
.catch(reject);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const addCollectionToWorkspace = (workspaceUid, collectionUid) => (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const workspace = find(state.workspaces.workspaces, (w) => w.uid === workspaceUid);
|
||||||
|
const collection = find(state.collections.collections, (c) => c.uid === collectionUid);
|
||||||
|
|
||||||
|
if(!workspace) {
|
||||||
|
return reject(new Error('Workspace not found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!collection) {
|
||||||
|
return reject(new Error('Collection not found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaceCopy = cloneDeep(workspace);
|
||||||
|
if(workspaceCopy.collectionUids && workspace.collectionUids.length) {
|
||||||
|
if(!workspaceCopy.collectionUids.includes(collectionUid)) {
|
||||||
|
workspaceCopy.collectionUids.push(collectionUid);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
workspaceCopy.collectionUids = [collectionUid];
|
||||||
|
}
|
||||||
|
|
||||||
|
saveWorkspaceToIdb(window.__idb, workspaceCopy)
|
||||||
|
.then(() => dispatch(_addCollectionToWorkspace({
|
||||||
|
workspaceUid: workspaceUid,
|
||||||
|
collectionUid: collectionUid
|
||||||
|
})))
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeCollectionFromWorkspace = (workspaceUid, collectionUid) => (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const workspace = find(state.workspaces.workspaces, (w) => w.uid === workspaceUid);
|
||||||
|
const collection = find(state.collections.collections, (c) => c.uid === collectionUid);
|
||||||
|
|
||||||
|
if(!workspace) {
|
||||||
|
return reject(new Error('Workspace not found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!collection) {
|
||||||
|
return reject(new Error('Collection not found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaceCopy = cloneDeep(workspace);
|
||||||
|
if(workspaceCopy.collectionUids && workspace.collectionUids.length) {
|
||||||
|
workspaceCopy.collectionUids = filter(workspaceCopy.collectionUids, (uid) => uid !== collectionUid);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveWorkspaceToIdb(window.__idb, workspaceCopy)
|
||||||
|
.then(() => dispatch(_removeCollectionFromWorkspace({
|
||||||
|
workspaceUid: workspaceUid,
|
||||||
|
collectionUid: collectionUid
|
||||||
|
})))
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// Workspaces can have collection uids that no longer exist
|
||||||
|
// or the user may have the collections access revoked (in teams)
|
||||||
|
// This action will have to be called at the beginning to purge any zombi collectionUids in the workspaces
|
||||||
|
export const removeZombieCollectionFromAllWorkspaces = (collectionUid) => (dispatch, getState) => {};
|
@ -1,9 +1,10 @@
|
|||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
import each from 'lodash/each';
|
|
||||||
import find from 'lodash/find';
|
import find from 'lodash/find';
|
||||||
|
import filter from 'lodash/filter';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
workspaces: [],
|
workspaces: [],
|
||||||
|
collectionUids: [],
|
||||||
activeWorkspaceUid: null
|
activeWorkspaceUid: null
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -37,6 +38,28 @@ export const workspacesSlice = createSlice({
|
|||||||
},
|
},
|
||||||
addWorkspace: (state, action) => {
|
addWorkspace: (state, action) => {
|
||||||
state.workspaces.push(action.payload.workspace);
|
state.workspaces.push(action.payload.workspace);
|
||||||
|
},
|
||||||
|
addCollectionToWorkspace: (state, action) => {
|
||||||
|
const { workspaceUid, collectionUid } = action.payload;
|
||||||
|
const workspace = find(state.workspaces, (w) => w.uid === workspaceUid);
|
||||||
|
|
||||||
|
if(workspace) {
|
||||||
|
if(workspace.collectionUids && workspace.collectionUids.length) {
|
||||||
|
if(!workspace.collectionUids.includes(collectionUid)) {
|
||||||
|
workspace.collectionUids.push(collectionUid);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
workspace.collectionUids = [collectionUid];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeCollectionFromWorkspace: (state, action) => {
|
||||||
|
const { workspaceUid, collectionUid } = action.payload;
|
||||||
|
const workspace = find(state.workspaces, (w) => w.uid === workspaceUid);
|
||||||
|
|
||||||
|
if(workspace && workspace.collectionUids && workspace.collectionUids.length) {
|
||||||
|
workspace.collectionUids = filter(workspace.collectionUids, (uid) => uid !== collectionUid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -46,7 +69,9 @@ export const {
|
|||||||
selectWorkspace,
|
selectWorkspace,
|
||||||
renameWorkspace,
|
renameWorkspace,
|
||||||
deleteWorkspace,
|
deleteWorkspace,
|
||||||
addWorkspace
|
addWorkspace,
|
||||||
|
addCollectionToWorkspace,
|
||||||
|
removeCollectionFromWorkspace
|
||||||
} = workspacesSlice.actions;
|
} = workspacesSlice.actions;
|
||||||
|
|
||||||
export default workspacesSlice.reducer;
|
export default workspacesSlice.reducer;
|
||||||
|
@ -18,3 +18,9 @@ export const simpleHash = str => {
|
|||||||
}
|
}
|
||||||
return new Uint32Array([hash])[0].toString(36);
|
return new Uint32Array([hash])[0].toString(36);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const waitForNextTick = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
setTimeout(() => resolve(), 0);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user