mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-22 07:53:34 +01:00
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 { 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();
|
||||||
|
@ -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 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>
|
||||||
);
|
);
|
||||||
|
@ -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: []
|
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,
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user