feat: rename collection

This commit is contained in:
Anoop M D 2022-10-11 02:11:52 +05:30
parent 62e9f4d5f0
commit 6476b47d53
7 changed files with 145 additions and 16 deletions

View File

@ -0,0 +1,64 @@
import React, { useRef, useEffect } from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import Modal from 'components/Modal';
import { useDispatch } from 'react-redux';
import { renameCollection } from 'providers/ReduxStore/slices/collections/actions';
const RenameCollection = ({collection, onClose}) => {
const dispatch = useDispatch();
const inputRef = useRef();
const formik = useFormik({
enableReinitialize: true,
initialValues: {
name: collection.name
},
validationSchema: Yup.object({
name: Yup.string()
.min(1, 'must be atleast 1 characters')
.max(50, 'must be 50 characters or less')
.required('name is required')
}),
onSubmit: (values) => {
dispatch(renameCollection(values.name, collection.uid));
onClose();
}
});
useEffect(() => {
if(inputRef && inputRef.current) {
inputRef.current.focus();
}
}, [inputRef]);
const onSubmit = () => formik.handleSubmit();
return (
<Modal
size="sm"
title="Rename Collection"
confirmText='Rename'
handleConfirm={onSubmit}
handleCancel={onClose}
>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
<div>
<label htmlFor="name" className="block font-semibold">Name</label>
<input
id="collection-name" type="text" name="name"
ref={inputRef}
className="block textbox mt-2 w-full"
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false"
onChange={formik.handleChange}
value={formik.values.name || ''}
/>
{formik.touched.name && formik.errors.name ? (
<div className="text-red-500">{formik.errors.name}</div>
) : null}
</div>
</form>
</Modal>
);
};
export default RenameCollection;

View File

