forked from extern/bruno
feat: rename collection
This commit is contained in:
parent
62e9f4d5f0
commit
6476b47d53
@ -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;
|
@ -11,11 +11,13 @@ import CollectionItem from './CollectionItem';
|
||||
import { doesCollectionHaveItemsMatchingSearchText } from 'utils/collections/search';
|
||||
import { isItemAFolder, isItemARequest } from 'utils/collections';
|
||||
|
||||
import RenameCollection from './RenameCollection';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const Collection = ({collection, searchText}) => {
|
||||
const [showNewFolderModal, setShowNewFolderModal] = useState(false);
|
||||
const [showNewRequestModal, setShowNewRequestModal] = useState(false);
|
||||
const [showRenameCollectionModal, setShowRenameCollectionModal] = useState(false);
|
||||
const [collectionIsCollapsed, setCollectionIsCollapsed] = useState(collection.collapsed);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@ -58,6 +60,7 @@ const Collection = ({collection, searchText}) => {
|
||||
<StyledWrapper className="flex flex-col">
|
||||
{showNewRequestModal && <NewRequest collection={collection} onClose={() => setShowNewRequestModal(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 flex-grow items-center" onClick={handleClick}>
|
||||
<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) => {
|
||||
menuDropdownTippyRef.current.hide();
|
||||
setShowNewRequestModal(true)
|
||||
setShowNewRequestModal(true);
|
||||
}}>
|
||||
New Request
|
||||
</div>
|
||||
<div className="dropdown-item" onClick={(e) => {
|
||||
menuDropdownTippyRef.current.hide();
|
||||
setShowNewFolderModal(true)
|
||||
setShowNewFolderModal(true);
|
||||
}}>
|
||||
New Folder
|
||||
</div>
|
||||
<div className="dropdown-item" onClick={(e) => {
|
||||
dispatch(removeCollection(collection.uid));
|
||||
menuDropdownTippyRef.current.hide();
|
||||
setShowRenameCollectionModal(true);
|
||||
}}>
|
||||
Rename
|
||||
</div>
|
||||
<div className="dropdown-item" onClick={(e) => {
|
||||
dispatch(removeCollection(collection.uid));
|
||||
menuDropdownTippyRef.current.hide();
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,9 +1,8 @@
|
||||
import React from 'react';
|
||||
import { IconEdit, IconTrash } from "@tabler/icons";
|
||||
import { useSelector } from 'react-redux';
|
||||
import CollectionItem from './CollectionItem';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
|
||||
export default function Collections() {
|
||||
const collections = useSelector((state) => state.collections.collections);
|
||||
|
||||
@ -12,15 +11,9 @@ export default function Collections() {
|
||||
<h4 className="heading">Collections</h4>
|
||||
|
||||
<div className="collection-list mt-6">
|
||||
{collections && collections.length ? collections.map((collection) =>
|
||||
<div className="flex justify-between items-baseline mb-2 collection-list-item" key={collection.uid} >
|
||||
<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}/>
|
||||
<IconTrash className="cursor-pointer" size={20} strokeWidth={1.5}/>
|
||||
</div>
|
||||
</div>
|
||||
): null}
|
||||
{collections && collections.length ? collections.map((collection) => {
|
||||
return <CollectionItem key={collection.uid} collection={collection}/>;
|
||||
}): null}
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
@ -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));
|
||||
}
|
||||
};
|
@ -28,8 +28,6 @@ const initialState = {
|
||||
collections: []
|
||||
};
|
||||
|
||||
const PATH_SEPARATOR = path.sep;
|
||||
|
||||
export const collectionsSlice = createSlice({
|
||||
name: 'collections',
|
||||
initialState,
|
||||
@ -42,6 +40,13 @@ export const collectionsSlice = createSlice({
|
||||
_createCollection: (state, action) => {
|
||||
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) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
|
||||
@ -521,6 +526,7 @@ export const collectionsSlice = createSlice({
|
||||
|
||||
export const {
|
||||
_createCollection,
|
||||
_renameCollection,
|
||||
_loadCollections,
|
||||
_newItem,
|
||||
_deleteItem,
|
@ -162,7 +162,10 @@ export const transformCollectionToSaveToIdb = (collection, options = {}) => {
|
||||
di.name = si.name;
|
||||
|
||||
// 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) {
|
||||
di.request = {
|
||||
url: si.draft.request.url,
|
||||
|
Loading…
Reference in New Issue
Block a user