diff --git a/packages/bruno-app/src/components/FolderSettings/Headers/StyledWrapper.js b/packages/bruno-app/src/components/FolderSettings/Headers/StyledWrapper.js new file mode 100644 index 000000000..9f723cb81 --- /dev/null +++ b/packages/bruno-app/src/components/FolderSettings/Headers/StyledWrapper.js @@ -0,0 +1,56 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + table { + width: 100%; + border-collapse: collapse; + font-weight: 600; + table-layout: fixed; + + thead, + td { + border: 1px solid ${(props) => props.theme.table.border}; + } + + thead { + color: ${(props) => props.theme.table.thead.color}; + font-size: 0.8125rem; + user-select: none; + } + td { + padding: 6px 10px; + + &:nth-child(1) { + width: 30%; + } + + &:nth-child(3) { + width: 70px; + } + } + } + + .btn-add-header { + font-size: 0.8125rem; + } + + input[type='text'] { + width: 100%; + border: solid 1px transparent; + outline: none !important; + background-color: inherit; + + &:focus { + outline: none !important; + border: solid 1px transparent; + } + } + + input[type='checkbox'] { + cursor: pointer; + position: relative; + top: 1px; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/FolderSettings/Headers/index.js b/packages/bruno-app/src/components/FolderSettings/Headers/index.js new file mode 100644 index 000000000..a944285e6 --- /dev/null +++ b/packages/bruno-app/src/components/FolderSettings/Headers/index.js @@ -0,0 +1,150 @@ +import React from 'react'; +import get from 'lodash/get'; +import cloneDeep from 'lodash/cloneDeep'; +import { IconTrash } from '@tabler/icons'; +import { useDispatch } from 'react-redux'; +import { useTheme } from 'providers/Theme'; +import { addFolderHeader, updateFolderHeader, deleteFolderHeader } from 'providers/ReduxStore/slices/collections'; +import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions'; +import SingleLineEditor from 'components/SingleLineEditor'; +import StyledWrapper from './StyledWrapper'; +import { headers as StandardHTTPHeaders } from 'know-your-http-well'; +const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header); + +const Headers = ({ collection, folder }) => { + const dispatch = useDispatch(); + const { storedTheme } = useTheme(); + const headers = get(folder, 'root.request.headers', []); + + const addHeader = () => { + dispatch( + addFolderHeader({ + collectionUid: collection.uid, + folderUid: folder.uid + }) + ); + }; + + const handleSave = () => dispatch(saveFolderRoot(collection.uid, folder.uid)); + const handleHeaderValueChange = (e, _header, type) => { + const header = cloneDeep(_header); + switch (type) { + case 'name': { + header.name = e.target.value; + break; + } + case 'value': { + header.value = e.target.value; + break; + } + case 'enabled': { + header.enabled = e.target.checked; + break; + } + } + dispatch( + updateFolderHeader({ + header: header, + collectionUid: collection.uid, + folderUid: folder.uid + }) + ); + }; + + const handleRemoveHeader = (header) => { + dispatch( + deleteFolderHeader({ + headerUid: header.uid, + collectionUid: collection.uid, + folderUid: folder.uid + }) + ); + }; + + return ( + + + + + + + + + + + {headers && headers.length + ? headers.map((header) => { + return ( + + + + + + ); + }) + : null} + +
NameValue
+ + handleHeaderValueChange( + { + target: { + value: newValue + } + }, + header, + 'name' + ) + } + autocomplete={headerAutoCompleteList} + collection={collection} + /> + + + handleHeaderValueChange( + { + target: { + value: newValue + } + }, + header, + 'value' + ) + } + collection={collection} + /> + +
+ handleHeaderValueChange(e, header, 'enabled')} + /> + +
+
+ + +
+ +
+
+ ); +}; +export default Headers; diff --git a/packages/bruno-app/src/components/FolderSettings/index.js b/packages/bruno-app/src/components/FolderSettings/index.js new file mode 100644 index 000000000..b1d6ee249 --- /dev/null +++ b/packages/bruno-app/src/components/FolderSettings/index.js @@ -0,0 +1,52 @@ +import React from 'react'; +import classnames from 'classnames'; +import { updateSettingsSelectedTab } from 'providers/ReduxStore/slices/collections'; +import { useDispatch } from 'react-redux'; +import Headers from './Headers'; + +const FolderSettings = ({ collection, folder }) => { + const dispatch = useDispatch(); + const tab = folder?.settingsSelectedTab || 'headers'; + const setTab = (tab) => { + dispatch( + updateSettingsSelectedTab({ + collectionUid: folder.collectionUid, + folderUid: folder.uid, + tab + }) + ); + }; + + const getTabPanel = (tab) => { + switch (tab) { + case 'headers': { + return ; + } + // TODO: Add auth + } + }; + + const getTabClassname = (tabName) => { + return classnames(`tab select-none ${tabName}`, { + active: tabName === tab + }); + }; + + return ( +
+
+
setTab('headers')}> + Headers +
+ {/*
setTab('auth')}> + Auth +
*/} +
+
+ {getTabPanel(tab)} +
+
+ ); +}; + +export default FolderSettings; diff --git a/packages/bruno-app/src/components/RequestTabPanel/index.js b/packages/bruno-app/src/components/RequestTabPanel/index.js index f719eb0f3..2fd253f4b 100644 --- a/packages/bruno-app/src/components/RequestTabPanel/index.js +++ b/packages/bruno-app/src/components/RequestTabPanel/index.js @@ -18,6 +18,7 @@ import CollectionSettings from 'components/CollectionSettings'; import { DocExplorer } from '@usebruno/graphql-docs'; import StyledWrapper from './StyledWrapper'; +import FolderSettings from 'components/FolderSettings'; const MIN_LEFT_PANE_WIDTH = 300; const MIN_RIGHT_PANE_WIDTH = 350; @@ -131,6 +132,10 @@ const RequestTabPanel = () => { if (focusedTab.type === 'collection-settings') { return ; } + if (focusedTab.type === 'folder-settings') { + const folder = findItemInCollection(collection, focusedTab.folderUid); + return ; + } const item = findItemInCollection(collection, activeTabUid); if (!item || !item.uid) { diff --git a/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js b/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js index ba77d47c9..dcfacb260 100644 --- a/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js +++ b/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js @@ -44,7 +44,7 @@ const CollectionToolBar = ({ collection }) => {
- {collection.name} + {collection?.name || 'Folder'}
diff --git a/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js b/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js index aebc3db75..7510af30b 100644 --- a/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js +++ b/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js @@ -1,22 +1,30 @@ import React from 'react'; import { IconVariable, IconSettings, IconRun } from '@tabler/icons'; -const SpecialTab = ({ handleCloseClick, type }) => { - const getTabInfo = (type) => { +const SpecialTab = ({ handleCloseClick, type, folderName }) => { + const getTabInfo = (type, folderName) => { switch (type) { case 'collection-settings': { return ( <> - Collection + Collection ); } + case 'folder-settings': { + return ( +
+ + {folderName || 'Folder'} +
+ ); + } case 'variables': { return ( <> - Variables + Variables ); } @@ -24,7 +32,7 @@ const SpecialTab = ({ handleCloseClick, type }) => { return ( <> - Runner + Runner ); } @@ -33,7 +41,7 @@ const SpecialTab = ({ handleCloseClick, type }) => { return ( <> -
{getTabInfo(type)}
+
{getTabInfo(type, folderName)}
handleCloseClick(e)}> { +const RequestTab = ({ tab, collection, folderUid }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); const [showConfirmClose, setShowConfirmClose] = useState(false); @@ -80,11 +80,11 @@ const RequestTab = ({ tab, collection }) => { return color; }; - - if (['collection-settings', 'variables', 'collection-runner'].includes(tab.type)) { + const folder = folderUid ? findItemInCollection(collection, folderUid) : null; + if (['collection-settings', 'folder-settings', 'variables', 'collection-runner'].includes(tab.type)) { return ( - + ); } diff --git a/packages/bruno-app/src/components/RequestTabs/index.js b/packages/bruno-app/src/components/RequestTabs/index.js index 3063771e8..fbafb55cf 100644 --- a/packages/bruno-app/src/components/RequestTabs/index.js +++ b/packages/bruno-app/src/components/RequestTabs/index.js @@ -75,7 +75,6 @@ const RequestTabs = () => { 'has-chevrons': showChevrons }); }; - // Todo: Must support ephemeral requests return ( @@ -111,7 +110,7 @@ const RequestTabs = () => { role="tab" onClick={() => handleClick(tab)} > - + ); }) diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js index bf84b8289..9fce06eec 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js @@ -24,6 +24,7 @@ import { hideHomePage } from 'providers/ReduxStore/slices/app'; import toast from 'react-hot-toast'; import StyledWrapper from './StyledWrapper'; import NetworkError from 'components/ResponsePane/NetworkError/index'; +import { uuid } from 'utils/common'; const CollectionItem = ({ item, collection, searchText }) => { const tabs = useSelector((state) => state.tabs.tabs); @@ -188,6 +189,16 @@ const CollectionItem = ({ item, collection, searchText }) => { toast.error('URL is required'); } }; + const viewFolderSettings = () => { + dispatch( + addTab({ + uid: uuid(), + collectionUid: collection.uid, + folderUid: item.uid, + type: 'folder-settings' + }) + ); + }; const requestItems = sortRequestItems(filter(item.items, (i) => isItemARequest(i))); const folderItems = sortFolderItems(filter(item.items, (i) => isItemAFolder(i))); @@ -345,6 +356,17 @@ const CollectionItem = ({ item, collection, searchText }) => { > Delete
+ {isFolder && ( +
{ + dropdownTippyRef.current.hide(); + viewFolderSettings(); + }} + > + Settings +
+ )}
diff --git a/packages/bruno-app/src/providers/App/useIpcEvents.js b/packages/bruno-app/src/providers/App/useIpcEvents.js index 467a8582c..133185d9a 100644 --- a/packages/bruno-app/src/providers/App/useIpcEvents.js +++ b/packages/bruno-app/src/providers/App/useIpcEvents.js @@ -11,6 +11,7 @@ import { collectionUnlinkFileEvent, processEnvUpdateEvent, runFolderEvent, + folderAddFileEvent, runRequestEvent, scriptEnvironmentUpdateEvent } from 'providers/ReduxStore/slices/collections'; @@ -48,6 +49,13 @@ const useIpcEvents = () => { }) ); } + if (type === 'addFileDir') { + dispatch( + folderAddFileEvent({ + file: val + }) + ); + } if (type === 'change') { dispatch( collectionChangeFileEvent({ diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index 526b43a1e..3c3d1786e 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -14,10 +14,11 @@ import { findParentItemInCollection, getItemsToResequence, isItemAFolder, + refreshUidsInItem, + findItemInCollectionByPathname, isItemARequest, moveCollectionItem, moveCollectionItemToRootOfCollection, - refreshUidsInItem, transformRequestToSaveToFilesystem } from 'utils/collections'; import { uuid, waitForNextTick } from 'utils/common'; @@ -143,7 +144,65 @@ export const saveCollectionRoot = (collectionUid) => (dispatch, getState) => { }); }; -export const sendCollectionOauth2Request = (collectionUid) => (dispatch, getState) => { +export const saveFolderRoot = (collectionUid, folderUid) => (dispatch, getState) => { + const state = getState(); + const collection = findCollectionByUid(state.collections.collections, collectionUid); + const folder = findItemInCollection(collection, folderUid); + + return new Promise((resolve, reject) => { + if (!collection) { + return reject(new Error('Collection not found')); + } + + if (!folder) { + return reject(new Error('Folder not found')); + } + + const { ipcRenderer } = window; + + ipcRenderer + .invoke('renderer:save-folder-root', folder.pathname, folder.root) + .then(() => toast.success('Folder Settings saved successfully')) + .then(resolve) + .catch((err) => { + toast.error('Failed to save folder settings!'); + reject(err); + }); + }); +}; + +export const retrieveDirectoriesBetween = (pathname, parameter, filename) => { + const parameterIndex = pathname.indexOf(parameter); + const filenameIndex = pathname.indexOf(filename); + if (parameterIndex === -1 || filenameIndex === -1 || filenameIndex < parameterIndex) { + return []; + } + const directories = pathname + .substring(parameterIndex + parameter.length, filenameIndex) + .split('/') + .filter((directory) => directory.trim() !== ''); + const reconstructedPaths = []; + let currentPath = pathname.substring(0, parameterIndex + parameter.length); + for (const directory of directories) { + currentPath += `/${directory}`; + reconstructedPaths.push(currentPath); + } + return reconstructedPaths; +}; + +export const mergeRequests = (parentRequest, childRequest) => { + return _.mergeWith({}, parentRequest, childRequest, customizer); +}; + +function customizer(objValue, srcValue, key) { + const exceptions = ['headers', 'params', 'vars']; + if (exceptions.includes(key) && _.isArray(objValue) && _.isArray(srcValue)) { + return _.unionBy(srcValue, objValue, 'name'); + } + return undefined; +} + +export const sendCollectionOauth2Request = (collectionUid, itemUid) => (dispatch, getState) => { const state = getState(); const collection = findCollectionByUid(state.collections.collections, collectionUid); @@ -156,7 +215,10 @@ export const sendCollectionOauth2Request = (collectionUid) => (dispatch, getStat const environment = findEnvironmentInCollection(collectionCopy, collection.activeEnvironmentUid); - _sendCollectionOauth2Request(collection, environment, collectionCopy.collectionVariables) + const externalSecrets = getExternalCollectionSecretsForActiveEnvironment({ collection }); + const secretVariables = getFormattedCollectionSecretVariables({ externalSecrets }); + + _sendCollectionOauth2Request(collection, environment, collectionCopy.collectionVariables, itemUid, secretVariables) .then((response) => { if (response?.data?.error) { toast.error(response?.data?.error); @@ -184,9 +246,26 @@ export const sendRequest = (item, collectionUid) => (dispatch, getState) => { const itemCopy = cloneDeep(item || {}); const collectionCopy = cloneDeep(collection); - const environment = findEnvironmentInCollection(collectionCopy, collection.activeEnvironmentUid); + const environment = findEnvironmentInCollection(collectionCopy, collectionCopy.activeEnvironmentUid); + const itemTree = retrieveDirectoriesBetween(itemCopy.pathname, collectionCopy.name, itemCopy.filename); - sendNetworkRequest(itemCopy, collection, environment, collectionCopy.collectionVariables) + const folderDatas = itemTree.reduce((acc, currentPath) => { + const folder = findItemInCollectionByPathname(collectionCopy, currentPath); + if (folder) { + acc = mergeRequests(acc, folder.root.request); + } + return acc; + }, {}); + const mergeParams = mergeRequests(collectionCopy.root.request, folderDatas); + // merge collection and folder settings with request + const mergedCollection = { + ...collectionCopy, + root: { + ...collectionCopy.root, + request: mergeParams + } + }; + sendNetworkRequest(itemCopy, mergedCollection, environment, collectionCopy.collectionVariables) .then((response) => { return dispatch( responseReceived({ diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index 2a851c238..64cf4f654 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -89,7 +89,7 @@ export const collectionsSlice = createSlice({ } }, updateSettingsSelectedTab: (state, action) => { - const { collectionUid, tab } = action.payload; + const { collectionUid, folderUid, tab } = action.payload; const collection = findCollectionByUid(state.collections, collectionUid); @@ -1114,6 +1114,44 @@ export const collectionsSlice = createSlice({ set(collection, 'root.docs', action.payload.docs); } }, + addFolderHeader: (state, action) => { + const collection = findCollectionByUid(state.collections, action.payload.collectionUid); + const folder = collection ? findItemInCollection(collection, action.payload.folderUid) : null; + if (folder) { + const headers = get(folder, 'root.request.headers', []); + headers.push({ + uid: uuid(), + name: '', + value: '', + description: '', + enabled: true + }); + set(folder, 'root.request.headers', headers); + } + }, + updateFolderHeader: (state, action) => { + const collection = findCollectionByUid(state.collections, action.payload.collectionUid); + const folder = collection ? findItemInCollection(collection, action.payload.folderUid) : null; + if (folder) { + const headers = get(folder, 'root.request.headers', []); + const header = find(headers, (h) => h.uid === action.payload.header.uid); + if (header) { + header.name = action.payload.header.name; + header.value = action.payload.header.value; + header.description = action.payload.header.description; + header.enabled = action.payload.header.enabled; + } + } + }, + deleteFolderHeader: (state, action) => { + const collection = findCollectionByUid(state.collections, action.payload.collectionUid); + const folder = collection ? findItemInCollection(collection, action.payload.folderUid) : null; + if (folder) { + let headers = get(folder, 'root.request.headers', []); + headers = filter(headers, (h) => h.uid !== action.payload.headerUid); + set(folder, 'root.request.headers', headers); + } + }, addCollectionHeader: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload.collectionUid); @@ -1152,11 +1190,22 @@ export const collectionsSlice = createSlice({ set(collection, 'root.request.headers', headers); } }, + folderAddFileEvent: (state, action) => { + const file = action.payload.file; + const isFolderRoot = file.meta.folderRoot ? true : false; + const collection = findCollectionByUid(state.collections, file.meta.collectionUid); + const folder = findItemInCollectionByPathname(collection, file.meta.pathname); + if (isFolderRoot) { + if (folder) { + folder.root = file.data; + } + return; + } + }, collectionAddFileEvent: (state, action) => { const file = action.payload.file; const isCollectionRoot = file.meta.collectionRoot ? true : false; const collection = findCollectionByUid(state.collections, file.meta.collectionUid); - if (isCollectionRoot) { if (collection) { collection.root = file.data; @@ -1187,7 +1236,7 @@ export const collectionsSlice = createSlice({ currentSubItems = childItem.items; } - if (!currentSubItems.find((f) => f.name === file.meta.name)) { + if (file.meta.name != 'folder.bru' && !currentSubItems.find((f) => f.name === file.meta.name)) { // this happens when you rename a file // the add event might get triggered first, before the unlink event // this results in duplicate uids causing react renderer to go mad @@ -1521,6 +1570,9 @@ export const { addVar, updateVar, deleteVar, + addFolderHeader, + updateFolderHeader, + deleteFolderHeader, addCollectionHeader, updateCollectionHeader, deleteCollectionHeader, @@ -1537,6 +1589,7 @@ export const { collectionUnlinkDirectoryEvent, collectionAddEnvFileEvent, collectionRenamedEvent, + folderAddFileEvent, resetRunResults, runRequestEvent, runFolderEvent, diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js b/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js index 74c503dad..b64a71fad 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js @@ -38,7 +38,8 @@ export const tabsSlice = createSlice({ requestPaneWidth: null, requestPaneTab: action.payload.requestPaneTab || 'params', responsePaneTab: 'response', - type: action.payload.type || 'request' + type: action.payload.type || 'request', + ...(action.payload.folderUid ? { folderUid: action.payload.folderUid } : {}) }); state.activeTabUid = action.payload.uid; }, diff --git a/packages/bruno-cli/src/commands/run.js b/packages/bruno-cli/src/commands/run.js index 35192b128..e51daa552 100644 --- a/packages/bruno-cli/src/commands/run.js +++ b/packages/bruno-cli/src/commands/run.js @@ -179,6 +179,17 @@ const getCollectionRoot = (dir) => { return collectionBruToJson(content); }; +const getFolderRoot = (dir) => { + const folderRootPath = path.join(dir, 'folder.bru'); + const exists = fs.existsSync(folderRootPath); + if (!exists) { + return {}; + } + + const content = fs.readFileSync(folderRootPath, 'utf8'); + return collectionBruToJson(content); +}; + const builder = async (yargs) => { yargs .option('r', { diff --git a/packages/bruno-electron/src/app/watcher.js b/packages/bruno-electron/src/app/watcher.js index 441bba3b2..6b7c6c31b 100644 --- a/packages/bruno-electron/src/app/watcher.js +++ b/packages/bruno-electron/src/app/watcher.js @@ -40,10 +40,42 @@ const isBruEnvironmentConfig = (pathname, collectionPath) => { const isCollectionRootBruFile = (pathname, collectionPath) => { const dirname = path.dirname(pathname); const basename = path.basename(pathname); - return dirname === collectionPath && basename === 'collection.bru'; }; +const isFolderRootBruFile = (pathname, folderPath) => { + const dirname = path.dirname(pathname); + const basename = path.basename(pathname); + return dirname === folderPath && basename === 'folder.bru'; +}; + +const scanDirectory = (directoryPath, callback) => { + fs.readdir(directoryPath, (err, files) => { + if (err) { + console.error(`Error reading directory ${directoryPath}: ${err}`); + return; + } + if (files.includes('folder.bru')) { + callback(directoryPath); + } + // Iterate through each file/folder in the directory + files.forEach((file) => { + const filePath = path.join(directoryPath, file); + // Check if it's a directory + fs.stat(filePath, (err, stats) => { + if (err) { + console.error(`Error statting ${filePath}: ${err}`); + return; + } + // If it's a directory, recursively scan it + if (stats.isDirectory()) { + scanDirectory(filePath, callback); + } + }); + }); + }); +}; + const hydrateRequestWithUuid = (request, pathname) => { request.uid = getRequestUid(pathname); @@ -225,12 +257,27 @@ const add = async (win, pathname, collectionUid, collectionPath) => { collectionRoot: true } }; - + const folderCallback = (filePath) => { + const bruContent = fs.readFileSync(`${filePath}/folder.bru`, 'utf8'); + if (bruContent) { + const folder = { + meta: { + collectionUid, + pathname: filePath, + name: path.basename(filePath), + folderRoot: true + } + }; + folder.data = collectionBruToJson(bruContent); + hydrateBruCollectionFileWithUuid(folder.data); + win.webContents.send('main:collection-tree-updated', 'addFileDir', folder); + } + }; try { + scanDirectory(collectionPath, folderCallback); let bruContent = fs.readFileSync(pathname, 'utf8'); file.data = collectionBruToJson(bruContent); - hydrateBruCollectionFileWithUuid(file.data); win.webContents.send('main:collection-tree-updated', 'addFile', file); return; @@ -334,7 +381,6 @@ const change = async (win, pathname, collectionUid, collectionPath) => { let bruContent = fs.readFileSync(pathname, 'utf8'); file.data = collectionBruToJson(bruContent); - hydrateBruCollectionFileWithUuid(file.data); win.webContents.send('main:collection-tree-updated', 'change', file); return; diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index 5f8b63c3b..c7901edb2 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -152,6 +152,16 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection } }); + ipcMain.handle('renderer:save-folder-root', async (event, folderPathname, folderRoot) => { + try { + const folderBruFilePath = path.join(folderPathname, 'folder.bru'); + + const content = jsonToBru(folderRoot); + await writeFile(folderBruFilePath, content); + } catch (error) { + return Promise.reject(error); + } + }); ipcMain.handle('renderer:save-collection-root', async (event, collectionPathname, collectionRoot) => { try { const collectionBruFilePath = path.join(collectionPathname, 'collection.bru');