@ -11,11 +11,13 @@ import CollectionItem from './CollectionItem';
import { doesCollectionHaveItemsMatchingSearchText } from 'utils/collections/search'; import { doesCollectionHaveItemsMatchingSearchText } from 'utils/collections/search';
import { isItemAFolder, isItemARequest } from 'utils/collections'; import { isItemAFolder, isItemARequest } from 'utils/collections';
import RenameCollection from './RenameCollection';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const Collection = ({collection, searchText}) => { 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 [collectionIsCollapsed, setCollectionIsCollapsed] = useState(collection.collapsed); const [collectionIsCollapsed, setCollectionIsCollapsed] = useState(collection.collapsed);
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -58,6 +60,7 @@ const Collection = ({collection, searchText}) => {
<StyledWrapper className="flex flex-col"> <StyledWrapper className="flex flex-col">
{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)}/>}
<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}>
<IconChevronRight size={16} strokeWidth={2} className={iconClassName} style={{width:16, color: 'rgb(160 160 160)'}}/> <IconChevronRight size={16} strokeWidth={2} className={iconClassName} style={{width:16, color: 'rgb(160 160 160)'}}/>
@ -71,16 +74,23 @@ const Collection = ({collection, searchText}) => {
> >
<div className="dropdown-item" onClick={(e) => { <div className="dropdown-item" onClick={(e) => {
menuDropdownTippyRef.current.hide(); menuDropdownTippyRef.current.hide();
setShowNewRequestModal(true) setShowNewRequestModal(true);
}}> }}>
New Request New Request
</div> </div>
<div className="dropdown-item" onClick={(e) => { <div className="dropdown-item" onClick={(e) => {
menuDropdownTippyRef.current.hide(); menuDropdownTippyRef.current.hide();
setShowNewFolderModal(true) setShowNewFolderModal(true);
}}> }}>
New Folder New Folder
</div> </div>
<div className="dropdown-item" onClick={(e) => {
dispatch(removeCollection(collection.uid));
menuDropdownTippyRef.current.hide();
setShowRenameCollectionModal(true);
}}>
Rename
</div>
<div className="dropdown-item" onClick={(e) => { <div className="dropdown-item" onClick={(e) => {
dispatch(removeCollection(collection.uid)); dispatch(removeCollection(collection.uid));
menuDropdownTippyRef.current.hide(); menuDropdownTippyRef.current.hide();

View File

@ -0,0 +1,20 @@
import React, { useState } from 'react';
import { IconEdit, IconTrash } from "@tabler/icons";
import RenameCollection from 'components/Sidebar/Collections/Collection/RenameCollection';
export default function CollectionItem({collection}) {
const [showRenameCollectionModal, setShowRenameCollectionModal] = useState(false);
return (
<>
{showRenameCollectionModal && <RenameCollection collection={collection} onClose={() => setShowRenameCollectionModal(false)}/>}
<div className="flex justify-between items-baseline mb-2 collection-list-item">
<li style={{listStyle: 'none'}} className="collection-name">{collection.name}</li>
<div className="flex gap-x-4" >
<IconEdit className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setShowRenameCollectionModal(true)}/>
<IconTrash className="cursor-pointer" size={20} strokeWidth={1.5}/>
</div>
</div>
</>
);
};

View File

@ -1,9 +1,8 @@
import React from 'react'; import React from 'react';
import { IconEdit, IconTrash } from "@tabler/icons";
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import CollectionItem from './CollectionItem';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
export default function Collections() { export default function Collections() {
const collections = useSelector((state) => state.collections.collections); const collections = useSelector((state) => state.collections.collections);
@ -12,15 +11,9 @@ export default function Collections() {
<h4 className="heading">Collections</h4> <h4 className="heading">Collections</h4>
<div className="collection-list mt-6"> <div className="collection-list mt-6">
{collections && collections.length ? collections.map((collection) => {collections && collections.length ? collections.map((collection) => {
<div className="flex justify-between items-baseline mb-2 collection-list-item" key={collection.uid} > return <CollectionItem key={collection.uid} collection={collection}/>;
<li style={{listStyle: 'none'}} className="collection-name">{collection.name}</li> }): null}
<div className="flex gap-x-4" >
<IconEdit className="cursor-pointer" size={20} strokeWidth={1.5}/>
<IconTrash className="cursor-pointer" size={20} strokeWidth={1.5}/>
</div>
</div>
): null}
</div> </div>
</StyledWrapper> </StyledWrapper>
); );

View File

@ -0,0 +1,33 @@
import cloneDeep from 'lodash/cloneDeep';
import {
findCollectionByUid,
findItemInCollection,
transformCollectionToSaveToIdb
} from 'utils/collections';
import { saveCollectionToIdb } from 'utils/idb';
import {
_renameCollection
} from './index';
export const renameCollection = (newName, collectionUid) => (dispatch, getState) => {
const state = getState();
const collection = findCollectionByUid(state.collections.collections, collectionUid);
if(collection) {
const collectionCopy = cloneDeep(collection);
collectionCopy.name = newName;
const collectionToSave = transformCollectionToSaveToIdb(collectionCopy, {
ignoreDraft: true
});
saveCollectionToIdb(window.__idb, collectionToSave)
.then(() => {
dispatch(_renameCollection({
newName: newName,
collectionUid: collectionUid
}));
})
.catch((err) => console.log(err));
}
};

View File

@ -28,8 +28,6 @@ const initialState = {
collections: [] collections: []
}; };
const PATH_SEPARATOR = path.sep;
export const collectionsSlice = createSlice({ export const collectionsSlice = createSlice({
name: 'collections', name: 'collections',
initialState, initialState,
@ -42,6 +40,13 @@ export const collectionsSlice = createSlice({
_createCollection: (state, action) => { _createCollection: (state, action) => {
state.collections.push(action.payload); state.collections.push(action.payload);
}, },
_renameCollection: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
if(collection) {
collection.name = action.payload.newName;
}
},
_newItem: (state, action) => { _newItem: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid); const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
@ -521,6 +526,7 @@ export const collectionsSlice = createSlice({
export const { export const {
_createCollection, _createCollection,
_renameCollection,
_loadCollections, _loadCollections,
_newItem, _newItem,
_deleteItem, _deleteItem,

View File

@ -162,7 +162,10 @@ export const transformCollectionToSaveToIdb = (collection, options = {}) => {
di.name = si.name; di.name = si.name;
// if items is draft, then take data from draft to save // if items is draft, then take data from draft to save
if(!options.ignoreDraft && si.draft) { // The condition "!options.ignoreDraft" may appear confusing
// When saving a collection, this option allows the caller to specify to ignore any draft changes while still saving rest of the collection.
// This is useful for performing rename request/collections while still leaving changes in draft not making its way into the indexeddb
if(si.draft && !options.ignoreDraft) {
if(si.draft.request) { if(si.draft.request) {
di.request = { di.request = {
url: si.draft.request.url, url: si.draft.request.url,