{
+ dropdownTippyRef.current.hide();
+ setCloneItemModalOpen(true);
+ }}>
+ Clone
+
+ )}
{
dropdownTippyRef.current.hide();
setDeleteItemModalOpen(true);
diff --git a/renderer/components/Sidebar/Collections/index.js b/renderer/components/Sidebar/Collections/index.js
index 951a0e809..c5e0c8890 100644
--- a/renderer/components/Sidebar/Collections/index.js
+++ b/renderer/components/Sidebar/Collections/index.js
@@ -4,6 +4,7 @@ import Collection from './Collection';
const Collections = () => {
const collections = useSelector((state) => state.collections.collections);
+ console.log(collections);
return (
diff --git a/renderer/globalStyles.js b/renderer/globalStyles.js
index d36372eb8..cd18287c6 100644
--- a/renderer/globalStyles.js
+++ b/renderer/globalStyles.js
@@ -6,7 +6,7 @@ const GlobalStyle = createGlobalStyle`
border-right: solid 1px var(--color-codemirror-border);
}
- .grafnode-form {
+ .bruno-form {
.textbox {
line-height: 1.42857143;
background-color: #fff;
diff --git a/renderer/providers/ReduxStore/slices/collections.js b/renderer/providers/ReduxStore/slices/collections.js
index 524d5fb64..9c63b952f 100644
--- a/renderer/providers/ReduxStore/slices/collections.js
+++ b/renderer/providers/ReduxStore/slices/collections.js
@@ -1,9 +1,9 @@
import path from 'path';
import { uuid } from 'utils/common';
-import trim from 'lodash/trim';
import find from 'lodash/find';
import concat from 'lodash/concat';
import filter from 'lodash/filter';
+import each from 'lodash/each';
import cloneDeep from 'lodash/cloneDeep';
import { createSlice } from '@reduxjs/toolkit'
import splitOnFirst from 'split-on-first';
@@ -11,15 +11,15 @@ import { sendNetworkRequest } from 'utils/network';
import {
findCollectionByUid,
findItemInCollection,
- cloneItem,
+ findParentItemInCollection,
transformCollectionToSaveToIdb,
addDepth,
deleteItemInCollection,
isItemARequest,
+ isItemAFolder
} from 'utils/collections';
import { parseQueryParams, stringifyQueryParams } from 'utils/url';
import { getCollectionsFromIdb, saveCollectionToIdb } from 'utils/idb';
-import { each } from 'lodash';
// todo: errors should be tracked in each slice and displayed as toasts
@@ -75,6 +75,21 @@ export const collectionsSlice = createSlice({
}
}
},
+ _cloneItem: (state, action) => {
+ const collectionUid = action.payload.collectionUid;
+ const clonedItem = action.payload.clonedItem;
+ const parentItemUid = action.payload.parentItemUid;
+ const collection = findCollectionByUid(state.collections, collectionUid);
+
+ if(collection) {
+ if(parentItemUid) {
+ const parentItem = findItemInCollection(collection, parentItemUid);
+ parentItem.items.push(clonedItem);
+ } else {
+ collection.items.push(clonedItem);
+ }
+ }
+ },
_requestSent: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
@@ -128,7 +143,7 @@ export const collectionsSlice = createSlice({
},
draft: null
};
- item.draft = cloneItem(item);
+ item.draft = cloneDeep(item);
collection.items.push(item);
}
},
@@ -158,7 +173,7 @@ export const collectionsSlice = createSlice({
if(item && isItemARequest(item)) {
if(!item.draft) {
- item.draft = cloneItem(item);
+ item.draft = cloneDeep(item);
}
item.draft.request.url = action.payload.url;
@@ -194,7 +209,7 @@ export const collectionsSlice = createSlice({
if(item && isItemARequest(item)) {
if(!item.draft) {
- item.draft = cloneItem(item);
+ item.draft = cloneDeep(item);
}
item.draft.request.params = item.draft.request.params || [];
item.draft.request.params.push({
@@ -215,7 +230,7 @@ export const collectionsSlice = createSlice({
if(item && isItemARequest(item)) {
if(!item.draft) {
- item.draft = cloneItem(item);
+ item.draft = cloneDeep(item);
}
const param = find(item.draft.request.params, (h) => h.uid === action.payload.param.uid);
if(param) {
@@ -256,7 +271,7 @@ export const collectionsSlice = createSlice({
if(item && isItemARequest(item)) {
if(!item.draft) {
- item.draft = cloneItem(item);
+ item.draft = cloneDeep(item);
}
item.draft.request.params = filter(item.draft.request.params, (p) => p.uid !== action.payload.paramUid);
@@ -279,7 +294,7 @@ export const collectionsSlice = createSlice({
if(item && isItemARequest(item)) {
if(!item.draft) {
- item.draft = cloneItem(item);
+ item.draft = cloneDeep(item);
}
item.draft.request.headers = item.draft.request.headers || [];
item.draft.request.headers.push({
@@ -300,7 +315,7 @@ export const collectionsSlice = createSlice({
if(item && isItemARequest(item)) {
if(!item.draft) {
- item.draft = cloneItem(item);
+ item.draft = cloneDeep(item);
}
const header = find(item.draft.request.headers, (h) => h.uid === action.payload.header.uid);
if(header) {
@@ -320,7 +335,7 @@ export const collectionsSlice = createSlice({
if(item && isItemARequest(item)) {
if(!item.draft) {
- item.draft = cloneItem(item);
+ item.draft = cloneDeep(item);
}
item.draft.request.headers = filter(item.draft.request.headers, (h) => h.uid !== action.payload.headerUid);
}
@@ -334,7 +349,7 @@ export const collectionsSlice = createSlice({
if(item && isItemARequest(item)) {
if(!item.draft) {
- item.draft = cloneItem(item);
+ item.draft = cloneDeep(item);
}
item.draft.request.body = {
mode: action.payload.mode,
@@ -351,7 +366,7 @@ export const collectionsSlice = createSlice({
if(item && isItemARequest(item)) {
if(!item.draft) {
- item.draft = cloneItem(item);
+ item.draft = cloneDeep(item);
}
item.draft.request.method = action.payload.method;
}
@@ -366,6 +381,7 @@ export const {
_newItem,
_deleteItem,
_renameItem,
+ _cloneItem,
_requestSent,
_responseReceived,
_saveRequest,
@@ -584,6 +600,49 @@ export const renameItem = (newName, itemUid, collectionUid) => (dispatch, getSta
}
};
+export const cloneItem = (newName, itemUid, collectionUid) => (dispatch, getState) => {
+ const state = getState();
+ const collection = findCollectionByUid(state.collections.collections, collectionUid);
+
+ if(collection) {
+ const collectionCopy = cloneDeep(collection);
+ const item = findItemInCollection(collectionCopy, itemUid);
+ if(!item) {
+ return;
+ }
+
+ if(isItemAFolder(item)) {
+ throw new Error('Cloning folders is not supported yet');
+ }
+
+ // todo: clone query params
+ const clonedItem = cloneDeep(item);
+ clonedItem.name = newName;
+ clonedItem.uid = uuid();
+ each(clonedItem.headers, h => h.uid = uuid());
+
+ const parentItem = findParentItemInCollection(collectionCopy, itemUid);
+
+ if(!parentItem) {
+ collectionCopy.items.push(clonedItem);
+ } else {
+ parentItem.items.push(clonedItem);
+ }
+
+ const collectionToSave = transformCollectionToSaveToIdb(collectionCopy);
+
+ saveCollectionToIdb(window.__idb, collectionToSave)
+ .then(() => {
+ dispatch(_cloneItem({
+ parentItemUid: parentItem ? parentItem.uid : null,
+ clonedItem: clonedItem,
+ collectionUid: collectionUid
+ }));
+ })
+ .catch((err) => console.log(err));
+ }
+};
+
export const removeCollection = (collectionPath) => () => {
console.log('removeCollection');
};
diff --git a/renderer/utils/collections/index.js b/renderer/utils/collections/index.js
index 53b6b052f..ac1e403de 100644
--- a/renderer/utils/collections/index.js
+++ b/renderer/utils/collections/index.js
@@ -73,16 +73,20 @@ export const findItemInCollection = (collection, itemUid) => {
return findItem(flattenedItems, itemUid);
}
+export const findParentItemInCollection = (collection, itemUid) => {
+ let flattenedItems = flattenItems(collection.items);
+
+ return find(flattenedItems, (item) => {
+ return item.items && find(item.items, i => i.uid === itemUid);
+ });
+}
+
export const recursivelyGetAllItemUids = (items = []) => {
let flattenedItems = flattenItems(items);
return map(flattenedItems, (i) => i.uid);
};
-export const cloneItem = (item) => {
- return cloneDeep(item);
-};
-
export const transformCollectionToSaveToIdb = (collection, options = {}) => {
const copyHeaders = (headers) => {
return map(headers, (header) => {
@@ -172,7 +176,9 @@ export const deleteItemInCollection = (itemUid, collection) => {
};
export const isItemARequest = (item) => {
- return item.hasOwnProperty('request') && ['http-request', 'graphql-request'].includes(item.type);
+ return item.hasOwnProperty('request')
+ && ['http-request', 'graphql-request'].includes(item.type)
+ && !item.items;
};
export const isItemAFolder = (item) => {