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 {
min-width: 300px;
max-width: 500px;
}
&.modal-md {
min-width: 500px;
max-width: 800px;
}
&.modal-lg {
min-width: 800px;
max-width: 1140px;
}
&.modal-xl {
min-width: 1140px;
max-width: calc(100% - 30px);
}
animation: fade-and-slide-in-from-top .50s forwards cubic-bezier(.19,1,.22,1);

View File

@ -47,7 +47,8 @@ const Modal = ({
handleConfirm,
children,
confirmDisabled,
hideCancel
hideCancel,
hideFooter
}) => {
const [isClosing, setIsClosing] = useState(false);
const escFunction = (event) => {
@ -60,7 +61,7 @@ const Modal = ({
const closeModal = () => {
setIsClosing(true);
setTimeout(() => handleCancel(), 500);
}
};
useEffect(() => {
document.addEventListener('keydown', escFunction, false);
@ -79,15 +80,17 @@ const Modal = ({
<div className={`bruno-modal-card modal-${size}`}>
<ModalHeader title={title} handleCancel={() => closeModal()} />
<ModalContent>{children}</ModalContent>
<ModalFooter
{!hideFooter ? <ModalFooter
confirmText={confirmText}
cancelText={cancelText}
handleCancel={() => closeModal()}
handleSubmit={handleConfirm}
confirmDisabled={confirmDisabled}
hideCancel={hideCancel}
/>
/> : null}
</div>
{/* Clicking on backdrop closes the modal */}
<div className="bruno-modal-backdrop" onClick={() => closeModal()} />
</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 NewFolder from 'components/Sidebar/NewFolder';
import CollectionItem from './CollectionItem';
import RemoveCollectionFromWorkspace from './RemoveCollectionFromWorkspace';
import { doesCollectionHaveItemsMatchingSearchText } from 'utils/collections/search';
import { isItemAFolder, isItemARequest } from 'utils/collections';
@ -19,6 +20,7 @@ const Collection = ({collection, searchText}) => {
const [showNewFolderModal, setShowNewFolderModal] = useState(false);
const [showNewRequestModal, setShowNewRequestModal] = useState(false);
const [showRenameCollectionModal, setShowRenameCollectionModal] = useState(false);
const [showRemoveCollectionFromWSModal, setShowRemoveCollectionFromWSModal] = useState(false);
const [showDeleteCollectionModal, setShowDeleteCollectionModal] = useState(false);
const [collectionIsCollapsed, setCollectionIsCollapsed] = useState(collection.collapsed);
const dispatch = useDispatch();
@ -63,6 +65,7 @@ const Collection = ({collection, searchText}) => {
{showNewRequestModal && <NewRequest collection={collection} onClose={() => setShowNewRequestModal(false)}/>}
{showNewFolderModal && <NewFolder collection={collection} onClose={() => setShowNewFolderModal(false)}/>}
{showRenameCollectionModal && <RenameCollection collection={collection} onClose={() => setShowRenameCollectionModal(false)}/>}
{showRemoveCollectionFromWSModal && <RemoveCollectionFromWorkspace collection={collection} onClose={() => setShowRemoveCollectionFromWSModal(false)}/>}
{showDeleteCollectionModal && <DeleteCollection collection={collection} onClose={() => setShowDeleteCollectionModal(false)}/>}
<div className="flex py-1 collection-name items-center">
<div className="flex flex-grow items-center" onClick={handleClick}>
@ -93,6 +96,12 @@ const Collection = ({collection, searchText}) => {
}}>
Rename
</div>
<div className="dropdown-item" onClick={(e) => {
menuDropdownTippyRef.current.hide();
setShowRemoveCollectionFromWSModal(true);
}}>
Remove from Workspace
</div>
<div className="dropdown-item delete-collection" onClick={(e) => {
menuDropdownTippyRef.current.hide();
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 find from 'lodash/find';
import filter from 'lodash/filter';
import Collection from './Collection';
import CreateOrAddCollection from './CreateOrAddCollection';
const Collections = ({searchText}) => {
const collections = useSelector((state) => state.collections.collections);
console.log(collections);
const { collections } = useSelector((state) => state.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 (
<div className="mt-4 flex flex-col">
{collections && collections.length ? collections.map((c) => {
{collectionToDisplay && collectionToDisplay.length ? collectionToDisplay.map((c) => {
return <Collection
searchText={searchText}
collection={c}

View File

@ -2,15 +2,19 @@ import React, { useState, forwardRef, useRef } from 'react';
import toast from 'react-hot-toast';
import Dropdown from 'components/Dropdown';
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 { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
import { showHomePage } from 'providers/ReduxStore/slices/app';
import { IconDots } from '@tabler/icons';
import CreateCollection from '../CreateCollection';
import SelectCollection from 'components/Sidebar/Collections/SelectCollection';
import StyledWrapper from './StyledWrapper';
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 menuDropdownTippyRef = useRef();
@ -23,11 +27,10 @@ const TitleBar = () => {
);
});
const handleCancel = () => setModalOpen(false);
const handleTitleClick = () => dispatch(showHomePage());
const handleConfirm = (values) => {
setModalOpen(false);
const handleCreateCollection = (values) => {
setCreateCollectionModalOpen(false);
dispatch(createCollection(values.collectionName))
.then(() => {
toast.success("Collection created");
@ -35,12 +38,29 @@ const TitleBar = () => {
.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 (
<StyledWrapper className="px-2 py-2">
{modalOpen ? (
{createCollectionModalOpen ? (
<CreateCollection
handleCancel={handleCancel}
handleConfirm={handleConfirm}
handleCancel={() => setCreateCollectionModalOpen(false)}
handleConfirm={handleCreateCollection}
/>
) : null}
{addCollectionToWSModalOpen ? (
<SelectCollection
title='Add Collection to Workspace'
onClose={() => setAddCollectionToWSModalOpen(false)}
onSelect={handleAddCollectionToWorkspace}
/>
): null}
@ -59,7 +79,7 @@ const TitleBar = () => {
<Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement='bottom-start'>
<div className="dropdown-item" onClick={(e) => {
menuDropdownTippyRef.current.hide();
setModalOpen(true);
setCreateCollectionModalOpen(true);
}}>
Create Collection
</div>
@ -70,6 +90,7 @@ const TitleBar = () => {
</div>
<div className="dropdown-item" onClick={(e) => {
menuDropdownTippyRef.current.hide();
setAddCollectionToWSModalOpen(true);
}}>
Add Collection to Workspace
</div>

View File

@ -1,4 +1,5 @@
import React, { useState } from 'react';
import toast from 'react-hot-toast';
import {
IconPlus,
IconUpload,
@ -9,19 +10,22 @@ import {
IconSpeakerphone,
IconDeviceDesktop
} from '@tabler/icons';
import { useDispatch } from 'react-redux';
import { useSelector, useDispatch } from 'react-redux';
import { createCollection } from 'providers/ReduxStore/slices/collections/actions';
import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
import Bruno from 'components/Bruno';
import CreateCollection from 'components/Sidebar/CreateCollection';
import SelectCollection from 'components/Sidebar/Collections/SelectCollection';
import StyledWrapper from './StyledWrapper';
const Welcome = () => {
const [modalOpen, setModalOpen] = useState(false);
const dispatch = useDispatch();
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
const [addCollectionToWSModalOpen, setAddCollectionToWSModalOpen] = useState(false);
const { activeWorkspaceUid } = useSelector((state) => state.workspaces);
const handleCancel = () => setModalOpen(false);
const handleConfirm = (values) => {
setModalOpen(false);
const handleCreateCollection = (values) => {
setCreateCollectionModalOpen(false);
dispatch(createCollection(values.collectionName))
.then(() => {
toast.success("Collection created");
@ -29,14 +33,32 @@ const Welcome = () => {
.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 (
<StyledWrapper className="pb-4 px-6 mt-6">
{modalOpen ? (
{createCollectionModalOpen ? (
<CreateCollection
handleCancel={handleCancel}
handleConfirm={handleConfirm}
handleCancel={() => setCreateCollectionModalOpen(false)}
handleConfirm={handleCreateCollection}
/>
) : null}
{addCollectionToWSModalOpen ? (
<SelectCollection
title='Add Collection to Workspace'
onClose={() => setAddCollectionToWSModalOpen(false)}
onSelect={handleAddCollectionToWorkspace}
/>
): null}
<div className="">
<Bruno width={50} />
</div>
@ -44,12 +66,12 @@ const Welcome = () => {
<div className="mt-4">Opensource API Client.</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">
<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 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 className="flex items-center ml-6">
<IconUpload size={18} strokeWidth={2}/><span className="label ml-2">Import Collection</span>
@ -60,9 +82,9 @@ const Welcome = () => {
</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">
<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 className="flex items-center ml-6">
<IconFolders size={18} strokeWidth={2}/><span className="label ml-2">Open Collection</span>
@ -70,7 +92,7 @@ const Welcome = () => {
</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">
<IconBrandChrome size={18} strokeWidth={2}/><span className="label ml-2">Chrome Extension</span>
</div>

View File

@ -24,8 +24,7 @@ const WorkspaceConfigurer = ({onClose}) => {
</ul>
{openAddModal && <AddWorkspace onClose={() => setOpenAddModal(false)}/>}
</Modal>
)
);
}
export default WorkspaceConfigurer;

View File

@ -5,6 +5,7 @@ import {
recursivelyGetAllItemUids,
transformCollectionToSaveToIdb
} from 'utils/collections';
import { waitForNextTick } from 'utils/common';
import { saveCollectionToIdb, deleteCollectionInIdb } from 'utils/idb';
import {
@ -13,9 +14,10 @@ import {
deleteCollection as _deleteCollection,
} 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 = {
uid: uuid(),
name: collectionName,
@ -23,9 +25,40 @@ export const createCollection = (collectionName) => (dispatch) => {
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) => {
saveCollectionToIdb(window.__idb, 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)
.catch(reject);
});

View File

@ -693,10 +693,7 @@ export const newHttpRequest = (params) => (dispatch, getState) => {
.then((val) => resolve(val))
.catch((err) => reject(err));
})
.catch((err) => {
reject(err);
console.log(err)
});
.catch(reject);
}
});
};

View File

@ -1,11 +1,15 @@
import find from 'lodash/find';
import filter from 'lodash/filter';
import { uuid } from 'utils/common';
import cloneDeep from 'lodash/cloneDeep';
import { getWorkspacesFromIdb, saveWorkspaceToIdb, deleteWorkspaceInIdb } from 'utils/idb/workspaces';
import {
loadWorkspaces,
addWorkspace as _addWorkspace,
renameWorkspace as _renameWorkspace,
deleteWorkspace as _deleteWorkspace
deleteWorkspace as _deleteWorkspace,
addCollectionToWorkspace as _addCollectionToWorkspace,
removeCollectionFromWorkspace as _removeCollectionFromWorkspace
} from './index';
const seedWorkpace = () => {
@ -75,7 +79,7 @@ export const renameWorkspace = (newName, uid) => (dispatch, getState) => {
name: newName
})))
.then(resolve)
.catch((err) => console.log(err));
.catch(reject);
});
};
@ -98,6 +102,76 @@ export const deleteWorkspace = (workspaceUid) => (dispatch, getState) => {
workspaceUid: workspaceUid
})))
.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 each from 'lodash/each';
import find from 'lodash/find';
import filter from 'lodash/filter';
const initialState = {
workspaces: [],
collectionUids: [],
activeWorkspaceUid: null
};
@ -37,6 +38,28 @@ export const workspacesSlice = createSlice({
},
addWorkspace: (state, action) => {
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,
renameWorkspace,
deleteWorkspace,
addWorkspace
addWorkspace,
addCollectionToWorkspace,
removeCollectionFromWorkspace
} = workspacesSlice.actions;
export default workspacesSlice.reducer;

View File

@ -18,3 +18,9 @@ export const simpleHash = str => {
}
return new Uint32Array([hash])[0].toString(36);
};
export const waitForNextTick = () => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(), 0);
});
};