feat: support adding and removing collections from workspaces

This commit is contained in:
Anoop M D 2022-10-14 00:20:02 +05:30
parent f8fbc88239
commit 6b0ccac1bf
16 changed files with 401 additions and 45 deletions

View File

@ -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);

View File

@ -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>
); );

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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}

View File

@ -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>

View File

@ -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>

View File

@ -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;

View File

@ -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);
}); });

View File

@ -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)
});
} }
}); });
}; };

View File

@ -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) => {};

View File

@ -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;

View File

@ -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);
});
};