From 49040985bdbc7deba2e9547bc9bad6274ff599b8 Mon Sep 17 00:00:00 2001 From: lohit Date: Thu, 22 Aug 2024 20:34:45 +0530 Subject: [PATCH 01/31] Update package.json --- packages/bruno-cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bruno-cli/package.json b/packages/bruno-cli/package.json index 7dd157e4ae..e856846c85 100644 --- a/packages/bruno-cli/package.json +++ b/packages/bruno-cli/package.json @@ -14,7 +14,7 @@ "url": "git+https://github.com/usebruno/bruno.git" }, "scripts": { - "test": "node --experimental-vm-modules $(npx --no-install which jest)" + "test": "node --experimental-vm-modules $(npx which jest)" }, "files": [ "src", From 97a2baaa097cae925eeeadb15f0bc2c044d4f06d Mon Sep 17 00:00:00 2001 From: lohit Date: Thu, 22 Aug 2024 20:35:05 +0530 Subject: [PATCH 02/31] Update package.json --- packages/bruno-electron/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json index 4ce7e565ec..818d66bee4 100644 --- a/packages/bruno-electron/package.json +++ b/packages/bruno-electron/package.json @@ -16,7 +16,7 @@ "dist:rpm": "electron-builder --linux rpm --config electron-builder-config.js", "dist:snap": "electron-builder --linux snap --config electron-builder-config.js", "pack": "electron-builder --dir", - "test": "node --experimental-vm-modules $(npx --no-install which jest)" + "test": "node --experimental-vm-modules $(npx which jest)" }, "jest": { "modulePaths": ["node_modules"] From b1ca2ed872431e1cff0ef1d7672762c480efc9c4 Mon Sep 17 00:00:00 2001 From: lohit Date: Thu, 22 Aug 2024 20:35:30 +0530 Subject: [PATCH 03/31] Update package.json --- packages/bruno-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bruno-js/package.json b/packages/bruno-js/package.json index 41a8b7bcb1..fdd8bfbd9a 100644 --- a/packages/bruno-js/package.json +++ b/packages/bruno-js/package.json @@ -11,7 +11,7 @@ "@n8n/vm2": "^3.9.23" }, "scripts": { - "test": "node --experimental-vm-modules $(npx --no-install which jest) --testPathIgnorePatterns test.js", + "test": "node --experimental-vm-modules $(npx which jest) --testPathIgnorePatterns test.js", "sandbox:bundle-libraries": "node ./src/sandbox/bundle-libraries.js" }, "dependencies": { From 9d66d22c4fd067fda5e6ea23355295232f3db36f Mon Sep 17 00:00:00 2001 From: lohit Date: Thu, 22 Aug 2024 20:37:53 +0530 Subject: [PATCH 04/31] Update package.json --- packages/bruno-app/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json index 0a793779e2..bb3bf8ee0b 100644 --- a/packages/bruno-app/package.json +++ b/packages/bruno-app/package.json @@ -7,7 +7,7 @@ "build": "next build && next export", "start": "next start", "lint": "next lint", - "test": "jest", + "test": "npx jest@latest", "test:prettier": "prettier --check \"./src/**/*.{js,jsx,json,ts,tsx}\"", "prettier": "prettier --write \"./src/**/*.{js,jsx,json,ts,tsx}\"" }, From f9686a983616590351dc32f35fc0280ac4a0f1b6 Mon Sep 17 00:00:00 2001 From: lohxt1 Date: Tue, 3 Sep 2024 10:35:04 +0530 Subject: [PATCH 05/31] revert jest command --- packages/bruno-app/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json index 8b6aa22719..7f1dfe7c54 100644 --- a/packages/bruno-app/package.json +++ b/packages/bruno-app/package.json @@ -7,7 +7,7 @@ "build": "next build && next export", "start": "next start", "lint": "next lint", - "test": "npx jest@latest", + "test": "jest", "test:prettier": "prettier --check \"./src/**/*.{js,jsx,json,ts,tsx}\"", "prettier": "prettier --write \"./src/**/*.{js,jsx,json,ts,tsx}\"" }, From 51f98c78d02c38a646de79a038d3a2ba3f001ece Mon Sep 17 00:00:00 2001 From: lohxt1 Date: Fri, 13 Sep 2024 17:47:24 +0530 Subject: [PATCH 06/31] draft: request/folder name seperate from file/directory name --- .../src/components/Modal/StyledWrapper.js | 4 + .../bruno-app/src/components/Modal/index.js | 48 ++++--- .../CloneCollectionItem/index.js | 80 ++++++++++- .../RenameCollectionItem/StyledWrapper.js | 9 ++ .../RenameCollectionItem/index.js | 130 ++++++++++++++---- .../src/components/Sidebar/NewFolder/index.js | 69 +++++++++- .../Sidebar/NewRequest/StyledWrapper.js | 4 + .../components/Sidebar/NewRequest/index.js | 73 +++++++++- .../ReduxStore/slices/collections/actions.js | 78 ++++++++--- .../ReduxStore/slices/collections/index.js | 26 +++- .../bruno-app/src/utils/importers/common.js | 1 - packages/bruno-electron/src/app/watcher.js | 24 ++++ packages/bruno-electron/src/ipc/collection.js | 67 ++++++++- 13 files changed, 520 insertions(+), 93 deletions(-) create mode 100644 packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/StyledWrapper.js diff --git a/packages/bruno-app/src/components/Modal/StyledWrapper.js b/packages/bruno-app/src/components/Modal/StyledWrapper.js index f26d811f54..ad58c86849 100644 --- a/packages/bruno-app/src/components/Modal/StyledWrapper.js +++ b/packages/bruno-app/src/components/Modal/StyledWrapper.js @@ -43,21 +43,25 @@ const Wrapper = styled.div` &.modal-sm { min-width: 300px; + width: 300px; max-width: 500px; } &.modal-md { min-width: 500px; + width: 500px; max-width: 800px; } &.modal-lg { min-width: 800px; + width: 800px; max-width: 1140px; } &.modal-xl { min-width: 1140px; + width: 1140px; max-width: calc(100% - 30px); } diff --git a/packages/bruno-app/src/components/Modal/index.js b/packages/bruno-app/src/components/Modal/index.js index b83549aac7..0a8ef550ac 100644 --- a/packages/bruno-app/src/components/Modal/index.js +++ b/packages/bruno-app/src/components/Modal/index.js @@ -21,7 +21,8 @@ const ModalFooter = ({ handleCancel, confirmDisabled, hideCancel, - hideFooter + hideFooter, + customFooter }) => { confirmText = confirmText || 'Save'; cancelText = cancelText || 'Cancel'; @@ -31,22 +32,27 @@ const ModalFooter = ({ } return ( -
- - - - - - +
+
+ {customFooter} +
+
+ + + + + + +
); }; @@ -67,7 +73,8 @@ const Modal = ({ disableCloseOnOutsideClick, disableEscapeKey, onClick, - closeModalFadeTimeout = 500 + closeModalFadeTimeout = 500, + customFooter }) => { const [isClosing, setIsClosing] = useState(false); const escFunction = (event) => { @@ -116,6 +123,7 @@ const Modal = ({ confirmDisabled={confirmDisabled} hideCancel={hideCancel} hideFooter={hideFooter} + customFooter={customFooter} />
@@ -126,8 +134,8 @@ const Modal = ({ disableCloseOnOutsideClick ? null : () => { - closeModal({ type: 'backdrop' }); - } + closeModal({ type: 'backdrop' }); + } } /> diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CloneCollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CloneCollectionItem/index.js index 0dd96e1970..101828e15c 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CloneCollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CloneCollectionItem/index.js @@ -1,4 +1,4 @@ -import React, { useRef, useEffect } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import toast from 'react-hot-toast'; import { useFormik } from 'formik'; import * as Yup from 'yup'; @@ -6,24 +6,52 @@ import Modal from 'components/Modal'; import { useDispatch } from 'react-redux'; import { isItemAFolder } from 'utils/tabs'; import { cloneItem } from 'providers/ReduxStore/slices/collections/actions'; +import { normalizeFileName } from 'utils/common/index'; +import { IconEdit, IconFile } from '@tabler/icons'; +import * as path from 'path'; const CloneCollectionItem = ({ collection, item, onClose }) => { const dispatch = useDispatch(); const isFolder = isItemAFolder(item); const inputRef = useRef(); + const [isEditingFilename, toggleEditingFilename] = useState(false); + const itemName = item?.name; + const itemType = item?.type; + const itemFilename = item?.filename ? path.parse(item?.filename).name : ''; const formik = useFormik({ enableReinitialize: true, initialValues: { - name: item.name + name: itemName, + filename: itemFilename }, validationSchema: Yup.object({ name: Yup.string() + .min(1, 'must be at least 1 character') + .max(50, 'must be 50 characters or less') + .required('name is required'), + filename: Yup.string() .min(1, 'must be at least 1 character') .max(50, 'must be 50 characters or less') .required('name is required') + .test({ + name: 'filename', + message: `The file names - collection and folder is reserved in bruno`, + test: (value) => { + const trimmedValue = value ? value.trim().toLowerCase() : ''; + return !['collection', 'folder'].includes(trimmedValue); + } + }) + .test({ + name: 'filename', + message: `Invalid file name`, + test: (value) => { + const trimmedValue = value ? value.trim().toLowerCase() : ''; + return (normalizeFileName(trimmedValue) === trimmedValue) + } + }) }), onSubmit: (values) => { - dispatch(cloneItem(values.name, item.uid, collection.uid)) + dispatch(cloneItem(values.name, values.filename, item.uid, collection.uid)) .then(() => { onClose(); }) @@ -41,13 +69,23 @@ const CloneCollectionItem = ({ collection, item, onClose }) => { const onSubmit = () => formik.handleSubmit(); + const filename = formik.values.filename; + const filenameFooter = !isEditingFilename && filename ? +
+

{filename}.bru

+ toggleEditingFilename(v => !v)} /> +
+ : + <> + return (
@@ -65,11 +103,43 @@ const CloneCollectionItem = ({ collection, item, onClose }) => { autoCorrect="off" autoCapitalize="off" spellCheck="false" - onChange={formik.handleChange} + onChange={e => { + formik.setFieldValue('name', e.target.value); + !isEditingFilename && formik.setFieldValue('filename', normalizeFileName(e.target.value)); + }} value={formik.values.name || ''} /> {formik.touched.name && formik.errors.name ?
{formik.errors.name}
: null}
+ { + isEditingFilename ? +
+ +
+ + {itemType !== 'folder' && .bru} +
+ {formik.touched.filename && formik.errors.filename ? ( +
{formik.errors.filename}
+ ) : null} +
+ : + <> + }
); diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/StyledWrapper.js new file mode 100644 index 0000000000..cb44f0e503 --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/StyledWrapper.js @@ -0,0 +1,9 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + .file-extension { + color: ${(props) => props.theme.colors.text.darkOrange}; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js index 74b25de478..7b9acfa00e 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js @@ -1,25 +1,54 @@ -import React, { useRef, useEffect } from 'react'; +import React, { useRef, useEffect, useState } from 'react'; import { useFormik } from 'formik'; import * as Yup from 'yup'; import Modal from 'components/Modal'; import { useDispatch } from 'react-redux'; import { isItemAFolder } from 'utils/tabs'; -import { renameItem, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; +import { renameItem, renameItemName, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; +import { normalizeFileName } from 'utils/common/index'; +import path from 'path'; +import { IconEdit, IconFile } from '@tabler/icons'; +import StyledWrapper from './StyledWrapper'; const RenameCollectionItem = ({ collection, item, onClose }) => { const dispatch = useDispatch(); const isFolder = isItemAFolder(item); const inputRef = useRef(); + const [isEditingFilename, toggleEditingFilename] = useState(false); + const itemName = item?.name; + const itemType = item?.type; + const itemFilename = item?.filename ? path.parse(item?.filename).name : ''; const formik = useFormik({ enableReinitialize: true, initialValues: { - name: item.name + name: itemName, + filename: itemFilename }, validationSchema: Yup.object({ name: Yup.string() + .min(1, 'must be at least 1 character') + .max(50, 'must be 50 characters or less') + .required('name is required'), + filename: Yup.string() .min(1, 'must be at least 1 character') .max(50, 'must be 50 characters or less') .required('name is required') + .test({ + name: 'filename', + message: `The file names - collection and folder is reserved in bruno`, + test: (value) => { + const trimmedValue = value ? value.trim().toLowerCase() : ''; + return !['collection', 'folder'].includes(trimmedValue); + } + }) + .test({ + name: 'filename', + message: `Invalid file name`, + test: (value) => { + const trimmedValue = value ? value.trim().toLowerCase() : ''; + return (normalizeFileName(trimmedValue) === trimmedValue) + } + }) }), onSubmit: async (values) => { // if there is unsaved changes in the request, @@ -27,7 +56,12 @@ const RenameCollectionItem = ({ collection, item, onClose }) => { if (!isFolder && item.draft) { await dispatch(saveRequest(item.uid, collection.uid, true)); } - dispatch(renameItem(values.name, item.uid, collection.uid)); + if (values.name !== itemName) { + await dispatch(renameItemName(values.name, item.uid, collection.uid)) + } + if (values.filename !== itemFilename) { + await dispatch(renameItem(values.name, values.filename, item.uid, collection.uid)); + } onClose(); } }); @@ -40,35 +74,79 @@ const RenameCollectionItem = ({ collection, item, onClose }) => { const onSubmit = () => formik.handleSubmit(); + const filename = formik.values.filename; + const filenameFooter = !isEditingFilename && filename ? +
+

{filename}.bru

+ toggleEditingFilename(v => !v)} /> +
+ : + <> + return ( -
-
- - - {formik.touched.name && formik.errors.name ?
{formik.errors.name}
: null} -
-
+ +
+
+ + { + formik.setFieldValue('name', e.target.value); + !isEditingFilename && formik.setFieldValue('filename', normalizeFileName(e.target.value)); + }} + value={formik.values.name || ''} + /> + {formik.touched.name && formik.errors.name ?
{formik.errors.name}
: null} +
+ { + isEditingFilename ? +
+ +
+ + {itemType !== 'folder' && .bru} +
+ {formik.touched.filename && formik.errors.filename ? ( +
{formik.errors.filename}
+ ) : null} +
+ : + <> + } +
+
); }; diff --git a/packages/bruno-app/src/components/Sidebar/NewFolder/index.js b/packages/bruno-app/src/components/Sidebar/NewFolder/index.js index 934a3bd29e..9eca52b4cb 100644 --- a/packages/bruno-app/src/components/Sidebar/NewFolder/index.js +++ b/packages/bruno-app/src/components/Sidebar/NewFolder/index.js @@ -1,14 +1,19 @@ -import React, { useRef, useEffect } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import { useFormik } from 'formik'; import toast from 'react-hot-toast'; import * as Yup from 'yup'; import Modal from 'components/Modal'; import { useDispatch } from 'react-redux'; import { newFolder } from 'providers/ReduxStore/slices/collections/actions'; +import { normalizeFileName } from 'utils/common/index'; +import { IconEdit } from '@tabler/icons'; const NewFolder = ({ collection, item, onClose }) => { const dispatch = useDispatch(); const inputRef = useRef(); + + const [isEditingFilename, toggleEditingFilename] = useState(false); + const formik = useFormik({ enableReinitialize: true, initialValues: { @@ -18,7 +23,11 @@ const NewFolder = ({ collection, item, onClose }) => { folderName: Yup.string() .trim() .min(1, 'must be at least 1 character') - .required('name is required') + .required('name is required'), + directoryName: Yup.string() + .trim() + .min(1, 'must be at least 1 character') + .required('foldername is required') .test({ name: 'folderName', message: 'The folder name "environments" at the root of the collection is reserved in bruno', @@ -29,9 +38,17 @@ const NewFolder = ({ collection, item, onClose }) => { return value && !value.trim().toLowerCase().includes('environments'); } }) + .test({ + name: 'foldername', + message: `Invalid folder name`, + test: (value) => { + const trimmedValue = value ? value.trim().toLowerCase() : ''; + return (normalizeFileName(trimmedValue) === trimmedValue) + } + }), }), onSubmit: (values) => { - dispatch(newFolder(values.folderName, collection.uid, item ? item.uid : null)) + dispatch(newFolder(values.folderName, values.directoryName, collection.uid, item ? item.uid : null)) .then(() => onClose()) .catch((err) => toast.error(err ? err.message : 'An error occurred while adding the folder')); } @@ -45,8 +62,18 @@ const NewFolder = ({ collection, item, onClose }) => { const onSubmit = () => formik.handleSubmit(); + + const directoryName = formik.values.directoryName; + const directoryNameFooter = !isEditingFilename && directoryName ? +
+

{directoryName}.bru

+ toggleEditingFilename(v => !v)} /> +
+ : + <> + return ( - +
+ { + isEditingFilename ? +
+ +
+ + .bru +
+ {formik.touched.directoryName && formik.errors.directoryName ? ( +
{formik.errors.directoryName}
+ ) : null} +
+ : + <> + }
); diff --git a/packages/bruno-app/src/components/Sidebar/NewRequest/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/NewRequest/StyledWrapper.js index 9845bd2efe..80499b8e58 100644 --- a/packages/bruno-app/src/components/Sidebar/NewRequest/StyledWrapper.js +++ b/packages/bruno-app/src/components/Sidebar/NewRequest/StyledWrapper.js @@ -39,6 +39,10 @@ const StyledWrapper = styled.div` textarea.curl-command { min-height: 150px; } + + .file-extension { + color: ${(props) => props.theme.colors.text.darkOrange}; + } `; export default StyledWrapper; diff --git a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js index 50e7be277d..41765e16c1 100644 --- a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js +++ b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js @@ -1,4 +1,4 @@ -import React, { useRef, useEffect, useCallback } from 'react'; +import React, { useState, useRef, useEffect, useCallback } from 'react'; import { useFormik } from 'formik'; import * as Yup from 'yup'; import toast from 'react-hot-toast'; @@ -12,6 +12,8 @@ import HttpMethodSelector from 'components/RequestPane/QueryUrl/HttpMethodSelect import { getDefaultRequestPaneTab } from 'utils/collections'; import StyledWrapper from './StyledWrapper'; import { getRequestFromCurlCommand } from 'utils/curl'; +import { normalizeFileName } from 'utils/common/index'; +import { IconEdit } from '@tabler/icons'; const NewRequest = ({ collection, item, isEphemeral, onClose }) => { const dispatch = useDispatch(); @@ -20,6 +22,8 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { brunoConfig: { presets: collectionPresets = {} } } = collection; + const [isEditingFilename, toggleEditingFilename] = useState(false); + const getRequestType = (collectionPresets) => { if (!collectionPresets || !collectionPresets.requestType) { return 'http-request'; @@ -44,6 +48,7 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { enableReinitialize: true, initialValues: { requestName: '', + filename: '', requestType: getRequestType(collectionPresets), requestUrl: collectionPresets.requestUrl || '', requestMethod: 'GET', @@ -53,14 +58,26 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { requestName: Yup.string() .trim() .min(1, 'must be at least 1 character') - .required('name is required') + .required('name is required'), + filename: Yup.string() + .trim() + .min(1, 'must be at least 1 character') + .required('filename is required') .test({ - name: 'requestName', - message: `The request names - collection and folder is reserved in bruno`, + name: 'filename', + message: `The file names - collection and folder is reserved in bruno`, test: (value) => { const trimmedValue = value ? value.trim().toLowerCase() : ''; return !['collection', 'folder'].includes(trimmedValue); } + }) + .test({ + name: 'filename', + message: `Invalid file name`, + test: (value) => { + const trimmedValue = value ? value.trim().toLowerCase() : ''; + return (normalizeFileName(trimmedValue) === trimmedValue) + } }), curlCommand: Yup.string().when('requestType', { is: (requestType) => requestType === 'from-curl', @@ -81,6 +98,7 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { newEphemeralHttpRequest({ uid: uid, requestName: values.requestName, + filename: values.filename, requestType: values.requestType, requestUrl: values.requestUrl, requestMethod: values.requestMethod, @@ -103,6 +121,7 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { dispatch( newHttpRequest({ requestName: values.requestName, + filename: values.filename, requestType: 'http-request', requestUrl: request.url, requestMethod: request.method, @@ -119,6 +138,7 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { dispatch( newHttpRequest({ requestName: values.requestName, + filename: values.filename, requestType: values.requestType, requestUrl: values.requestUrl, requestMethod: values.requestMethod, @@ -159,9 +179,18 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { [formik] ); + const filename = formik.values.filename; + const filenameFooter = !isEditingFilename && filename ? +
+

{filename}.bru

+ toggleEditingFilename(v => !v)} /> +
+ : + <> + return ( - +
{ autoCorrect="off" autoCapitalize="off" spellCheck="false" - onChange={formik.handleChange} + onChange={e => { + formik.setFieldValue('requestName', e.target.value); + !isEditingFilename && formik.setFieldValue('filename', normalizeFileName(e.target.value)); + }} value={formik.values.requestName || ''} /> {formik.touched.requestName && formik.errors.requestName ? (
{formik.errors.requestName}
) : null} + { + isEditingFilename ? +
+ +
+ + .bru +
+ {formik.touched.filename && formik.errors.filename ? ( +
{formik.errors.filename}
+ ) : null} +
+ : + <> + } {formik.values.requestType !== 'from-curl' ? ( <>
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 054b4fbd46..2ebf482b6e 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -3,6 +3,7 @@ import cloneDeep from 'lodash/cloneDeep'; import filter from 'lodash/filter'; import find from 'lodash/find'; import get from 'lodash/get'; +import set from 'lodash/set'; import trim from 'lodash/trim'; import path from 'path'; import { insertTaskIntoQueue } from 'providers/ReduxStore/slices/app'; @@ -324,7 +325,7 @@ export const runCollectionFolder = (collectionUid, folderUid, recursive, delay) }); }; -export const newFolder = (folderName, collectionUid, itemUid) => (dispatch, getState) => { +export const newFolder = (folderName, directoryName, collectionUid, itemUid) => (dispatch, getState) => { const state = getState(); const collection = findCollectionByUid(state.collections.collections, collectionUid); @@ -336,14 +337,14 @@ export const newFolder = (folderName, collectionUid, itemUid) => (dispatch, getS if (!itemUid) { const folderWithSameNameExists = find( collection.items, - (i) => i.type === 'folder' && trim(i.name) === trim(folderName) + (i) => i.type === 'folder' && trim(i.filename) === trim(directoryName) ); if (!folderWithSameNameExists) { - const fullName = `${collection.pathname}${PATH_SEPARATOR}${folderName}`; + const fullName = `${collection.pathname}${PATH_SEPARATOR}${directoryName}`; const { ipcRenderer } = window; ipcRenderer - .invoke('renderer:new-folder', fullName) + .invoke('renderer:new-folder', fullName, folderName) .then(() => resolve()) .catch((error) => reject(error)); } else { @@ -354,14 +355,14 @@ export const newFolder = (folderName, collectionUid, itemUid) => (dispatch, getS if (currentItem) { const folderWithSameNameExists = find( currentItem.items, - (i) => i.type === 'folder' && trim(i.name) === trim(folderName) + (i) => i.type === 'folder' && trim(i.filename) === trim(directoryName) ); if (!folderWithSameNameExists) { - const fullName = `${currentItem.pathname}${PATH_SEPARATOR}${folderName}`; + const fullName = `${currentItem.pathname}${PATH_SEPARATOR}${directoryName}`; const { ipcRenderer } = window; ipcRenderer - .invoke('renderer:new-folder', fullName) + .invoke('renderer:new-folder', fullName, folderName) .then(() => resolve()) .catch((error) => reject(error)); } else { @@ -375,7 +376,33 @@ export const newFolder = (folderName, collectionUid, itemUid) => (dispatch, getS }; // rename item -export const renameItem = (newName, itemUid, collectionUid) => (dispatch, getState) => { +export const renameItemName = (newName, itemUid, collectionUid) => (dispatch, getState) => { + const state = getState(); + const collection = findCollectionByUid(state.collections.collections, collectionUid); + + return new Promise((resolve, reject) => { + if (!collection) { + return reject(new Error('Collection not found')); + } + + const collectionCopy = cloneDeep(collection); + const item = findItemInCollection(collectionCopy, itemUid); + if (!item) { + return reject(new Error('Unable to locate item')); + } + + const { ipcRenderer } = window; + + ipcRenderer.invoke('renderer:rename-item-name', item.pathname, newName).then(resolve).catch((err) => { + toast.error('Failed to rename the request'); + console.error(err); + reject(); + }); + }); +}; + +// rename item +export const renameItem = (newName, newFilename, itemUid, collectionUid) => (dispatch, getState) => { const state = getState(); const collection = findCollectionByUid(state.collections.collections, collectionUid); @@ -394,18 +421,22 @@ export const renameItem = (newName, itemUid, collectionUid) => (dispatch, getSta let newPathname = ''; if (item.type === 'folder') { - newPathname = path.join(dirname, trim(newName)); + newPathname = path.join(dirname, trim(newFilename)); } else { - const filename = resolveRequestFilename(newName); + const filename = resolveRequestFilename(newFilename); newPathname = path.join(dirname, filename); } const { ipcRenderer } = window; - ipcRenderer.invoke('renderer:rename-item', item.pathname, newPathname, newName).then(resolve).catch(reject); + ipcRenderer.invoke('renderer:rename-item-filename', item.pathname, newPathname, newName, newFilename).then(resolve).catch((err) => { + toast.error('Failed to rename the request'); + console.error(err); + reject(); + }); }); }; -export const cloneItem = (newName, itemUid, collectionUid) => (dispatch, getState) => { +export const cloneItem = (newName, newFilename, itemUid, collectionUid) => (dispatch, getState) => { const state = getState(); const collection = findCollectionByUid(state.collections.collections, collectionUid); @@ -424,20 +455,24 @@ export const cloneItem = (newName, itemUid, collectionUid) => (dispatch, getStat const folderWithSameNameExists = find( parentFolder.items, - (i) => i.type === 'folder' && trim(i.name) === trim(newName) + (i) => i.type === 'folder' && trim(i?.filename) === trim(newFilename) ); if (folderWithSameNameExists) { return reject(new Error('Duplicate folder names under same parent folder are not allowed')); } - const collectionPath = `${parentFolder.pathname}${PATH_SEPARATOR}${newName}`; + set(item, 'name', newName); + set(item, 'filename', newFilename); + set(item, 'root.meta.name', newName); + + const collectionPath = `${parentFolder.pathname}${PATH_SEPARATOR}${newFilename}`; ipcRenderer.invoke('renderer:clone-folder', item, collectionPath).then(resolve).catch(reject); return; } const parentItem = findParentItemInCollection(collectionCopy, itemUid); - const filename = resolveRequestFilename(newName); + const filename = resolveRequestFilename(newFilename); const itemToSave = refreshUidsInItem(transformRequestToSaveToFilesystem(item)); itemToSave.name = trim(newName); if (!parentItem) { @@ -700,7 +735,7 @@ export const moveItemToRootOfCollection = (collectionUid, draggedItemUid) => (di }; export const newHttpRequest = (params) => (dispatch, getState) => { - const { requestName, requestType, requestUrl, requestMethod, collectionUid, itemUid, headers, body, auth } = params; + const { requestName, filename, requestType, requestUrl, requestMethod, collectionUid, itemUid, headers, body, auth } = params; return new Promise((resolve, reject) => { const state = getState(); @@ -728,6 +763,7 @@ export const newHttpRequest = (params) => (dispatch, getState) => { uid: uuid(), type: requestType, name: requestName, + filename, request: { method: requestMethod, url: requestUrl, @@ -749,17 +785,17 @@ export const newHttpRequest = (params) => (dispatch, getState) => { }; // itemUid is null when we are creating a new request at the root level - const filename = resolveRequestFilename(requestName); + const resolvedFilename = resolveRequestFilename(filename); if (!itemUid) { const reqWithSameNameExists = find( collection.items, - (i) => i.type !== 'folder' && trim(i.filename) === trim(filename) + (i) => i.type !== 'folder' && trim(i.filename) === trim(resolvedFilename) ); const requestItems = filter(collection.items, (i) => i.type !== 'folder'); item.seq = requestItems.length + 1; if (!reqWithSameNameExists) { - const fullName = `${collection.pathname}${PATH_SEPARATOR}${filename}`; + const fullName = `${collection.pathname}${PATH_SEPARATOR}${resolvedFilename}`; const { ipcRenderer } = window; ipcRenderer.invoke('renderer:new-request', fullName, item).then(resolve).catch(reject); @@ -780,12 +816,12 @@ export const newHttpRequest = (params) => (dispatch, getState) => { if (currentItem) { const reqWithSameNameExists = find( currentItem.items, - (i) => i.type !== 'folder' && trim(i.filename) === trim(filename) + (i) => i.type !== 'folder' && trim(i.filename) === trim(resolvedFilename) ); const requestItems = filter(currentItem.items, (i) => i.type !== 'folder'); item.seq = requestItems.length + 1; if (!reqWithSameNameExists) { - const fullName = `${currentItem.pathname}${PATH_SEPARATOR}${filename}`; + const fullName = `${currentItem.pathname}${PATH_SEPARATOR}${resolvedFilename}`; const { ipcRenderer } = window; ipcRenderer.invoke('renderer:new-request', fullName, item).then(resolve).catch(reject); 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 e8fb4d602b..e384453302 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -1416,6 +1416,9 @@ export const collectionsSlice = createSlice({ const folderPath = getDirectoryName(file.meta.pathname); const folderItem = findItemInCollectionByPathname(collection, folderPath); if (folderItem) { + if (file?.data?.meta?.name) { + folderItem.name = file?.data?.meta?.name; + } folderItem.root = file.data; } return; @@ -1427,7 +1430,7 @@ export const collectionsSlice = createSlice({ let currentPath = collection.pathname; let currentSubItems = collection.items; for (const directoryName of subDirectories) { - let childItem = currentSubItems.find((f) => f.type === 'folder' && f.name === directoryName); + let childItem = currentSubItems.find((f) => f.type === 'folder' && f.filename === directoryName); if (!childItem) { childItem = { uid: uuid(), @@ -1488,6 +1491,7 @@ export const collectionsSlice = createSlice({ uid: uuid(), pathname: `${currentPath}${PATH_SEPARATOR}${directoryName}`, name: directoryName, + filename: directoryName, collapsed: true, type: 'folder', items: [] @@ -1503,11 +1507,25 @@ export const collectionsSlice = createSlice({ }, collectionChangeFileEvent: (state, action) => { const { file } = action.payload; + const isCollectionRoot = file.meta.collectionRoot ? true : false; + const isFolderRoot = file.meta.folderRoot ? true : false; const collection = findCollectionByUid(state.collections, file.meta.collectionUid); + if (isCollectionRoot) { + if (collection) { + collection.root = file.data; + } + return; + } - // check and update collection root - if (collection && file.meta.collectionRoot) { - collection.root = file.data; + if (isFolderRoot) { + const folderPath = getDirectoryName(file.meta.pathname); + const folderItem = findItemInCollectionByPathname(collection, folderPath); + if (folderItem) { + if (file?.data?.meta?.name) { + folderItem.name = file?.data?.meta?.name; + } + folderItem.root = file.data; + } return; } diff --git a/packages/bruno-app/src/utils/importers/common.js b/packages/bruno-app/src/utils/importers/common.js index c99048419d..5ffbfce843 100644 --- a/packages/bruno-app/src/utils/importers/common.js +++ b/packages/bruno-app/src/utils/importers/common.js @@ -61,7 +61,6 @@ export const updateUidsInCollection = (_collection) => { export const transformItemsInCollection = (collection) => { const transformItems = (items = []) => { each(items, (item) => { - item.name = normalizeFileName(item.name); if (['http', 'graphql'].includes(item.type)) { item.type = `${item.type}-request`; diff --git a/packages/bruno-electron/src/app/watcher.js b/packages/bruno-electron/src/app/watcher.js index 589cd29d87..bf0100048d 100644 --- a/packages/bruno-electron/src/app/watcher.js +++ b/packages/bruno-electron/src/app/watcher.js @@ -368,6 +368,30 @@ const change = async (win, pathname, collectionUid, collectionPath) => { } } + if (path.basename(pathname) === 'folder.bru') { + const file = { + meta: { + collectionUid, + pathname, + name: path.basename(pathname), + folderRoot: true + } + }; + + try { + let bruContent = fs.readFileSync(pathname, 'utf8'); + + file.data = collectionBruToJson(bruContent); + + hydrateBruCollectionFileWithUuid(file.data); + win.webContents.send('main:collection-tree-updated', 'change', file); + return; + } catch (err) { + console.error(err); + return; + } + } + if (hasBruExtension(pathname)) { try { const file = { diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index 945c215599..19c8f9c6f7 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -2,7 +2,7 @@ const _ = require('lodash'); const fs = require('fs'); const path = require('path'); const { ipcMain, shell, dialog, app } = require('electron'); -const { envJsonToBru, bruToJson, jsonToBru, jsonToCollectionBru } = require('../bru'); +const { envJsonToBru, bruToJson, jsonToBru, jsonToCollectionBru, collectionBruToJson } = require('../bru'); const { isValidPathname, @@ -326,7 +326,55 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection }); // rename item - ipcMain.handle('renderer:rename-item', async (event, oldPath, newPath, newName) => { + ipcMain.handle('renderer:rename-item-name', async (event, itemPath, newName) => { + try { + // Normalize paths if they are WSL paths + if (isWSLPath(itemPath)) { + itemPath = normalizeWslPath(itemPath); + } + + if (!fs.existsSync(itemPath)) { + throw new Error(`path: ${itemPath} does not exist`); + } + + if (isDirectory(itemPath)) { + let data; + const folderBruFilePath = path.join(itemPath, 'folder.bru'); + + if (fs.existsSync(folderBruFilePath)) { + const fileData = await fs.promises.readFile(folderBruFilePath, 'utf8'); + data = collectionBruToJson(fileData); + } else { + data = {}; + } + + data.meta = { + name: newName, + }; + + const content = jsonToCollectionBru(data, true); // isFolder flag + await writeFile(folderBruFilePath, content); + + return; + } + + const isBru = hasBruExtension(itemPath); + if (!isBru) { + throw new Error(`path: ${itemPath} is not a bru file`); + } + + const data = fs.readFileSync(itemPath, 'utf8'); + const jsonData = bruToJson(data); + jsonData.name = newName; + const content = jsonToBru(jsonData); + await writeFile(itemPath, content); + } catch (error) { + return Promise.reject(error); + } + }); + + // rename item + ipcMain.handle('renderer:rename-item-filename', async (event, oldPath, newPath, newName) => { try { // Normalize paths if they are WSL paths if (isWSLPath(oldPath)) { @@ -376,10 +424,18 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection }); // new folder - ipcMain.handle('renderer:new-folder', async (event, pathname) => { + ipcMain.handle('renderer:new-folder', async (event, pathname, folderName) => { try { if (!fs.existsSync(pathname)) { fs.mkdirSync(pathname); + const folderBruFilePath = path.join(pathname, 'folder.bru'); + let data = { + meta: { + name: folderName, + } + }; + const content = jsonToCollectionBru(data, true); // isFolder flag + await writeFile(folderBruFilePath, content); } else { return Promise.reject(new Error('The directory already exists')); } @@ -539,16 +595,17 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection items.forEach((item) => { if (['http-request', 'graphql-request'].includes(item.type)) { const content = jsonToBru(item); - const filePath = path.join(currentPath, `${item.name}.bru`); + const filePath = path.join(currentPath, `${item.filename}`); fs.writeFileSync(filePath, content); } if (item.type === 'folder') { - const folderPath = path.join(currentPath, item.name); + const folderPath = path.join(currentPath, item.filename); fs.mkdirSync(folderPath); // If folder has a root element, then I should write its folder.bru file if (item.root) { const folderContent = jsonToCollectionBru(item.root, true); + folderContent.name = item.name; if (folderContent) { const bruFolderPath = path.join(folderPath, `folder.bru`); fs.writeFileSync(bruFolderPath, folderContent); From caf8ff53f8602219665c528de9809778e11c8412 Mon Sep 17 00:00:00 2001 From: lohxt1 Date: Fri, 13 Sep 2024 18:34:01 +0530 Subject: [PATCH 07/31] feat: updates --- .../Collection/CollectionItem/CloneCollectionItem/index.js | 2 +- .../Collection/CollectionItem/RenameCollectionItem/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CloneCollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CloneCollectionItem/index.js index 101828e15c..a20b490fa4 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CloneCollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CloneCollectionItem/index.js @@ -72,7 +72,7 @@ const CloneCollectionItem = ({ collection, item, onClose }) => { const filename = formik.values.filename; const filenameFooter = !isEditingFilename && filename ?
-

{filename}.bru

+

{filename}{itemType !== 'folder' ? '.bru' : ''}

toggleEditingFilename(v => !v)} />
: diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js index 7b9acfa00e..e80549a926 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js @@ -77,7 +77,7 @@ const RenameCollectionItem = ({ collection, item, onClose }) => { const filename = formik.values.filename; const filenameFooter = !isEditingFilename && filename ?
-

{filename}.bru

+

{filename}{itemType !== 'folder' ? '.bru' : ''}

toggleEditingFilename(v => !v)} />
: From 7c19633d51bd5802758a3dec611358cfe2375e5b Mon Sep 17 00:00:00 2001 From: lohxt1 Date: Wed, 18 Sep 2024 10:47:50 +0530 Subject: [PATCH 08/31] feat: updates --- .../src/components/Modal/StyledWrapper.js | 4 -- .../CollectionItemInfo/index.js | 38 +++++++++++++++++++ .../Collection/CollectionItem/index.js | 15 +++++++- .../ReduxStore/slices/collections/index.js | 4 +- packages/bruno-electron/src/app/watcher.js | 27 ++++++++++++- 5 files changed, 79 insertions(+), 9 deletions(-) create mode 100644 packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CollectionItemInfo/index.js diff --git a/packages/bruno-app/src/components/Modal/StyledWrapper.js b/packages/bruno-app/src/components/Modal/StyledWrapper.js index ad58c86849..f26d811f54 100644 --- a/packages/bruno-app/src/components/Modal/StyledWrapper.js +++ b/packages/bruno-app/src/components/Modal/StyledWrapper.js @@ -43,25 +43,21 @@ const Wrapper = styled.div` &.modal-sm { min-width: 300px; - width: 300px; max-width: 500px; } &.modal-md { min-width: 500px; - width: 500px; max-width: 800px; } &.modal-lg { min-width: 800px; - width: 800px; max-width: 1140px; } &.modal-xl { min-width: 1140px; - width: 1140px; max-width: calc(100% - 30px); } diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CollectionItemInfo/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CollectionItemInfo/index.js new file mode 100644 index 0000000000..adffcb90de --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CollectionItemInfo/index.js @@ -0,0 +1,38 @@ +import React from 'react'; +import Modal from 'components/Modal'; +import * as path from 'path'; + +const CollectionItemInfo = ({ collection, item, onClose }) => { + const { pathname: collectionPathname } = collection; + const { name, filename, pathname, type } = item; + return ( + +
+ + + + + + + + + + + + + + + +
Name :{name}
{type=='folder' ? 'Directory Name' : 'Filename'} :{filename}
Pathname :{path.relative(collectionPathname, pathname)}
+
+
+ ); +}; + +export default CollectionItemInfo; 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 6e5947e585..906083f79a 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,7 +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'; +import CollectionItemInfo from './CollectionItemInfo/index'; const CollectionItem = ({ item, collection, searchText }) => { const tabs = useSelector((state) => state.tabs.tabs); @@ -40,6 +40,7 @@ const CollectionItem = ({ item, collection, searchText }) => { const [newFolderModalOpen, setNewFolderModalOpen] = useState(false); const [runCollectionModalOpen, setRunCollectionModalOpen] = useState(false); const [itemIsCollapsed, setItemisCollapsed] = useState(item.collapsed); + const [itemInfoModalOpen, setItemInfoModalOpen] = useState(false); const [{ isDragging }, drag] = useDrag({ type: `COLLECTION_ITEM_${collection.uid}`, @@ -237,6 +238,9 @@ const CollectionItem = ({ item, collection, searchText }) => { {generateCodeItemModalOpen && ( setGenerateCodeItemModalOpen(false)} /> )} + {itemInfoModalOpen && ( + setItemInfoModalOpen(false)} /> + )}
drag(drop(node))}>
{indents && indents.length @@ -379,6 +383,15 @@ const CollectionItem = ({ item, collection, searchText }) => { Settings
)} +
{ + dropdownTippyRef.current.hide(); + setItemInfoModalOpen(true); + }} + > + Info +
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 e384453302..d151c134ce 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -1485,12 +1485,12 @@ export const collectionsSlice = createSlice({ let currentPath = collection.pathname; let currentSubItems = collection.items; for (const directoryName of subDirectories) { - let childItem = currentSubItems.find((f) => f.type === 'folder' && f.name === directoryName); + let childItem = currentSubItems.find((f) => f.type === 'folder' && f.filename === directoryName); if (!childItem) { childItem = { uid: uuid(), pathname: `${currentPath}${PATH_SEPARATOR}${directoryName}`, - name: directoryName, + name: dir?.meta?.name || directoryName, filename: directoryName, collapsed: true, type: 'folder', diff --git a/packages/bruno-electron/src/app/watcher.js b/packages/bruno-electron/src/app/watcher.js index bf0100048d..34045d0106 100644 --- a/packages/bruno-electron/src/app/watcher.js +++ b/packages/bruno-electron/src/app/watcher.js @@ -294,13 +294,25 @@ const addDirectory = (win, pathname, collectionUid, collectionPath) => { return; } + const folderBruFilePath = path.join(pathname, `folder.bru`); + + let name = path.basename(pathname); + + if(fs.existsSync(folderBruFilePath)) { + let folderBruFileContent = fs.readFileSync(folderBruFilePath, 'utf8'); + let folderBruData = collectionBruToJson(folderBruFileContent); + name = folderBruData?.meta?.name || name; + } + const directory = { meta: { collectionUid, pathname, - name: path.basename(pathname) + name } }; + + win.webContents.send('main:collection-tree-updated', 'addDir', directory); }; @@ -437,11 +449,22 @@ const unlinkDir = (win, pathname, collectionUid, collectionPath) => { return; } + + const folderBruFilePath = path.join(pathname, `folder.bru`); + + let name = path.basename(pathname); + + if(fs.existsSync(folderBruFilePath)) { + let folderBruFileContent = fs.readFileSync(folderBruFilePath, 'utf8'); + let folderBruData = collectionBruToJson(folderBruFileContent); + name = folderBruData?.meta?.name || name; + } + const directory = { meta: { collectionUid, pathname, - name: path.basename(pathname) + name } }; win.webContents.send('main:collection-tree-updated', 'unlinkDir', directory); From 5d197b10e255f312f57e4b14eb527d31ef897252 Mon Sep 17 00:00:00 2001 From: lohxt1 Date: Fri, 20 Sep 2024 12:24:14 +0530 Subject: [PATCH 09/31] feat: updates --- .../CloneCollectionItem/StyledWrapper.js | 9 ++ .../CloneCollectionItem/index.js | 38 +++-- .../RenameCollectionItem/StyledWrapper.js | 4 +- .../RenameCollectionItem/index.js | 55 +++---- .../Sidebar/NewFolder/StyledWrapper.js | 9 ++ .../src/components/Sidebar/NewFolder/index.js | 141 +++++++++--------- .../Sidebar/NewRequest/StyledWrapper.js | 4 + .../components/Sidebar/NewRequest/index.js | 30 ++-- packages/bruno-app/src/utils/common/regex.js | 53 +++++++ 9 files changed, 200 insertions(+), 143 deletions(-) create mode 100644 packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CloneCollectionItem/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/Sidebar/NewFolder/StyledWrapper.js diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CloneCollectionItem/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CloneCollectionItem/StyledWrapper.js new file mode 100644 index 0000000000..9e5ceb016f --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CloneCollectionItem/StyledWrapper.js @@ -0,0 +1,9 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + .highlight { + color: ${(props) => props.theme.colors.text.yellow}; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CloneCollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CloneCollectionItem/index.js index a20b490fa4..051f2c7450 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CloneCollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CloneCollectionItem/index.js @@ -6,9 +6,10 @@ import Modal from 'components/Modal'; import { useDispatch } from 'react-redux'; import { isItemAFolder } from 'utils/tabs'; import { cloneItem } from 'providers/ReduxStore/slices/collections/actions'; -import { normalizeFileName } from 'utils/common/index'; import { IconEdit, IconFile } from '@tabler/icons'; import * as path from 'path'; +import { sanitizeName, validateName } from 'utils/common/regex'; +import StyledWrapper from './StyledWrapper'; const CloneCollectionItem = ({ collection, item, onClose }) => { const dispatch = useDispatch(); @@ -22,7 +23,7 @@ const CloneCollectionItem = ({ collection, item, onClose }) => { enableReinitialize: true, initialValues: { name: itemName, - filename: itemFilename + filename: sanitizeName(itemFilename) }, validationSchema: Yup.object({ name: Yup.string() @@ -33,22 +34,11 @@ const CloneCollectionItem = ({ collection, item, onClose }) => { .min(1, 'must be at least 1 character') .max(50, 'must be 50 characters or less') .required('name is required') - .test({ - name: 'filename', - message: `The file names - collection and folder is reserved in bruno`, - test: (value) => { - const trimmedValue = value ? value.trim().toLowerCase() : ''; - return !['collection', 'folder'].includes(trimmedValue); - } - }) - .test({ - name: 'filename', - message: `Invalid file name`, - test: (value) => { - const trimmedValue = value ? value.trim().toLowerCase() : ''; - return (normalizeFileName(trimmedValue) === trimmedValue) - } + .test('is-valid-filename', function(value) { + const isValid = validateName(value); + return isValid ? true : this.createError({ message: validateNameError(value) }); }) + .test('not-reserved', `The file names "collection" and "folder" are reserved in bruno`, value => !['collection', 'folder'].includes(value)) }), onSubmit: (values) => { dispatch(cloneItem(values.name, values.filename, item.uid, collection.uid)) @@ -70,15 +60,21 @@ const CloneCollectionItem = ({ collection, item, onClose }) => { const onSubmit = () => formik.handleSubmit(); const filename = formik.values.filename; + const name = formik.values.name; + const doNamesDiffer = filename !== name; + + console.log("clone item", filename, itemFilename); + const filenameFooter = !isEditingFilename && filename ? -
-

{filename}{itemType !== 'folder' ? '.bru' : ''}

+
+

{filename}{itemType !== 'folder' ? '.bru' : ''}

toggleEditingFilename(v => !v)} />
: <> return ( + { spellCheck="false" onChange={e => { formik.setFieldValue('name', e.target.value); - !isEditingFilename && formik.setFieldValue('filename', normalizeFileName(e.target.value)); + !isEditingFilename && formik.setFieldValue('filename', sanitizeName(e.target.value)); }} value={formik.values.name || ''} /> {formik.touched.name && formik.errors.name ?
{formik.errors.name}
: null}
+ {formik.touched.filename && formik.errors.filename ?
{formik.errors.filename}
: null} { isEditingFilename ?
@@ -142,6 +139,7 @@ const CloneCollectionItem = ({ collection, item, onClose }) => { } + ); }; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/StyledWrapper.js index cb44f0e503..9e5ceb016f 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/StyledWrapper.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/StyledWrapper.js @@ -1,8 +1,8 @@ import styled from 'styled-components'; const Wrapper = styled.div` - .file-extension { - color: ${(props) => props.theme.colors.text.darkOrange}; + .highlight { + color: ${(props) => props.theme.colors.text.yellow}; } `; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js index e80549a926..d46d3efa8b 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js @@ -5,10 +5,10 @@ import Modal from 'components/Modal'; import { useDispatch } from 'react-redux'; import { isItemAFolder } from 'utils/tabs'; import { renameItem, renameItemName, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; -import { normalizeFileName } from 'utils/common/index'; import path from 'path'; import { IconEdit, IconFile } from '@tabler/icons'; import StyledWrapper from './StyledWrapper'; +import { sanitizeName, validateName, validateNameError } from 'utils/common/regex'; const RenameCollectionItem = ({ collection, item, onClose }) => { const dispatch = useDispatch(); @@ -22,7 +22,7 @@ const RenameCollectionItem = ({ collection, item, onClose }) => { enableReinitialize: true, initialValues: { name: itemName, - filename: itemFilename + filename: sanitizeName(itemFilename) }, validationSchema: Yup.object({ name: Yup.string() @@ -33,22 +33,11 @@ const RenameCollectionItem = ({ collection, item, onClose }) => { .min(1, 'must be at least 1 character') .max(50, 'must be 50 characters or less') .required('name is required') - .test({ - name: 'filename', - message: `The file names - collection and folder is reserved in bruno`, - test: (value) => { - const trimmedValue = value ? value.trim().toLowerCase() : ''; - return !['collection', 'folder'].includes(trimmedValue); - } - }) - .test({ - name: 'filename', - message: `Invalid file name`, - test: (value) => { - const trimmedValue = value ? value.trim().toLowerCase() : ''; - return (normalizeFileName(trimmedValue) === trimmedValue) - } + .test('is-valid-filename', function(value) { + const isValid = validateName(value); + return isValid ? true : this.createError({ message: validateNameError(value) }); }) + .test('not-reserved', `The file names "collection" and "folder" are reserved in bruno`, value => !['collection', 'folder'].includes(value)) }), onSubmit: async (values) => { // if there is unsaved changes in the request, @@ -75,24 +64,27 @@ const RenameCollectionItem = ({ collection, item, onClose }) => { const onSubmit = () => formik.handleSubmit(); const filename = formik.values.filename; + const name = formik.values.name; + const doNamesDiffer = filename !== name; + const filenameFooter = !isEditingFilename && filename ? -
-

{filename}{itemType !== 'folder' ? '.bru' : ''}

+
+

{filename}{itemType !== 'folder' ? '.bru' : ''}

toggleEditingFilename(v => !v)} />
: <> return ( - - + +
+ {formik.touched.filename && formik.errors.filename ?
{formik.errors.filename}
: null} { isEditingFilename ?
@@ -146,8 +139,8 @@ const RenameCollectionItem = ({ collection, item, onClose }) => { <> } - - + + ); }; diff --git a/packages/bruno-app/src/components/Sidebar/NewFolder/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/NewFolder/StyledWrapper.js new file mode 100644 index 0000000000..9e5ceb016f --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/NewFolder/StyledWrapper.js @@ -0,0 +1,9 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + .highlight { + color: ${(props) => props.theme.colors.text.yellow}; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/Sidebar/NewFolder/index.js b/packages/bruno-app/src/components/Sidebar/NewFolder/index.js index 9eca52b4cb..eaf756c1b6 100644 --- a/packages/bruno-app/src/components/Sidebar/NewFolder/index.js +++ b/packages/bruno-app/src/components/Sidebar/NewFolder/index.js @@ -5,8 +5,9 @@ import * as Yup from 'yup'; import Modal from 'components/Modal'; import { useDispatch } from 'react-redux'; import { newFolder } from 'providers/ReduxStore/slices/collections/actions'; -import { normalizeFileName } from 'utils/common/index'; import { IconEdit } from '@tabler/icons'; +import { sanitizeName, validateName, validateNameError } from 'utils/common/regex'; +import StyledWrapper from './StyledWrapper'; const NewFolder = ({ collection, item, onClose }) => { const dispatch = useDispatch(); @@ -28,24 +29,18 @@ const NewFolder = ({ collection, item, onClose }) => { .trim() .min(1, 'must be at least 1 character') .required('foldername is required') + .test('is-valid-folder-name', function(value) { + const isValid = validateName(value); + return isValid ? true : this.createError({ message: validateNameError(value) }); + }) .test({ name: 'folderName', message: 'The folder name "environments" at the root of the collection is reserved in bruno', test: (value) => { - if (item && item.uid) { - return true; - } + if (item?.uid) return true; return value && !value.trim().toLowerCase().includes('environments'); } }) - .test({ - name: 'foldername', - message: `Invalid folder name`, - test: (value) => { - const trimmedValue = value ? value.trim().toLowerCase() : ''; - return (normalizeFileName(trimmedValue) === trimmedValue) - } - }), }), onSubmit: (values) => { dispatch(newFolder(values.folderName, values.directoryName, collection.uid, item ? item.uid : null)) @@ -62,74 +57,78 @@ const NewFolder = ({ collection, item, onClose }) => { const onSubmit = () => formik.handleSubmit(); - const directoryName = formik.values.directoryName; + const name = formik.values.name; + const doNamesDiffer = directoryName !== name; + const directoryNameFooter = !isEditingFilename && directoryName ? -
-

{directoryName}.bru

+
+

{directoryName}.bru

toggleEditingFilename(v => !v)} />
: <> return ( - -
-
- - { - formik.setFieldValue('folderName', e.target.value); - !isEditingFilename && formik.setFieldValue('directoryName', normalizeFileName(e.target.value)); - }} - value={formik.values.folderName || ''} - /> - {formik.touched.folderName && formik.errors.folderName ? ( -
{formik.errors.folderName}
- ) : null} -
- { - isEditingFilename ? -
- -
- - .bru + + + +
+ + { + formik.setFieldValue('folderName', e.target.value); + !isEditingFilename && formik.setFieldValue('directoryName', sanitizeName(e.target.value)); + }} + value={formik.values.folderName || ''} + /> + {formik.touched.folderName && formik.errors.folderName ? ( +
{formik.errors.folderName}
+ ) : null} +
+ { + isEditingFilename ? +
+ +
+ + .bru +
+ {formik.touched.directoryName && formik.errors.directoryName ? ( +
{formik.errors.directoryName}
+ ) : null}
- {formik.touched.directoryName && formik.errors.directoryName ? ( -
{formik.errors.directoryName}
- ) : null} -
- : - <> - } - - + : + <> + } + + + ); }; diff --git a/packages/bruno-app/src/components/Sidebar/NewRequest/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/NewRequest/StyledWrapper.js index 80499b8e58..9fb3282cc7 100644 --- a/packages/bruno-app/src/components/Sidebar/NewRequest/StyledWrapper.js +++ b/packages/bruno-app/src/components/Sidebar/NewRequest/StyledWrapper.js @@ -43,6 +43,10 @@ const StyledWrapper = styled.div` .file-extension { color: ${(props) => props.theme.colors.text.darkOrange}; } + + .highlight { + color: ${(props) => props.theme.colors.text.yellow}; + } `; export default StyledWrapper; diff --git a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js index 41765e16c1..2f9f2e0cc9 100644 --- a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js +++ b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js @@ -12,8 +12,8 @@ import HttpMethodSelector from 'components/RequestPane/QueryUrl/HttpMethodSelect import { getDefaultRequestPaneTab } from 'utils/collections'; import StyledWrapper from './StyledWrapper'; import { getRequestFromCurlCommand } from 'utils/curl'; -import { normalizeFileName } from 'utils/common/index'; import { IconEdit } from '@tabler/icons'; +import { sanitizeName, validateName, validateNameError } from 'utils/common/regex'; const NewRequest = ({ collection, item, isEphemeral, onClose }) => { const dispatch = useDispatch(); @@ -63,22 +63,11 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { .trim() .min(1, 'must be at least 1 character') .required('filename is required') - .test({ - name: 'filename', - message: `The file names - collection and folder is reserved in bruno`, - test: (value) => { - const trimmedValue = value ? value.trim().toLowerCase() : ''; - return !['collection', 'folder'].includes(trimmedValue); - } + .test('is-valid-filename', function(value) { + const isValid = validateName(value); + return isValid ? true : this.createError({ message: validateNameError(value) }); }) - .test({ - name: 'filename', - message: `Invalid file name`, - test: (value) => { - const trimmedValue = value ? value.trim().toLowerCase() : ''; - return (normalizeFileName(trimmedValue) === trimmedValue) - } - }), + .test('not-reserved', `The file names "collection" and "folder" are reserved in bruno`, value => !['collection', 'folder'].includes(value)), curlCommand: Yup.string().when('requestType', { is: (requestType) => requestType === 'from-curl', then: Yup.string() @@ -180,9 +169,12 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { ); const filename = formik.values.filename; + const name = formik.values.name; + const doNamesDiffer = filename !== name; + const filenameFooter = !isEditingFilename && filename ? -
-

{filename}.bru

+
+

{filename}.bru

toggleEditingFilename(v => !v)} />
: @@ -268,7 +260,7 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { spellCheck="false" onChange={e => { formik.setFieldValue('requestName', e.target.value); - !isEditingFilename && formik.setFieldValue('filename', normalizeFileName(e.target.value)); + !isEditingFilename && formik.setFieldValue('filename', sanitizeName(e.target.value)); }} value={formik.values.requestName || ''} /> diff --git a/packages/bruno-app/src/utils/common/regex.js b/packages/bruno-app/src/utils/common/regex.js index 53f46741e8..a5dc20c892 100644 --- a/packages/bruno-app/src/utils/common/regex.js +++ b/packages/bruno-app/src/utils/common/regex.js @@ -1 +1,54 @@ export const variableNameRegex = /^[\w-.]*$/; + +export const sanitizeName = (name) => { + const invalidCharacters = /[<>:"/\\|?*\x00-\x1F]/g; // Match one or more invalid characters + return name + .replace(invalidCharacters, '-') // Replace invalid characters with hyphens + .replace(/^-+/, '') // Remove leading hyphens + .replace(/\.$/, ''); // Remove trailing period if it exists +}; + +export const validateName = (name) => { + const reservedDeviceNames = /^(CON|PRN|AUX|NUL|COM[0-9]|LPT[0-9])$/i; + const firstCharacter = /^[^ \-\<>:"/\\|?*\x00-\x1F]/; + const middleCharacters = /^[^<>:"/\\|?*\x00-\x1F]*$/; + const lastCharacter = /[^.\ ]$/; + + return ( + !reservedDeviceNames.test(name) && + firstCharacter.test(name) && + middleCharacters.test(name) && + lastCharacter.test(name) + ); +} + +export const validateNameError = (name) => { + const reservedDeviceNames = /^(CON|PRN|AUX|NUL|COM[0-9]|LPT[0-9])$/i; + const firstCharacter = /^[^ \-\<>:"/\\|?*\x00-\x1F]/; + const middleCharacters = /^[^<>:"/\\|?*\x00-\x1F]*$/; + const lastCharacter = /[^.\ ]$/; + + // reserved device names + if (reservedDeviceNames.test(name)) { + return "Filename cannot be a reserved device name."; + } + + // first character + if (!firstCharacter.test(name[0])) { + return `Invalid first character`; + } + + // middle characters + for (let i = 1; i < name.length - 1; i++) { + if (!middleCharacters.test(name[i])) { + return `Invalid character in the middle - ${name[i]} at position ${i}` + } + } + + // last character + if (!lastCharacter.test(name[name.length - 1])) { + return `Invalid last character` + } + + return ''; +}; From b95cc1688bc1918fb74aee7edd1bab451d5cf47a Mon Sep 17 00:00:00 2001 From: lohxt1 Date: Fri, 20 Sep 2024 17:59:53 +0530 Subject: [PATCH 10/31] feat: added tests --- packages/bruno-app/src/utils/common/regex.js | 3 +- .../bruno-app/src/utils/common/regex.spec.js | 153 ++++++++++++++++++ 2 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 packages/bruno-app/src/utils/common/regex.spec.js diff --git a/packages/bruno-app/src/utils/common/regex.js b/packages/bruno-app/src/utils/common/regex.js index a5dc20c892..5d87e9203a 100644 --- a/packages/bruno-app/src/utils/common/regex.js +++ b/packages/bruno-app/src/utils/common/regex.js @@ -4,8 +4,7 @@ export const sanitizeName = (name) => { const invalidCharacters = /[<>:"/\\|?*\x00-\x1F]/g; // Match one or more invalid characters return name .replace(invalidCharacters, '-') // Replace invalid characters with hyphens - .replace(/^-+/, '') // Remove leading hyphens - .replace(/\.$/, ''); // Remove trailing period if it exists + .replace(/^[\.\-\s]+|[\.\s]+$/g, ''); }; export const validateName = (name) => { diff --git a/packages/bruno-app/src/utils/common/regex.spec.js b/packages/bruno-app/src/utils/common/regex.spec.js new file mode 100644 index 0000000000..897c3225d7 --- /dev/null +++ b/packages/bruno-app/src/utils/common/regex.spec.js @@ -0,0 +1,153 @@ +const { describe, it, expect } = require('@jest/globals'); + +import { sanitizeName, validateName } from './regex'; + +describe('regex validators', () => { + describe('sanitize name', () => { + it('should remove invalid characters', () => { + expect(sanitizeName('hello world')).toBe('hello world'); + expect(sanitizeName('hello-world')).toBe('hello-world'); + expect(sanitizeName('hello_world')).toBe('hello_world'); + expect(sanitizeName('hello_world-')).toBe('hello_world-'); + expect(sanitizeName('hello_world-123')).toBe('hello_world-123'); + expect(sanitizeName('hello_world-123!@#$%^&*()')).toBe('hello_world-123!@#$%^&-()'); + expect(sanitizeName('hello_world?')).toBe('hello_world-'); + expect(sanitizeName('foo/bar/')).toBe('foo-bar-'); + expect(sanitizeName('foo\\bar\\')).toBe('foo-bar-'); + }); + + it('should remove leading hyphens', () => { + expect(sanitizeName('-foo')).toBe('foo'); + expect(sanitizeName('---foo')).toBe('foo'); + expect(sanitizeName('-foo-bar')).toBe('foo-bar'); + }); + + it('should remove trailing periods', () => { + expect(sanitizeName('file.')).toBe('file'); + expect(sanitizeName('file.name.')).toBe('file.name'); + expect(sanitizeName('hello world.')).toBe('hello world'); + }); + + it('should handle filenames with only invalid characters', () => { + expect(sanitizeName('<>:"/\\|?*')).toBe(''); + expect(sanitizeName('::::')).toBe(''); + }); + + it('should handle filenames with a mix of valid and invalid characters', () => { + expect(sanitizeName('test<>:"/\\|?*')).toBe('test---------'); + expect(sanitizeName('foo')).toBe('foo-bar-'); + }); + + it('should remove control characters', () => { + expect(sanitizeName('foo\x00bar')).toBe('foo-bar'); + expect(sanitizeName('file\x1Fname')).toBe('file-name'); + }); + + it('should return an empty string if the name is empty or consists only of invalid characters', () => { + expect(sanitizeName('')).toBe(''); + expect(sanitizeName('<>:"/\\|?*')).toBe(''); + }); + + it('should handle filenames with multiple consecutive invalid characters', () => { + expect(sanitizeName('foo< { + expect(sanitizeName(' ')).toBe(''); + }); + + it('should handle names with leading/trailing spaces', () => { + expect(sanitizeName(' foo bar ')).toBe('foo bar'); + }); + + it('should preserve valid non-ASCII characters', () => { + expect(sanitizeName('brunó')).toBe('brunó'); + expect(sanitizeName('文件')).toBe('文件'); + expect(sanitizeName('brunfais')).toBe('brunfais'); + expect(sanitizeName('brunai')).toBe('brunai'); + expect(sanitizeName('brunsборка')).toBe('brunsборка'); + expect(sanitizeName('brunпривет')).toBe('brunпривет'); + expect(sanitizeName('🐶')).toBe('🐶'); + expect(sanitizeName('brunfais🐶')).toBe('brunfais🐶'); + expect(sanitizeName('file-🐶-bruno')).toBe('file-🐶-bruno'); + expect(sanitizeName('helló')).toBe('helló'); + }); + + it('should preserve case sensitivity', () => { + expect(sanitizeName('FileName')).toBe('FileName'); + expect(sanitizeName('fileNAME')).toBe('fileNAME'); + }); + + it('should handle filenames with multiple consecutive periods (only remove trailing)', () => { + expect(sanitizeName('file.name...')).toBe('file.name'); + expect(sanitizeName('...file')).toBe('file'); + expect(sanitizeName('file.name... ')).toBe('file.name'); + expect(sanitizeName(' ...file')).toBe('file'); + expect(sanitizeName(' ...file ')).toBe('file'); + expect(sanitizeName(' ...file.... ')).toBe('file'); + }); + + it('should handle very long filenames', () => { + const longName = 'a'.repeat(300) + '.txt'; + expect(sanitizeName(longName)).toBe(longName); + }); + + it('should handle names with leading/trailing invalid characters', () => { + expect(sanitizeName('-foo/bar-')).toBe('foo-bar-'); + expect(sanitizeName('/foo\\bar/')).toBe('foo-bar-'); + }); + + }); +}); + +describe('sanitizeName and validateName', () => { + it('should sanitize and then validate valid names', () => { + const validNames = [ + 'valid_filename.txt', + ' valid name ', + ' valid-name ', + 'valid<>name.txt', + 'file/with?invalid*chars' + ]; + + validNames.forEach(name => { + const sanitized = sanitizeName(name); + expect(validateName(sanitized)).toBe(true); + }); + }); + + it('should sanitize and then validate names with reserved device names', () => { + const reservedNames = ['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'LPT2']; + + reservedNames.forEach(name => { + const sanitized = sanitizeName(name); + expect(validateName(sanitized)).toBe(false); + }); + }); + + it('should sanitize invalid names to empty strings', () => { + const invalidNames = [ + ' <>:"/\\|?* ', + ' ... ', + ' ', + ]; + + invalidNames.forEach(name => { + const sanitized = sanitizeName(name); + expect(validateName(sanitized)).toBe(false); + }); + }); + + it('should return false for reserved device names with leading/trailing spaces', () => { + const mixedNames = [ + 'AUX ', + ' COM1 ' + ]; + + mixedNames.forEach(name => { + const sanitized = sanitizeName(name); + expect(validateName(sanitized)).toBe(false); + }); + }); +}); From 1473f5e9b20fba66cbd6c6eb290824c3980183af Mon Sep 17 00:00:00 2001 From: lohxt1 Date: Fri, 20 Sep 2024 18:03:14 +0530 Subject: [PATCH 11/31] feat: added comment for regex --- packages/bruno-app/src/utils/common/regex.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bruno-app/src/utils/common/regex.js b/packages/bruno-app/src/utils/common/regex.js index 5d87e9203a..d0ea8724d0 100644 --- a/packages/bruno-app/src/utils/common/regex.js +++ b/packages/bruno-app/src/utils/common/regex.js @@ -3,8 +3,8 @@ export const variableNameRegex = /^[\w-.]*$/; export const sanitizeName = (name) => { const invalidCharacters = /[<>:"/\\|?*\x00-\x1F]/g; // Match one or more invalid characters return name - .replace(invalidCharacters, '-') // Replace invalid characters with hyphens - .replace(/^[\.\-\s]+|[\.\s]+$/g, ''); + .replace(invalidCharacters, '-') // Replace invalid characters with hyphens + .replace(/^[\.\-\s]+|[\.\s]+$/g, ''); // Remove leading dots, hyphens, and spaces; remove trailing dots and spaces }; export const validateName = (name) => { From f2bc753b587bc1e09225f49d123f39f6f1d4bc47 Mon Sep 17 00:00:00 2001 From: lohxt1 Date: Thu, 26 Sep 2024 17:11:45 +0530 Subject: [PATCH 12/31] feat: tests update --- packages/bruno-app/src/utils/common/regex.spec.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/bruno-app/src/utils/common/regex.spec.js b/packages/bruno-app/src/utils/common/regex.spec.js index 897c3225d7..61cc56f1ab 100644 --- a/packages/bruno-app/src/utils/common/regex.spec.js +++ b/packages/bruno-app/src/utils/common/regex.spec.js @@ -72,7 +72,7 @@ describe('regex validators', () => { expect(sanitizeName('brunfais🐶')).toBe('brunfais🐶'); expect(sanitizeName('file-🐶-bruno')).toBe('file-🐶-bruno'); expect(sanitizeName('helló')).toBe('helló'); - }); + }); it('should preserve case sensitivity', () => { expect(sanitizeName('FileName')).toBe('FileName'); @@ -97,6 +97,17 @@ describe('regex validators', () => { expect(sanitizeName('-foo/bar-')).toBe('foo-bar-'); expect(sanitizeName('/foo\\bar/')).toBe('foo-bar-'); }); + + it('should handle different language unicode characters', () => { + expect(sanitizeName('你好世界!?@#$%^&*()')).toBe('你好世界!-@#$%^&-()'); + expect(sanitizeName('こんにちは世界!?@#$%^&*()')).toBe('こんにちは世界!-@#$%^&-()'); + expect(sanitizeName('안녕하세요 세계!?@#$%^&*()')).toBe('안녕하세요 세계!-@#$%^&-()'); + expect(sanitizeName('مرحبا بالعالم!?@#$%^&*()')).toBe('مرحبا بالعالم!-@#$%^&-()'); + expect(sanitizeName('Здравствуй мир!?@#$%^&*()')).toBe('Здравствуй мир!-@#$%^&-()'); + expect(sanitizeName('नमस्ते दुनिया!?@#$%^&*()')).toBe('नमस्ते दुनिया!-@#$%^&-()'); + expect(sanitizeName('สวัสดีชาวโลก!?@#$%^&*()')).toBe('สวัสดีชาวโลก!-@#$%^&-()'); + expect(sanitizeName('γειά σου κόσμος!?@#$%^&*()')).toBe('γειά σου κόσμος!-@#$%^&-()'); + }); }); }); From 89b11149bfbf31dbd65ea6b11e656577544b2bd3 Mon Sep 17 00:00:00 2001 From: lohxt1 Date: Thu, 14 Nov 2024 10:56:15 +0530 Subject: [PATCH 13/31] fix: updates --- .../Collection/CollectionItem/RenameCollectionItem/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js index c265dfc84b..206a12c294 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/index.js @@ -43,7 +43,7 @@ const RenameCollectionItem = ({ collection, item, onClose }) => { onSubmit: async (values) => { // if there is unsaved changes in the request, // save them before renaming the request - if ((item.name === values.name) && (itemFilename === values.filename) { + if ((item.name === values.name) && (itemFilename === values.filename)) { return; } if (!isFolder && item.draft) { From 34ea352e4ee85e60937ee3c1db85fed583c6d16b Mon Sep 17 00:00:00 2001 From: Pooja Belaramani Date: Tue, 17 Dec 2024 11:26:35 +0530 Subject: [PATCH 14/31] add: document save button --- .../src/components/CollectionSettings/Docs/index.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/bruno-app/src/components/CollectionSettings/Docs/index.js b/packages/bruno-app/src/components/CollectionSettings/Docs/index.js index 18a1aca1dc..f1a66d063e 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Docs/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Docs/index.js @@ -8,6 +8,7 @@ import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/acti import Markdown from 'components/MarkDown'; import CodeEditor from 'components/CodeEditor'; import StyledWrapper from './StyledWrapper'; +import { IconDeviceFloppy } from '@tabler/icons'; const Docs = ({ collection }) => { const dispatch = useDispatch(); @@ -15,6 +16,7 @@ const Docs = ({ collection }) => { const [isEditing, setIsEditing] = useState(false); const docs = get(collection, 'root.docs', ''); const preferences = useSelector((state) => state.app.preferences); + const { theme, storedTheme } = useTheme(); const toggleViewMode = () => { setIsEditing((prev) => !prev); @@ -33,8 +35,13 @@ const Docs = ({ collection }) => { return ( -
+
{isEditing ? 'Preview' : 'Edit'} + {isEditing ? + + : null}
{isEditing ? ( From 96766b6e2fec3555c529a2de6ebc528b43f47e1a Mon Sep 17 00:00:00 2001 From: Pooja Belaramani Date: Tue, 24 Dec 2024 12:09:46 +0530 Subject: [PATCH 15/31] add: save btn in file and folder docs --- .../components/CollectionSettings/Docs/index.js | 11 ++++++++--- .../src/components/Documentation/index.js | 14 +++++++++++++- .../FolderSettings/Documentation/index.js | 15 ++++++++++++++- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/packages/bruno-app/src/components/CollectionSettings/Docs/index.js b/packages/bruno-app/src/components/CollectionSettings/Docs/index.js index f1a66d063e..2674b687bf 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Docs/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Docs/index.js @@ -37,11 +37,16 @@ const Docs = ({ collection }) => {
{isEditing ? 'Preview' : 'Edit'} - {isEditing ? + {isEditing ? ( - : null} + ) : null}
{isEditing ? ( diff --git a/packages/bruno-app/src/components/Documentation/index.js b/packages/bruno-app/src/components/Documentation/index.js index 0af0d7588c..5f472ae1df 100644 --- a/packages/bruno-app/src/components/Documentation/index.js +++ b/packages/bruno-app/src/components/Documentation/index.js @@ -8,6 +8,7 @@ import { saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import Markdown from 'components/MarkDown'; import CodeEditor from 'components/CodeEditor'; import StyledWrapper from './StyledWrapper'; +import { IconDeviceFloppy } from '@tabler/icons'; const Documentation = ({ item, collection }) => { const dispatch = useDispatch(); @@ -15,6 +16,7 @@ const Documentation = ({ item, collection }) => { const [isEditing, setIsEditing] = useState(false); const docs = item.draft ? get(item, 'draft.request.docs') : get(item, 'request.docs'); const preferences = useSelector((state) => state.app.preferences); + const { theme } = useTheme(); const toggleViewMode = () => { setIsEditing((prev) => !prev); @@ -38,8 +40,18 @@ const Documentation = ({ item, collection }) => { return ( -
+
{isEditing ? 'Preview' : 'Edit'} + {isEditing ? ( + + ) : null}
{isEditing ? ( diff --git a/packages/bruno-app/src/components/FolderSettings/Documentation/index.js b/packages/bruno-app/src/components/FolderSettings/Documentation/index.js index 81a3ccd014..8183d0f453 100644 --- a/packages/bruno-app/src/components/FolderSettings/Documentation/index.js +++ b/packages/bruno-app/src/components/FolderSettings/Documentation/index.js @@ -8,6 +8,7 @@ import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions' import Markdown from 'components/MarkDown'; import CodeEditor from 'components/CodeEditor'; import StyledWrapper from './StyledWrapper'; +import { IconDeviceFloppy } from '@tabler/icons'; const Documentation = ({ collection, folder }) => { const dispatch = useDispatch(); @@ -15,6 +16,8 @@ const Documentation = ({ collection, folder }) => { const preferences = useSelector((state) => state.app.preferences); const [isEditing, setIsEditing] = useState(false); const docs = get(folder, 'root.docs', ''); + const { theme } = useTheme(); + const toggleViewMode = () => { setIsEditing((prev) => !prev); @@ -38,8 +41,18 @@ const Documentation = ({ collection, folder }) => { return ( -
+
{isEditing ? 'Preview' : 'Edit'} + {isEditing ? ( + + ) : null}
{isEditing ? ( From d3fd0eb3c6927eab861f9c38f9a2101d2c63ba8f Mon Sep 17 00:00:00 2001 From: Pooja Belaramani Date: Tue, 31 Dec 2024 16:31:14 +0530 Subject: [PATCH 16/31] adding save button for folder and collection --- .../CollectionSettings/Docs/StyledWrapper.js | 3 -- .../CollectionSettings/Docs/index.js | 37 ++++++++----------- .../src/components/Documentation/index.js | 10 ----- .../FolderSettings/Documentation/index.js | 37 ++++++++----------- 4 files changed, 32 insertions(+), 55 deletions(-) diff --git a/packages/bruno-app/src/components/CollectionSettings/Docs/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Docs/StyledWrapper.js index f0ffee808e..262f068e78 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Docs/StyledWrapper.js +++ b/packages/bruno-app/src/components/CollectionSettings/Docs/StyledWrapper.js @@ -2,9 +2,6 @@ import styled from 'styled-components'; const StyledWrapper = styled.div` div.CodeMirror { - /* todo: find a better way */ - height: calc(100vh - 240px); - .CodeMirror-scroll { padding-bottom: 0px; } diff --git a/packages/bruno-app/src/components/CollectionSettings/Docs/index.js b/packages/bruno-app/src/components/CollectionSettings/Docs/index.js index 2674b687bf..a95ce6ba50 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Docs/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Docs/index.js @@ -34,32 +34,27 @@ const Docs = ({ collection }) => { const onSave = () => dispatch(saveCollectionRoot(collection.uid)); return ( - +
{isEditing ? 'Preview' : 'Edit'} - {isEditing ? ( - - ) : null}
{isEditing ? ( - +
+ + +
) : ( )} diff --git a/packages/bruno-app/src/components/Documentation/index.js b/packages/bruno-app/src/components/Documentation/index.js index 5f472ae1df..fd7b6339b4 100644 --- a/packages/bruno-app/src/components/Documentation/index.js +++ b/packages/bruno-app/src/components/Documentation/index.js @@ -42,16 +42,6 @@ const Documentation = ({ item, collection }) => {
{isEditing ? 'Preview' : 'Edit'} - {isEditing ? ( - - ) : null}
{isEditing ? ( diff --git a/packages/bruno-app/src/components/FolderSettings/Documentation/index.js b/packages/bruno-app/src/components/FolderSettings/Documentation/index.js index 8183d0f453..58b2106aef 100644 --- a/packages/bruno-app/src/components/FolderSettings/Documentation/index.js +++ b/packages/bruno-app/src/components/FolderSettings/Documentation/index.js @@ -40,32 +40,27 @@ const Documentation = ({ collection, folder }) => { } return ( - +
{isEditing ? 'Preview' : 'Edit'} - {isEditing ? ( - - ) : null}
{isEditing ? ( - +
+ + +
) : ( )} From f3a49cb58a3ea46e3d09954b689b1fc419df59bf Mon Sep 17 00:00:00 2001 From: Pooja Belaramani Date: Fri, 3 Jan 2025 14:05:42 +0530 Subject: [PATCH 17/31] adding: collection draft --- .../Auth/ApiKeyAuth/index.js | 2 +- .../CollectionSettings/Auth/AuthMode/index.js | 2 +- .../Auth/AwsV4Auth/index.js | 2 +- .../Auth/BasicAuth/index.js | 2 +- .../Auth/BearerAuth/index.js | 2 +- .../Auth/DigestAuth/index.js | 2 +- .../Auth/OAuth2/AuthorizationCode/index.js | 2 +- .../Auth/OAuth2/ClientCredentials/index.js | 2 +- .../Auth/OAuth2/GrantTypeSelector/index.js | 2 +- .../CollectionSettings/Auth/OAuth2/index.js | 2 +- .../CollectionSettings/Auth/WsseAuth/index.js | 2 +- .../CollectionSettings/Auth/index.js | 2 +- .../CollectionSettings/Docs/index.js | 2 +- .../CollectionSettings/Headers/index.js | 2 +- .../CollectionSettings/Script/index.js | 4 +- .../CollectionSettings/Tests/index.js | 2 +- .../CollectionSettings/Vars/index.js | 4 +- .../components/CollectionSettings/index.js | 10 +- .../ReduxStore/slices/collections/actions.js | 8 +- .../ReduxStore/slices/collections/index.js | 171 ++++++++++++------ .../bruno-app/src/utils/collections/index.js | 26 +++ 21 files changed, 172 insertions(+), 81 deletions(-) diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/ApiKeyAuth/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/ApiKeyAuth/index.js index 74d348c648..7d365ae170 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/ApiKeyAuth/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/ApiKeyAuth/index.js @@ -16,7 +16,7 @@ const ApiKeyAuth = ({ collection }) => { const dropdownTippyRef = useRef(); const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); - const apikeyAuth = get(collection, 'root.request.auth.apikey', {}); + const apikeyAuth = collection.draft ? get(collection, 'draft.request.auth.apikey', {}) : get(collection, 'root.request.auth.apikey', {}); const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js index 7dabb4c71a..dacd8950ac 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js @@ -11,7 +11,7 @@ const AuthMode = ({ collection }) => { const dispatch = useDispatch(); const dropdownTippyRef = useRef(); const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); - const authMode = get(collection, 'root.request.auth.mode'); + const authMode = collection.draft ? get(collection, 'draft.request.auth.mode') : get(collection, 'root.request.auth.mode'); const Icon = forwardRef((props, ref) => { return ( diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/AwsV4Auth/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/AwsV4Auth/index.js index 38fae3447e..da30225143 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/AwsV4Auth/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/AwsV4Auth/index.js @@ -11,7 +11,7 @@ const AwsV4Auth = ({ collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); - const awsv4Auth = get(collection, 'root.request.auth.awsv4', {}); + const awsv4Auth = collection.draft ? get(collection, 'draft..request.auth.awsv4', {}) : get(collection, 'root.request.auth.awsv4', {}); const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/BasicAuth/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/BasicAuth/index.js index 3c29895edc..d8c07753ea 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/BasicAuth/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/BasicAuth/index.js @@ -11,7 +11,7 @@ const BasicAuth = ({ collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); - const basicAuth = get(collection, 'root.request.auth.basic', {}); + const basicAuth = collection.draft ? get(collection, 'draft.request.auth.basic', {}) : get(collection, 'root.request.auth.basic', {}); const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/BearerAuth/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/BearerAuth/index.js index 82f8be12c8..a7a9638196 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/BearerAuth/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/BearerAuth/index.js @@ -11,7 +11,7 @@ const BearerAuth = ({ collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); - const bearerToken = get(collection, 'root.request.auth.bearer.token', ''); + const bearerToken = collection.draft ? get(collection, 'draft.request.auth.bearer.token', '') : get(collection, 'root.request.auth.bearer.token', ''); const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/DigestAuth/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/DigestAuth/index.js index 5ac6b1e263..18db037539 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/DigestAuth/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/DigestAuth/index.js @@ -11,7 +11,7 @@ const DigestAuth = ({ collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); - const digestAuth = get(collection, 'root.request.auth.digest', {}); + const digestAuth = collection.draft ? get(collection, 'draft..request.auth.digest', {}) : get(collection, 'root.request.auth.digest', {}); const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/AuthorizationCode/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/AuthorizationCode/index.js index 8f3dc16018..3539d510e2 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/AuthorizationCode/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/AuthorizationCode/index.js @@ -14,7 +14,7 @@ const OAuth2AuthorizationCode = ({ collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); - const oAuth = get(collection, 'root.request.auth.oauth2', {}); + const oAuth = collection.draft ? get(collection, 'draft.request.auth.oauth', {}) : get(collection, 'root.request.auth.oauth2', {}); const handleRun = async () => { dispatch(sendCollectionOauth2Request(collection.uid)); diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/ClientCredentials/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/ClientCredentials/index.js index d69122b482..aaae3b5b22 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/ClientCredentials/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/ClientCredentials/index.js @@ -12,7 +12,7 @@ const OAuth2ClientCredentials = ({ collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); - const oAuth = get(collection, 'root.request.auth.oauth2', {}); + const oAuth = collection.draft ? get(collection, 'draft.request.auth.oauth', {}) : get(collection, 'root.request.auth.oauth2', {}); const handleRun = async () => { dispatch(sendCollectionOauth2Request(collection.uid)); diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/GrantTypeSelector/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/GrantTypeSelector/index.js index 5d92893820..8b91e3a2a4 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/GrantTypeSelector/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/GrantTypeSelector/index.js @@ -14,7 +14,7 @@ const GrantTypeSelector = ({ collection }) => { const dropdownTippyRef = useRef(); const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); - const oAuth = get(collection, 'root.request.auth.oauth2', {}); + const oAuth = collection.draft ? get(collection, "draft.request.auth.oauth2", {}) : get(collection, 'root.request.auth.oauth2', {}); const Icon = forwardRef((props, ref) => { return ( diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/index.js index 1aa674ab95..2bf906a120 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/index.js @@ -24,7 +24,7 @@ const grantTypeComponentMap = (grantType, collection) => { }; const OAuth2 = ({ collection }) => { - const oAuth = get(collection, 'root.request.auth.oauth2', {}); + const oAuth = collection.draft ? get(collection, 'draft.request.auth.oauth', {}) : get(collection, 'root.request.auth.oauth2', {}); return ( diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/WsseAuth/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/WsseAuth/index.js index 45efc7b1eb..a26319fbce 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/WsseAuth/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/WsseAuth/index.js @@ -11,7 +11,7 @@ const WsseAuth = ({ collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); - const wsseAuth = get(collection, 'root.request.auth.wsse', {}); + const wsseAuth = collection.draft ? get(collection, 'draft.request.auth.wsse', {}) : get(collection, 'root.request.auth.wsse', {}); const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/index.js index 05efc17b23..ee5e2def29 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/index.js @@ -13,7 +13,7 @@ import StyledWrapper from './StyledWrapper'; import OAuth2 from './OAuth2'; const Auth = ({ collection }) => { - const authMode = get(collection, 'root.request.auth.mode'); + const authMode = collection.draft ? get(collection, 'draft.request.auth.mode') : get(collection, 'root.request.auth.mode'); const dispatch = useDispatch(); const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); diff --git a/packages/bruno-app/src/components/CollectionSettings/Docs/index.js b/packages/bruno-app/src/components/CollectionSettings/Docs/index.js index a95ce6ba50..0c00c1927b 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Docs/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Docs/index.js @@ -14,7 +14,7 @@ const Docs = ({ collection }) => { const dispatch = useDispatch(); const { displayedTheme } = useTheme(); const [isEditing, setIsEditing] = useState(false); - const docs = get(collection, 'root.docs', ''); + const docs = get(collection, 'draft.docs') || get(collection, 'root.docs', ''); const preferences = useSelector((state) => state.app.preferences); const { theme, storedTheme } = useTheme(); diff --git a/packages/bruno-app/src/components/CollectionSettings/Headers/index.js b/packages/bruno-app/src/components/CollectionSettings/Headers/index.js index 9ae6e1e074..c1bc16ca20 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Headers/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Headers/index.js @@ -19,7 +19,7 @@ const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header); const Headers = ({ collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); - const headers = get(collection, 'root.request.headers', []); + const headers = collection.draft ? get(collection, 'draft.request.headers') : get(collection, 'root.request.headers', []); const addHeader = () => { dispatch( diff --git a/packages/bruno-app/src/components/CollectionSettings/Script/index.js b/packages/bruno-app/src/components/CollectionSettings/Script/index.js index 6fe979cbfa..839b79afe6 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Script/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Script/index.js @@ -9,8 +9,8 @@ import StyledWrapper from './StyledWrapper'; const Script = ({ collection }) => { const dispatch = useDispatch(); - const requestScript = get(collection, 'root.request.script.req', ''); - const responseScript = get(collection, 'root.request.script.res', ''); + const requestScript = collection.draft ? get(collection, 'draft.request.script.res', '') : get(collection, 'root.request.script.req', ''); + const responseScript = collection.draft ? get(collection, 'draft.request.script.res', '') : get(collection, 'root.request.script.res', ''); const { displayedTheme } = useTheme(); const preferences = useSelector((state) => state.app.preferences); diff --git a/packages/bruno-app/src/components/CollectionSettings/Tests/index.js b/packages/bruno-app/src/components/CollectionSettings/Tests/index.js index d87a1dea4f..2ceb96e6f3 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Tests/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Tests/index.js @@ -9,7 +9,7 @@ import StyledWrapper from './StyledWrapper'; const Tests = ({ collection }) => { const dispatch = useDispatch(); - const tests = get(collection, 'root.request.tests', ''); + const tests = collection.draft ? get(collection, 'draft.request.tests', '') : get(collection, 'root.request.tests', ''); const { displayedTheme } = useTheme(); const preferences = useSelector((state) => state.app.preferences); diff --git a/packages/bruno-app/src/components/CollectionSettings/Vars/index.js b/packages/bruno-app/src/components/CollectionSettings/Vars/index.js index fae3ed6132..06255d639b 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Vars/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Vars/index.js @@ -7,8 +7,8 @@ import { useDispatch } from 'react-redux'; const Vars = ({ collection }) => { const dispatch = useDispatch(); - const requestVars = get(collection, 'root.request.vars.req', []); - const responseVars = get(collection, 'root.request.vars.res', []); + const requestVars = collection.draft ? get(collection, 'draft.request.vars.req', []) : get(collection, 'root.request.vars.req', []); + const responseVars = collection.draft ? get(collection, 'draft.request.vars.res', []) : get(collection, 'root.request.vars.res', []); const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); return ( diff --git a/packages/bruno-app/src/components/CollectionSettings/index.js b/packages/bruno-app/src/components/CollectionSettings/index.js index b849d6b185..e2d3dac1c7 100644 --- a/packages/bruno-app/src/components/CollectionSettings/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/index.js @@ -44,13 +44,13 @@ const CollectionSettings = ({ collection }) => { const hasTests = root?.request?.tests; const hasDocs = root?.docs; - const headers = get(collection, 'root.request.headers', []); + const headers = collection.draft ? get(collection, 'draft.request.headers') : get(collection, 'root.request.headers', []); const activeHeadersCount = headers.filter((header) => header.enabled).length; - const requestVars = get(collection, 'root.request.vars.req', []); - const responseVars = get(collection, 'root.request.vars.res', []); + const requestVars = collection.draft ? get(collection, 'draft.request.vars.req', []) : get(collection, 'root.request.vars.req', []); + const responseVars = collection.draft ? get(collection, 'draft.request.vars.res', []) : get(collection, 'root.request.vars.res', []); const activeVarsCount = requestVars.filter((v) => v.enabled).length + responseVars.filter((v) => v.enabled).length; - const auth = get(collection, 'root.request.auth', {}).mode; + const auth = collection.draft ? get(collection, 'draft.request,auth', {}).mode : get(collection, 'root.request.auth', {}).mode; const proxyConfig = get(collection, 'brunoConfig.proxy', {}); const clientCertConfig = get(collection, 'brunoConfig.clientCertificates.certs', []); @@ -142,7 +142,7 @@ const CollectionSettings = ({ collection }) => { active: tabName === tab }); }; - + return (
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 75c6f2cb90..3f89e4e700 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -44,7 +44,7 @@ import { parsePathParams, parseQueryParams, splitOnFirst } from 'utils/url/index import { sendCollectionOauth2Request as _sendCollectionOauth2Request } from 'utils/network/index'; import { name } from 'file-loader'; import slash from 'utils/common/slash'; -import { getGlobalEnvironmentVariables } from 'utils/collections/index'; +import { getGlobalEnvironmentVariables, transformCollectionRootToSave } from 'utils/collections/index'; import { findCollectionByPathname, findEnvironmentInCollectionByName } from 'utils/collections/index'; export const renameCollection = (newName, collectionUid) => (dispatch, getState) => { @@ -130,6 +130,10 @@ export const saveCollectionRoot = (collectionUid) => (dispatch, getState) => { const state = getState(); const collection = findCollectionByUid(state.collections.collections, collectionUid); + const transformRoot = transformCollectionRootToSave(collection); + + console.log({ transformRoot }); + return new Promise((resolve, reject) => { if (!collection) { return reject(new Error('Collection not found')); @@ -138,7 +142,7 @@ export const saveCollectionRoot = (collectionUid) => (dispatch, getState) => { const { ipcRenderer } = window; ipcRenderer - .invoke('renderer:save-collection-root', collection.pathname, collection.root) + .invoke('renderer:save-collection-root', collection.pathname, transformRoot) .then(() => toast.success('Collection Settings saved successfully')) .then(resolve) .catch((err) => { 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 2b5a532c33..9604c985ec 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -34,7 +34,19 @@ export const collectionsSlice = createSlice({ collection.settingsSelectedTab = 'headers'; collection.folderLevelSettingsSelectedTab = {}; - + collection.root = collection.root || { + docs: "", + request: { + auth: { + mode: "none" + }, + headers: [], + script: { req: null, res: null }, + tests: null, + } + } + collection.draft = null; + // TODO: move this to use the nextAction approach // last action is used to track the last action performed on the collection // this is optional @@ -1119,39 +1131,49 @@ export const collectionsSlice = createSlice({ }, updateCollectionAuthMode: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload.collectionUid); + const collectionRoot = collection.root || {}; if (collection) { - set(collection, 'root.request.auth', {}); - set(collection, 'root.request.auth.mode', action.payload.mode); + if (!collection.draft) { + collection.draft = cloneDeep(collectionRoot); + } + + collection.draft.request.auth = {}; + collection.draft.request.auth.mode = action.payload.mode; } }, updateCollectionAuth: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload.collectionUid); + const collectionRoot = collection.root || {}; if (collection) { - set(collection, 'root.request.auth', {}); - set(collection, 'root.request.auth.mode', action.payload.mode); + if (!collection.draft) { + collection.draft = cloneDeep(collectionRoot); + } + + collection.draft.request.auth = {}; + collection.draft.request.auth.mode = action.payload.mode; switch (action.payload.mode) { case 'awsv4': - set(collection, 'root.request.auth.awsv4', action.payload.content); + collection.draft.request.auth.awsv4 = action.payload.content; break; case 'bearer': - set(collection, 'root.request.auth.bearer', action.payload.content); + collection.draft.request.auth.bearer = action.payload.content; break; case 'basic': - set(collection, 'root.request.auth.basic', action.payload.content); + collection.draft.request.auth.basic = action.payload.content; break; case 'digest': - set(collection, 'root.request.auth.digest', action.payload.content); + collection.draft.request.auth.digest = action.payload.content; break; case 'oauth2': - set(collection, 'root.request.auth.oauth2', action.payload.content); + collection.draft.request.auth.oauth2 = action.payload.content; break; case 'wsse': - set(collection, 'root.request.auth.wsse', action.payload.content); + collection.draft.request.auth.wsse = action.payload.content; break; case 'apikey': - set(collection, 'root.request.auth.apikey', action.payload.content); + collection.draft.request.auth.apikey = action.payload.content; break; } } @@ -1160,28 +1182,41 @@ export const collectionsSlice = createSlice({ const collection = findCollectionByUid(state.collections, action.payload.collectionUid); if (collection) { - set(collection, 'root.request.script.req', action.payload.script); + if(!collection.draft) { + collection.draft = cloneDeep(collection.root); + } + collection.draft.request.script.req = action.payload.script; } }, updateCollectionResponseScript: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload.collectionUid); if (collection) { - set(collection, 'root.request.script.res', action.payload.script); + if(!collection.draft) { + collection.draft = cloneDeep(collection.root); + } + collection.draft.request.script.res = action.payload.script; } }, updateCollectionTests: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload.collectionUid); if (collection) { - set(collection, 'root.request.tests', action.payload.tests); + if(!collection.draft) { + collection.draft = cloneDeep(collection.root); + } + collection.draft.request.tests = action.payload.tests; } }, updateCollectionDocs: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload.collectionUid); + const collectionRoot = collection.root || {}; if (collection) { - set(collection, 'root.docs', action.payload.docs); + if (!collection.draft) { + collection.draft = cloneDeep(collectionRoot); + } + collection.draft.docs = action.payload.docs; } }, addFolderHeader: (state, action) => { @@ -1317,22 +1352,28 @@ export const collectionsSlice = createSlice({ const collection = findCollectionByUid(state.collections, action.payload.collectionUid); if (collection) { - const headers = get(collection, 'root.request.headers', []); - headers.push({ + if(!collection.draft) { + collection.draft = cloneDeep(collection.root); + } + collection.draft.request.headers = collection.draft.request.headers || []; + collection.draft.request.headers.push({ uid: uuid(), name: '', value: '', description: '', enabled: true }); - set(collection, 'root.request.headers', headers); } }, updateCollectionHeader: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload.collectionUid); if (collection) { - const headers = get(collection, 'root.request.headers', []); + if(!collection.draft) { + collection.draft = cloneDeep(collection.root); + } + + const headers = collection.draft.request.headers; const header = find(headers, (h) => h.uid === action.payload.header.uid); if (header) { header.name = action.payload.header.name; @@ -1346,73 +1387,93 @@ export const collectionsSlice = createSlice({ const collection = findCollectionByUid(state.collections, action.payload.collectionUid); if (collection) { - let headers = get(collection, 'root.request.headers', []); - headers = filter(headers, (h) => h.uid !== action.payload.headerUid); - set(collection, 'root.request.headers', headers); + if(!collection.draft) { + collection.draft = cloneDeep(collection.root); + } + collection.draft.request.headers = filter(collection.draft.request.headers, (h) => h.uid !== action.payload.headerUid); } }, addCollectionVar: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload.collectionUid); const type = action.payload.type; if (collection) { + + if(!collection.draft) { + collection.draft = cloneDeep(collection.root); + } + if (type === 'request') { - const vars = get(collection, 'root.request.vars.req', []); - vars.push({ + collection.draft.request.vars = collection.draft.request.vars || {}; + collection.draft.request.vars.req = collection.draft.request.vars.req || []; + collection.draft.request.vars.req.push({ uid: uuid(), name: '', value: '', enabled: true }); - set(collection, 'root.request.vars.req', vars); } else if (type === 'response') { - const vars = get(collection, 'root.request.vars.res', []); - vars.push({ + collection.draft.request.vars = collection.draft.request.vars || {}; + collection.draft.request.vars.res = collection.draft.request.vars.res || []; + collection.draft.request.vars.res.push({ uid: uuid(), name: '', value: '', enabled: true }); - set(collection, 'root.request.vars.res', vars); } } }, updateCollectionVar: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload.collectionUid); const type = action.payload.type; - if (type === 'request') { - let vars = get(collection, 'root.request.vars.req', []); - const _var = find(vars, (h) => h.uid === action.payload.var.uid); - if (_var) { - _var.name = action.payload.var.name; - _var.value = action.payload.var.value; - _var.description = action.payload.var.description; - _var.enabled = action.payload.var.enabled; - } - set(collection, 'root.request.vars.req', vars); - } else if (type === 'response') { - let vars = get(collection, 'root.request.vars.res', []); - const _var = find(vars, (h) => h.uid === action.payload.var.uid); - if (_var) { - _var.name = action.payload.var.name; - _var.value = action.payload.var.value; - _var.description = action.payload.var.description; - _var.enabled = action.payload.var.enabled; - } - set(collection, 'root.request.vars.res', vars); + + if (collection) { + + if(!collection.draft) { + collection.draft = cloneDeep(collection.root); + } + + if (type === 'request') { + collection.draft.request.vars = collection.draft.request.vars || {}; + collection.draft.request.vars.req = collection.draft.request.vars.req || []; + const reqVar = find(collection.draft.request.vars.req, (v) => v.uid === action.payload.var.uid); + if (reqVar) { + reqVar.name = action.payload.var.name; + reqVar.value = action.payload.var.value; + reqVar.description = action.payload.var.description; + reqVar.enabled = action.payload.var.enabled; + } + } else if (type === 'response') { + collection.draft.request.vars = collection.draft.request.vars || {}; + collection.draft.request.vars.res = collection.draft.request.vars.res || []; + const resVar = find(collection.draft.request.vars.res, (v) => v.uid === action.payload.var.uid); + if (resVar) { + resVar.name = action.payload.var.name; + resVar.value = action.payload.var.value; + resVar.description = action.payload.var.description; + resVar.enabled = action.payload.var.enabled; + } + } } }, deleteCollectionVar: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload.collectionUid); const type = action.payload.type; + if (collection) { + + if(!collection.draft) { + collection.draft = cloneDeep(collection.root); + } + if (type === 'request') { - let vars = get(collection, 'root.request.vars.req', []); - vars = filter(vars, (h) => h.uid !== action.payload.varUid); - set(collection, 'root.request.vars.req', vars); + collection.draft.request.vars = collection.draft.request.vars || {}; + collection.draft.request.vars.req = collection.draft.request.vars.req || []; + collection.draft.request.vars.req = collection.draft.request.vars.req.filter((v) => v.uid !== action.payload.varUid); } else if (type === 'response') { - let vars = get(collection, 'root.request.vars.res', []); - vars = filter(vars, (h) => h.uid !== action.payload.varUid); - set(collection, 'root.request.vars.res', vars); + collection.draft.request.vars = collection.draft.request.vars || {}; + collection.draft.request.vars.res = collection.draft.request.vars.res || []; + collection.draft.request.vars.res = collection.draft.request.vars.res.filter((v) => v.uid !== action.payload.varUid); } } }, diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index 3a691e53f7..012e3e2657 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -594,6 +594,32 @@ export const transformRequestToSaveToFilesystem = (item) => { return itemToSave; }; +export const transformCollectionRootToSave = (collection) => { + const _collection = collection.draft ? collection.draft : collection.root; + const collectionRootToSave = { + docs: _collection.docs, + request: { + auth: _collection?.request?.auth, + headers: [], + script: _collection?.request?.script, + vars: _collection?.request?.vars, + tests: _collection?.request?.tests + } + } + + each(_collection.request.headers, (header) => { + collectionRootToSave.request.headers.push({ + uid: header.uid, + name: header.name, + value: header.value, + description: header.description, + enabled: header.enabled + }); + }); + + return collectionRootToSave; +} + // todo: optimize this export const deleteItemInCollection = (itemUid, collection) => { collection.items = filter(collection.items, (i) => i.uid !== itemUid); From 10757acde7f4e06d3e288ef067703960c81a60c3 Mon Sep 17 00:00:00 2001 From: Pooja Belaramani Date: Fri, 3 Jan 2025 15:41:46 +0530 Subject: [PATCH 18/31] add: collection closing modal --- .../ConfirmCollectionClose/index.js | 71 +++++++++++++++++++ .../RequestTabs/RequestTab/SpecialTab.js | 8 ++- .../RequestTabs/RequestTab/index.js | 29 +++++++- .../ReduxStore/slices/collections/actions.js | 2 - .../ReduxStore/slices/collections/index.js | 8 +++ 5 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 packages/bruno-app/src/components/RequestTabs/RequestTab/ConfirmCollectionClose/index.js diff --git a/packages/bruno-app/src/components/RequestTabs/RequestTab/ConfirmCollectionClose/index.js b/packages/bruno-app/src/components/RequestTabs/RequestTab/ConfirmCollectionClose/index.js new file mode 100644 index 0000000000..d568bd0529 --- /dev/null +++ b/packages/bruno-app/src/components/RequestTabs/RequestTab/ConfirmCollectionClose/index.js @@ -0,0 +1,71 @@ +import React from 'react'; +import { IconAlertTriangle } from '@tabler/icons'; +import Modal from 'components/Modal'; +import { useDispatch } from 'react-redux'; +import { deleteCollectionDraft } from 'providers/ReduxStore/slices/collections/index'; +import { closeTabs } from 'providers/ReduxStore/slices/tabs'; +import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions'; + +const ConfirmCollectionClose = ({ onCancel, collection, tab }) => { + + const dispatch = useDispatch(); + + return ( + { + e.stopPropagation(); + e.preventDefault(); + }} + hideFooter={true} + > +
+ +

Hold on..

+
+
+ You have unsaved changes in request Collection. +
+ +
+
+ +
+
+ + +
+
+
+ ); +}; + +export default ConfirmCollectionClose; diff --git a/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js b/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js index c5d09faa8d..c9168f67c8 100644 --- a/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js +++ b/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js @@ -1,8 +1,9 @@ import React from 'react'; import CloseTabIcon from './CloseTabIcon'; import { IconVariable, IconSettings, IconRun, IconFolder, IconShieldLock } from '@tabler/icons'; +import DraftTabIcon from './DraftTabIcon'; -const SpecialTab = ({ handleCloseClick, type, tabName }) => { +const SpecialTab = ({ handleCloseClick, type, tabName, collection }) => { const getTabInfo = (type, tabName) => { switch (type) { case 'collection-settings': { @@ -52,7 +53,10 @@ const SpecialTab = ({ handleCloseClick, type, tabName }) => { <>
{getTabInfo(type, tabName)}
handleCloseClick(e)}> - + {type === 'collection-settings' && collection?.draft !== null ? + + : + }
); diff --git a/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js b/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js index e73313c139..e4f1b84513 100644 --- a/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js +++ b/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js @@ -18,11 +18,13 @@ import NewRequest from 'components/Sidebar/NewRequest/index'; import CloseTabIcon from './CloseTabIcon'; import DraftTabIcon from './DraftTabIcon'; import { flattenItems } from 'utils/collections/index'; +import ConfirmCollectionClose from './ConfirmCollectionClose/index'; const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUid }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); const [showConfirmClose, setShowConfirmClose] = useState(false); + const [showConfirmCloseCollection, setShowConfirmCloseCollection] = useState(false); const dropdownTippyRef = useRef(); const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); @@ -37,6 +39,16 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi ); }; + const handleCloseCollectionSettings = (event) => { + if(!collection.draft) { + return handleCloseClick(event); + } + + event.stopPropagation(); + event.preventDefault(); + setShowConfirmCloseCollection(true); + } + const handleRightClick = (_event) => { const menuDropdown = dropdownTippyRef.current; if (!menuDropdown) { @@ -70,7 +82,22 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi }; const folder = folderUid ? findItemInCollection(collection, folderUid) : null; - if (['collection-settings', 'folder-settings', 'variables', 'collection-runner', 'security-settings'].includes(tab.type)) { + + if(tab.type === 'collection-settings') { + return ( + + {showConfirmCloseCollection && ( + setShowConfirmCloseCollection(false)} collection={collection} tab={tab} /> + )} + + + ); + } + + if (['folder-settings', 'variables', 'collection-runner', 'security-settings'].includes(tab.type)) { return ( (dispatch, getState) => { const transformRoot = transformCollectionRootToSave(collection); - console.log({ transformRoot }); - return new Promise((resolve, reject) => { if (!collection) { return reject(new Error('Collection not found')); 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 9604c985ec..e5e63ffab4 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -62,6 +62,13 @@ export const collectionsSlice = createSlice({ state.collections.push(collection); } }, + deleteCollectionDraft: (state, action) => { + const collection = findCollectionByUid(state.collections, action.payload.collectionUid); + + if (collection) { + collection.draft = null; + } + }, setCollectionSecurityConfig: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload.collectionUid); if (collection) { @@ -1811,6 +1818,7 @@ export const collectionsSlice = createSlice({ export const { createCollection, + deleteCollectionDraft, setCollectionSecurityConfig, brunoConfigUpdateEvent, renameCollection, From 872223fd1b6ad10d18db8f0a5dd86ca66bd44861 Mon Sep 17 00:00:00 2001 From: Pooja Belaramani Date: Fri, 3 Jan 2025 17:49:12 +0530 Subject: [PATCH 19/31] add: folder draft --- .../FolderSettings/Documentation/index.js | 2 +- .../FolderSettings/Headers/index.js | 2 +- .../components/FolderSettings/Script/index.js | 4 +- .../components/FolderSettings/Tests/index.js | 2 +- .../components/FolderSettings/Vars/index.js | 4 +- .../RequestTab/ConfirmFolderClose/index.js | 70 +++++++++++ .../RequestTabs/RequestTab/SpecialTab.js | 7 +- .../RequestTabs/RequestTab/index.js | 34 +++++- .../ReduxStore/slices/collections/actions.js | 8 +- .../ReduxStore/slices/collections/index.js | 111 +++++++++++++----- .../bruno-app/src/utils/collections/index.js | 26 ++++ 11 files changed, 220 insertions(+), 50 deletions(-) create mode 100644 packages/bruno-app/src/components/RequestTabs/RequestTab/ConfirmFolderClose/index.js diff --git a/packages/bruno-app/src/components/FolderSettings/Documentation/index.js b/packages/bruno-app/src/components/FolderSettings/Documentation/index.js index 58b2106aef..9253bb6967 100644 --- a/packages/bruno-app/src/components/FolderSettings/Documentation/index.js +++ b/packages/bruno-app/src/components/FolderSettings/Documentation/index.js @@ -15,7 +15,7 @@ const Documentation = ({ collection, folder }) => { const { displayedTheme } = useTheme(); const preferences = useSelector((state) => state.app.preferences); const [isEditing, setIsEditing] = useState(false); - const docs = get(folder, 'root.docs', ''); + const docs = folder.draft ? get(folder, 'draft.docs', '') : get(folder, 'root.docs', ''); const { theme } = useTheme(); diff --git a/packages/bruno-app/src/components/FolderSettings/Headers/index.js b/packages/bruno-app/src/components/FolderSettings/Headers/index.js index 0f6e05f1fa..098a0fc622 100644 --- a/packages/bruno-app/src/components/FolderSettings/Headers/index.js +++ b/packages/bruno-app/src/components/FolderSettings/Headers/index.js @@ -14,7 +14,7 @@ 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 headers = folder.draft ? get(folder, 'draft.request.headers', []) : get(folder, 'root.request.headers', []); const addHeader = () => { dispatch( diff --git a/packages/bruno-app/src/components/FolderSettings/Script/index.js b/packages/bruno-app/src/components/FolderSettings/Script/index.js index 628fa5cb51..81c6e0dd1a 100644 --- a/packages/bruno-app/src/components/FolderSettings/Script/index.js +++ b/packages/bruno-app/src/components/FolderSettings/Script/index.js @@ -9,8 +9,8 @@ import StyledWrapper from './StyledWrapper'; const Script = ({ collection, folder }) => { const dispatch = useDispatch(); - const requestScript = get(folder, 'root.request.script.req', ''); - const responseScript = get(folder, 'root.request.script.res', ''); + const requestScript = folder.draft ? get(folder, 'draft.request.script.req', '') : get(folder, 'root.request.script.req', ''); + const responseScript = folder.draft ? get(folder, 'draft.request.script.res', '') : get(folder, 'root.request.script.res', ''); const { displayedTheme } = useTheme(); const preferences = useSelector((state) => state.app.preferences); diff --git a/packages/bruno-app/src/components/FolderSettings/Tests/index.js b/packages/bruno-app/src/components/FolderSettings/Tests/index.js index 8854b06cd9..6a6cc7f74c 100644 --- a/packages/bruno-app/src/components/FolderSettings/Tests/index.js +++ b/packages/bruno-app/src/components/FolderSettings/Tests/index.js @@ -9,7 +9,7 @@ import StyledWrapper from './StyledWrapper'; const Tests = ({ collection, folder }) => { const dispatch = useDispatch(); - const tests = get(folder, 'root.request.tests', ''); + const tests = folder.draft ? get(folder, 'draft.request.tests', '') : get(folder, 'root.request.tests', ''); const { displayedTheme } = useTheme(); const preferences = useSelector((state) => state.app.preferences); diff --git a/packages/bruno-app/src/components/FolderSettings/Vars/index.js b/packages/bruno-app/src/components/FolderSettings/Vars/index.js index 8f9cab4d23..a9f4584fc0 100644 --- a/packages/bruno-app/src/components/FolderSettings/Vars/index.js +++ b/packages/bruno-app/src/components/FolderSettings/Vars/index.js @@ -7,8 +7,8 @@ import { useDispatch } from 'react-redux'; const Vars = ({ collection, folder }) => { const dispatch = useDispatch(); - const requestVars = get(folder, 'root.request.vars.req', []); - const responseVars = get(folder, 'root.request.vars.res', []); + const requestVars = folder.draft ? get(folder, 'draft.request.vars.req', []) : get(folder, 'root.request.vars.req', []); + const responseVars = folder.draft ? get(folder, 'draft.request.vars.res', []) : get(folder, 'root.request.vars.res', []); const handleSave = () => dispatch(saveFolderRoot(collection.uid, folder.uid)); return ( diff --git a/packages/bruno-app/src/components/RequestTabs/RequestTab/ConfirmFolderClose/index.js b/packages/bruno-app/src/components/RequestTabs/RequestTab/ConfirmFolderClose/index.js new file mode 100644 index 0000000000..584a26fa78 --- /dev/null +++ b/packages/bruno-app/src/components/RequestTabs/RequestTab/ConfirmFolderClose/index.js @@ -0,0 +1,70 @@ +import React from 'react'; +import { IconAlertTriangle } from '@tabler/icons'; +import Modal from 'components/Modal'; +import { closeTabs } from 'providers/ReduxStore/slices/tabs'; +import { useDispatch } from 'react-redux'; +import { deleteFolderDraft } from 'providers/ReduxStore/slices/collections/index'; +import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions'; + +const ConfirmFolderClose = ({ onCancel, collection, folder, tab }) => { + const dispatch = useDispatch(); + + return ( + { + e.stopPropagation(); + e.preventDefault(); + }} + hideFooter={true} + > +
+ +

Hold on..

+
+
+ You have unsaved changes in folder {folder.name}. +
+ +
+
+ +
+
+ + +
+
+
+ ); +}; + +export default ConfirmFolderClose; diff --git a/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js b/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js index c9168f67c8..ac16a8c9f5 100644 --- a/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js +++ b/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js @@ -3,7 +3,7 @@ import CloseTabIcon from './CloseTabIcon'; import { IconVariable, IconSettings, IconRun, IconFolder, IconShieldLock } from '@tabler/icons'; import DraftTabIcon from './DraftTabIcon'; -const SpecialTab = ({ handleCloseClick, type, tabName, collection }) => { +const SpecialTab = ({ handleCloseClick, type, tabName, collection, folder }) => { const getTabInfo = (type, tabName) => { switch (type) { case 'collection-settings': { @@ -53,10 +53,11 @@ const SpecialTab = ({ handleCloseClick, type, tabName, collection }) => { <>
{getTabInfo(type, tabName)}
handleCloseClick(e)}> - {type === 'collection-settings' && collection?.draft !== null ? + {(type === 'folder-settings' && folder.draft !== null) || + (type === 'collection-settings' && collection.draft !== null) ? : - } + }
); diff --git a/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js b/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js index e4f1b84513..429175a26e 100644 --- a/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js +++ b/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js @@ -19,12 +19,14 @@ import CloseTabIcon from './CloseTabIcon'; import DraftTabIcon from './DraftTabIcon'; import { flattenItems } from 'utils/collections/index'; import ConfirmCollectionClose from './ConfirmCollectionClose/index'; +import ConfirmFolderClose from './ConfirmFolderClose/index'; const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUid }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); const [showConfirmClose, setShowConfirmClose] = useState(false); const [showConfirmCloseCollection, setShowConfirmCloseCollection] = useState(false); + const [showConfirmCloseFolder, setShowConfirmCloseFolder] = useState(false); const dropdownTippyRef = useRef(); const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); @@ -49,6 +51,16 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi setShowConfirmCloseCollection(true); } + const handleCloseFolderSettings = (event) => { + if(!folder.draft) { + return handleCloseClick(event); + } + + event.stopPropagation(); + event.preventDefault(); + setShowConfirmCloseFolder(true); + } + const handleRightClick = (_event) => { const menuDropdown = dropdownTippyRef.current; if (!menuDropdown) { @@ -97,17 +109,27 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi ); } - if (['folder-settings', 'variables', 'collection-runner', 'security-settings'].includes(tab.type)) { + if(tab.type === 'folder-settings') { return ( - {tab.type === 'folder-settings' ? ( - - ) : ( - + {showConfirmCloseFolder && ( + setShowConfirmCloseFolder(false)} collection={collection} folder={folder} tab={tab} /> )} + + + ); + } + + if (['variables', 'collection-runner', 'security-settings'].includes(tab.type)) { + return ( + + ); } 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 2063eacfe5..a368086f1f 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -44,7 +44,7 @@ import { parsePathParams, parseQueryParams, splitOnFirst } from 'utils/url/index import { sendCollectionOauth2Request as _sendCollectionOauth2Request } from 'utils/network/index'; import { name } from 'file-loader'; import slash from 'utils/common/slash'; -import { getGlobalEnvironmentVariables, transformCollectionRootToSave } from 'utils/collections/index'; +import { getGlobalEnvironmentVariables, transformCollectionRootToSave, transformFolderRootToSave } from 'utils/collections/index'; import { findCollectionByPathname, findEnvironmentInCollectionByName } from 'utils/collections/index'; export const renameCollection = (newName, collectionUid) => (dispatch, getState) => { @@ -167,10 +167,14 @@ export const saveFolderRoot = (collectionUid, folderUid) => (dispatch, getState) const { ipcRenderer } = window; + const transformFolderRoot = transformFolderRootToSave(folder); + + console.log({ transformFolderRoot }) + const folderData = { name: folder.name, pathname: folder.pathname, - root: folder.root + root: transformFolderRoot }; console.log(folderData); 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 e5e63ffab4..b59f8ca8de 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -1226,27 +1226,41 @@ export const collectionsSlice = createSlice({ collection.draft.docs = action.payload.docs; } }, + deleteFolderDraft: (state, action) => { + const collection = findCollectionByUid(state.collections, action.payload.collectionUid); + const folder = collection ? findItemInCollection(collection, action.payload.folderUid) : null; + + if (folder) { + folder.draft = null; + } + }, 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({ + if (!folder.draft) { + folder.draft = cloneDeep(folder.root); + } + folder.draft.request.headers = folder.draft.request.headers || []; + folder.draft.request.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 (!folder.draft) { + folder.draft = cloneDeep(folder.root); + } + const header = find(folder.draft.request.headers, (h) => h.uid === action.payload.header.uid); if (header) { header.name = action.payload.header.name; header.value = action.payload.header.value; @@ -1259,9 +1273,10 @@ export const collectionsSlice = createSlice({ 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); + if (!folder.draft) { + folder.draft = cloneDeep(folder.root); + } + folder.draft.request.headers = filter(folder.draft.request.headers, (h) => h.uid !== action.payload.headerUid); } }, addFolderVar: (state, action) => { @@ -1269,24 +1284,27 @@ export const collectionsSlice = createSlice({ const folder = collection ? findItemInCollection(collection, action.payload.folderUid) : null; const type = action.payload.type; if (folder) { + if(!folder.draft) { + folder.draft = cloneDeep(folder.root); + } if (type === 'request') { - const vars = get(folder, 'root.request.vars.req', []); - vars.push({ + folder.draft.request.vars = folder.draft.request.vars || {}; + folder.draft.request.vars.req = folder.draft.request.vars.req || []; + folder.draft.request.vars.req.push({ uid: uuid(), name: '', value: '', enabled: true }); - set(folder, 'root.request.vars.req', vars); } else if (type === 'response') { - const vars = get(folder, 'root.request.vars.res', []); - vars.push({ + folder.draft.request.vars = folder.draft.request.vars || {}; + folder.draft.request.vars.res = folder.draft.request.vars.res || []; + folder.draft.request.vars.res.push({ uid: uuid(), name: '', value: '', enabled: true }); - set(folder, 'root.request.vars.res', vars); } } }, @@ -1295,26 +1313,29 @@ export const collectionsSlice = createSlice({ const folder = collection ? findItemInCollection(collection, action.payload.folderUid) : null; const type = action.payload.type; if (folder) { + if(!folder.draft) { + folder.draft = cloneDeep(folder.root); + } if (type === 'request') { - let vars = get(folder, 'root.request.vars.req', []); - const _var = find(vars, (h) => h.uid === action.payload.var.uid); + folder.draft.request.vars = folder.draft.request.vars || {}; + folder.draft.request.vars.req = folder.draft.request.vars.req || []; + const _var = find(folder.draft.request.vars.req, (h) => h.uid === action.payload.var.uid); if (_var) { _var.name = action.payload.var.name; _var.value = action.payload.var.value; _var.description = action.payload.var.description; _var.enabled = action.payload.var.enabled; } - set(folder, 'root.request.vars.req', vars); } else if (type === 'response') { - let vars = get(folder, 'root.request.vars.res', []); - const _var = find(vars, (h) => h.uid === action.payload.var.uid); + folder.draft.request.vars = folder.draft.request.vars || {}; + folder.draft.request.vars.res = folder.draft.request.vars.res || []; + const _var = find(folder.draft.request.vars.res, (h) => h.uid === action.payload.var.uid); if (_var) { _var.name = action.payload.var.name; _var.value = action.payload.var.value; _var.description = action.payload.var.description; _var.enabled = action.payload.var.enabled; } - set(folder, 'root.request.vars.res', vars); } } }, @@ -1323,14 +1344,15 @@ export const collectionsSlice = createSlice({ const folder = collection ? findItemInCollection(collection, action.payload.folderUid) : null; const type = action.payload.type; if (folder) { + + if(!folder.draft) { + folder.draft = cloneDeep(folder.root); + } + if (type === 'request') { - let vars = get(folder, 'root.request.vars.req', []); - vars = filter(vars, (h) => h.uid !== action.payload.varUid); - set(folder, 'root.request.vars.req', vars); + folder.draft.request.vars.req = filter(folder.draft.request.vars.req, (h) => h.uid !== action.payload.varUid); } else if (type === 'response') { - let vars = get(folder, 'root.request.vars.res', []); - vars = filter(vars, (h) => h.uid !== action.payload.varUid); - set(folder, 'root.request.vars.res', vars); + folder.draft.request.vars.res = filter(folder.draft.request.vars.res, (h) => h.uid !== action.payload.varUid); } } }, @@ -1338,21 +1360,30 @@ export const collectionsSlice = createSlice({ const collection = findCollectionByUid(state.collections, action.payload.collectionUid); const folder = collection ? findItemInCollection(collection, action.payload.folderUid) : null; if (folder) { - set(folder, 'root.request.script.req', action.payload.script); + if(!folder.draft) { + folder.draft = cloneDeep(folder.root); + } + folder.draft.request.script.req = action.payload.script; } }, updateFolderResponseScript: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload.collectionUid); const folder = collection ? findItemInCollection(collection, action.payload.folderUid) : null; if (folder) { - set(folder, 'root.request.script.res', action.payload.script); + if(!folder.draft) { + folder.draft = cloneDeep(folder.root); + } + folder.draft.request.script.res = action.payload.script; } }, updateFolderTests: (state, action) => { const collection = findCollectionByUid(state.collections, action.payload.collectionUid); const folder = collection ? findItemInCollection(collection, action.payload.folderUid) : null; if (folder) { - set(folder, 'root.request.tests', action.payload.tests); + if(!folder.draft) { + folder.draft = cloneDeep(folder.root); + } + folder.draft.request.tests = action.payload.tests; } }, addCollectionHeader: (state, action) => { @@ -1574,7 +1605,19 @@ export const collectionsSlice = createSlice({ name: directoryName, collapsed: true, type: 'folder', - items: [] + items: [], + draft: null, + root: { + docs: "", + request: { + auth: { + mode: "none" + }, + headers: [], + script: { req: null, res: null }, + tests: null, + } + } }; currentSubItems.push(childItem); } @@ -1809,7 +1852,10 @@ export const collectionsSlice = createSlice({ const folder = collection ? findItemInCollection(collection, action.payload.folderUid) : null; if (folder) { if (isItemAFolder(folder)) { - set(folder, 'root.docs', action.payload.docs); + if (!folder.draft) { + folder.draft = cloneDeep(folder.root); + } + folder.draft.docs = action.payload.docs; } } } @@ -1908,7 +1954,8 @@ export const { runFolderEvent, resetCollectionRunner, updateRequestDocs, - updateFolderDocs + updateFolderDocs, + deleteFolderDraft } = collectionsSlice.actions; export default collectionsSlice.reducer; diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index 012e3e2657..9613d565f8 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -620,6 +620,32 @@ export const transformCollectionRootToSave = (collection) => { return collectionRootToSave; } +export const transformFolderRootToSave = (folder) => { + const _folder = folder.draft ? folder.draft : folder.root; + const folderRootToSave = { + docs: _folder.docs, + request: { + auth: _folder?.request?.auth, + headers: [], + script: _folder?.request?.script, + vars: _folder?.request?.vars, + tests: _folder?.request?.tests + } + } + + each(_folder.request.headers, (header) => { + folderRootToSave.request.headers.push({ + uid: header.uid, + name: header.name, + value: header.value, + description: header.description, + enabled: header.enabled + }); + }); + + return folderRootToSave; +} + // todo: optimize this export const deleteItemInCollection = (itemUid, collection) => { collection.items = filter(collection.items, (i) => i.uid !== itemUid); From ee08fca51a996c4f38da22a2a31cdaff43f72e62 Mon Sep 17 00:00:00 2001 From: Pooja Belaramani Date: Mon, 6 Jan 2025 17:48:15 +0530 Subject: [PATCH 20/31] add folder condition in collectionChangeFileEvent and some fixes --- .../Auth/AwsV4Auth/index.js | 2 +- .../Auth/DigestAuth/index.js | 2 +- .../Auth/OAuth2/PasswordCredentials/index.js | 2 +- .../CollectionSettings/Docs/index.js | 2 +- .../components/CollectionSettings/index.js | 4 +-- .../ReduxStore/slices/collections/actions.js | 2 -- .../ReduxStore/slices/collections/index.js | 13 +++++++ .../bruno-app/src/utils/collections/index.js | 2 +- .../bruno-cli/src/runner/prepare-request.js | 2 +- packages/bruno-cli/src/utils/collection.js | 14 ++++---- packages/bruno-electron/src/app/watcher.js | 34 +++++++++++++++++-- .../bruno-electron/src/utils/collection.js | 12 +++---- 12 files changed, 66 insertions(+), 25 deletions(-) diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/AwsV4Auth/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/AwsV4Auth/index.js index da30225143..d2b644e2c2 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/AwsV4Auth/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/AwsV4Auth/index.js @@ -11,7 +11,7 @@ const AwsV4Auth = ({ collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); - const awsv4Auth = collection.draft ? get(collection, 'draft..request.auth.awsv4', {}) : get(collection, 'root.request.auth.awsv4', {}); + const awsv4Auth = collection.draft ? get(collection, 'draft.request.auth.awsv4', {}) : get(collection, 'root.request.auth.awsv4', {}); const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/DigestAuth/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/DigestAuth/index.js index 18db037539..382ddc08d9 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/DigestAuth/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/DigestAuth/index.js @@ -11,7 +11,7 @@ const DigestAuth = ({ collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); - const digestAuth = collection.draft ? get(collection, 'draft..request.auth.digest', {}) : get(collection, 'root.request.auth.digest', {}); + const digestAuth = collection.draft ? get(collection, 'draft.request.auth.digest', {}) : get(collection, 'root.request.auth.digest', {}); const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/PasswordCredentials/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/PasswordCredentials/index.js index d2d9eed1f2..491af25ccf 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/PasswordCredentials/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/PasswordCredentials/index.js @@ -12,7 +12,7 @@ const OAuth2AuthorizationCode = ({ item, collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); - const oAuth = get(collection, 'root.request.auth.oauth2', {}); + const oAuth = collection.draft ? get(collection, 'draft.request.auth.oauth2', {}) : get(collection, 'root.request.auth.oauth2', {}); const handleRun = async () => { dispatch(sendCollectionOauth2Request(collection.uid)); diff --git a/packages/bruno-app/src/components/CollectionSettings/Docs/index.js b/packages/bruno-app/src/components/CollectionSettings/Docs/index.js index 0c00c1927b..a5ccbc6986 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Docs/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Docs/index.js @@ -14,7 +14,7 @@ const Docs = ({ collection }) => { const dispatch = useDispatch(); const { displayedTheme } = useTheme(); const [isEditing, setIsEditing] = useState(false); - const docs = get(collection, 'draft.docs') || get(collection, 'root.docs', ''); + const docs = collection.draft ? get(collection, 'draft.docs', '') : get(collection, 'root.docs', ''); const preferences = useSelector((state) => state.app.preferences); const { theme, storedTheme } = useTheme(); diff --git a/packages/bruno-app/src/components/CollectionSettings/index.js b/packages/bruno-app/src/components/CollectionSettings/index.js index e2d3dac1c7..2985084e9c 100644 --- a/packages/bruno-app/src/components/CollectionSettings/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/index.js @@ -44,13 +44,13 @@ const CollectionSettings = ({ collection }) => { const hasTests = root?.request?.tests; const hasDocs = root?.docs; - const headers = collection.draft ? get(collection, 'draft.request.headers') : get(collection, 'root.request.headers', []); + const headers = collection.draft ? get(collection, 'draft.request.headers', []) : get(collection, 'root.request.headers', []); const activeHeadersCount = headers.filter((header) => header.enabled).length; const requestVars = collection.draft ? get(collection, 'draft.request.vars.req', []) : get(collection, 'root.request.vars.req', []); const responseVars = collection.draft ? get(collection, 'draft.request.vars.res', []) : get(collection, 'root.request.vars.res', []); const activeVarsCount = requestVars.filter((v) => v.enabled).length + responseVars.filter((v) => v.enabled).length; - const auth = collection.draft ? get(collection, 'draft.request,auth', {}).mode : get(collection, 'root.request.auth', {}).mode; + const auth = collection.draft ? get(collection, 'draft.request.auth', {}).mode : get(collection, 'root.request.auth', {}).mode; const proxyConfig = get(collection, 'brunoConfig.proxy', {}); const clientCertConfig = get(collection, 'brunoConfig.clientCertificates.certs', []); 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 a368086f1f..53cc380d95 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -169,8 +169,6 @@ export const saveFolderRoot = (collectionUid, folderUid) => (dispatch, getState) const transformFolderRoot = transformFolderRootToSave(folder); - console.log({ transformFolderRoot }) - const folderData = { name: folder.name, pathname: folder.pathname, 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 1215f69bd7..c0722e38c0 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -1635,11 +1635,24 @@ export const collectionsSlice = createSlice({ // check and update collection root if (collection && file.meta.collectionRoot) { collection.root = file.data; + collection.draft = null; return; } if (collection) { const item = findItemInCollection(collection, file.data.uid); + const isFolderRoot = file.meta.folderRoot ? true : false; + + + if (isFolderRoot) { + const folderPath = getDirectoryName(file.meta.pathname); + const folderItem = findItemInCollectionByPathname(collection, folderPath); + if (folderItem) { + folderItem.root = file.data; + folderItem.draft = null; + } + return; + } if (item) { // whenever a user attempts to sort a req within the same folder diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index 009e1bcdbf..c1c7b9283a 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -1004,7 +1004,7 @@ const mergeVars = (collection, requestTreePath = []) => { let collectionVariables = {}; let folderVariables = {}; let requestVariables = {}; - let collectionRequestVars = get(collection, 'root.request.vars.req', []); + let collectionRequestVars = collection.draft ? get(collection, 'draft.request.vars.req', []) : get(collection, 'root.request.vars.req', []); collectionRequestVars.forEach((_var) => { if (_var.enabled) { collectionVariables[_var.name] = _var.value; diff --git a/packages/bruno-cli/src/runner/prepare-request.js b/packages/bruno-cli/src/runner/prepare-request.js index 0ed8667609..a26691c573 100644 --- a/packages/bruno-cli/src/runner/prepare-request.js +++ b/packages/bruno-cli/src/runner/prepare-request.js @@ -35,7 +35,7 @@ const prepareRequest = (item = {}, collection = {}) => { responseType: 'arraybuffer' }; - const collectionAuth = get(collection, 'root.request.auth'); + const collectionAuth = collection.draft ? get(collection, 'draft.request.auth') : get(collection, 'root.request.auth'); if (collectionAuth && request.auth.mode === 'inherit') { if (collectionAuth.mode === 'basic') { axiosRequest.auth = { diff --git a/packages/bruno-cli/src/utils/collection.js b/packages/bruno-cli/src/utils/collection.js index 365732c488..a8b97df64b 100644 --- a/packages/bruno-cli/src/utils/collection.js +++ b/packages/bruno-cli/src/utils/collection.js @@ -4,7 +4,7 @@ const os = require('os'); const mergeHeaders = (collection, request, requestTreePath) => { let headers = new Map(); - let collectionHeaders = get(collection, 'root.request.headers', []); + let collectionHeaders = collection.draft ? get(collection, 'draft.request.headers', []) : get(collection, 'root.request.headers', []); collectionHeaders.forEach((header) => { if (header.enabled) { headers.set(header.name, header.value); @@ -34,7 +34,7 @@ const mergeHeaders = (collection, request, requestTreePath) => { const mergeVars = (collection, request, requestTreePath) => { let reqVars = new Map(); - let collectionRequestVars = get(collection, 'root.request.vars.req', []); + let collectionRequestVars = collection.draft ? get(collection, 'draft.request.vars.req', []) : get(collection, 'root.request.vars.req', []); let collectionVariables = {}; collectionRequestVars.forEach((_var) => { if (_var.enabled) { @@ -78,7 +78,7 @@ const mergeVars = (collection, request, requestTreePath) => { } let resVars = new Map(); - let collectionResponseVars = get(collection, 'root.request.vars.res', []); + let collectionResponseVars = collection.draft ? get(collection, 'draft.request.vars.res', []) : get(collection, 'root.request.vars.res', []); collectionResponseVars.forEach((_var) => { if (_var.enabled) { resVars.set(_var.name, _var.value); @@ -113,9 +113,9 @@ const mergeVars = (collection, request, requestTreePath) => { }; const mergeScripts = (collection, request, requestTreePath, scriptFlow) => { - let collectionPreReqScript = get(collection, 'root.request.script.req', ''); - let collectionPostResScript = get(collection, 'root.request.script.res', ''); - let collectionTests = get(collection, 'root.request.tests', ''); + let collectionPreReqScript = collection.draft ? get(collection, 'draft.request.script.req') : get(collection, 'root.request.script.req', ''); + let collectionPostResScript = collection.draft ? get(collection, 'draft.request.script.res') : get(collection, 'root.request.script.res', ''); + let collectionTests = collection.draft ? get(collection, 'draft.request.tests', '') : get(collection, 'root.request.tests', ''); let combinedPreReqScript = []; let combinedPostResScript = []; @@ -137,7 +137,7 @@ const mergeScripts = (collection, request, requestTreePath, scriptFlow) => { combinedTests.push(tests); } } - } + }s request.script.req = compact([collectionPreReqScript, ...combinedPreReqScript, request?.script?.req || '']).join(os.EOL); diff --git a/packages/bruno-electron/src/app/watcher.js b/packages/bruno-electron/src/app/watcher.js index 43d01153d1..56bd68701f 100644 --- a/packages/bruno-electron/src/app/watcher.js +++ b/packages/bruno-electron/src/app/watcher.js @@ -66,7 +66,10 @@ const hydrateRequestWithUuid = (request, pathname) => { return request; }; -const hydrateBruCollectionFileWithUuid = (collectionRoot) => { +const hydrateBruCollectionFileWithUuid = (collectionRoot, pathname) => { + if(pathname) { + collectionRoot.uid = getRequestUid(pathname); + } const params = _.get(collectionRoot, 'request.params', []); const headers = _.get(collectionRoot, 'request.headers', []); const requestVars = _.get(collectionRoot, 'request.vars.req', []); @@ -256,7 +259,7 @@ const add = async (win, pathname, collectionUid, collectionPath) => { file.data = collectionBruToJson(bruContent); - hydrateBruCollectionFileWithUuid(file.data); + hydrateBruCollectionFileWithUuid(file.data, pathname); win.webContents.send('main:collection-tree-updated', 'addFile', file); return; } catch (err) { @@ -367,6 +370,33 @@ const change = async (win, pathname, collectionUid, collectionPath) => { } } + if (path.basename(pathname) === 'folder.bru') { + const file = { + meta: { + collectionUid, + pathname, + name: path.basename(pathname), + folderRoot: true + } + }; + + try { + let bruContent = fs.readFileSync(pathname, 'utf8'); + file.data = collectionBruToJson(bruContent); + + // Preserve the existing UID + const existingUid = getRequestUid(pathname); + file.data.uid = existingUid; + + hydrateBruCollectionFileWithUuid(file.data, pathname); + win.webContents.send('main:collection-tree-updated', 'change', file); + return; + } catch (err) { + console.error('Error handling folder.bru change:', err); + return; + } + } + if (hasBruExtension(pathname)) { try { const file = { diff --git a/packages/bruno-electron/src/utils/collection.js b/packages/bruno-electron/src/utils/collection.js index 15d5574e22..3a98ef57b5 100644 --- a/packages/bruno-electron/src/utils/collection.js +++ b/packages/bruno-electron/src/utils/collection.js @@ -4,7 +4,7 @@ const os = require('os'); const mergeHeaders = (collection, request, requestTreePath) => { let headers = new Map(); - let collectionHeaders = get(collection, 'root.request.headers', []); + let collectionHeaders = collection.draft ? get(collection, 'draft.request.headers', []) : get(collection, 'root.request.headers', []); collectionHeaders.forEach((header) => { if (header.enabled) { headers.set(header.name, header.value); @@ -37,7 +37,7 @@ const mergeHeaders = (collection, request, requestTreePath) => { const mergeVars = (collection, request, requestTreePath) => { let reqVars = new Map(); - let collectionRequestVars = get(collection, 'root.request.vars.req', []); + let collectionRequestVars = collection.draft ? get(collection, 'draft.request.vars.req', []) : get(collection, 'root.request.vars.req', []); let collectionVariables = {}; collectionRequestVars.forEach((_var) => { if (_var.enabled) { @@ -81,7 +81,7 @@ const mergeVars = (collection, request, requestTreePath) => { } let resVars = new Map(); - let collectionResponseVars = get(collection, 'root.request.vars.res', []); + let collectionResponseVars = collection.draft ? get(collection, 'draft.request.vars.res') : get(collection, 'root.request.vars.res', []); collectionResponseVars.forEach((_var) => { if (_var.enabled) { resVars.set(_var.name, _var.value); @@ -116,9 +116,9 @@ const mergeVars = (collection, request, requestTreePath) => { }; const mergeScripts = (collection, request, requestTreePath, scriptFlow) => { - let collectionPreReqScript = get(collection, 'root.request.script.req', ''); - let collectionPostResScript = get(collection, 'root.request.script.res', ''); - let collectionTests = get(collection, 'root.request.tests', ''); + let collectionPreReqScript = collection.draft ? get(collection, 'draft.request.script.req', '') : get(collection, 'root.request.script.req', ''); + let collectionPostResScript = collection.draft ? get(collection, 'draft.request.script.res', '') : get(collection, 'root.request.script.res', ''); + let collectionTests = collection.draft ? get(collection, 'draft.request.tests') : get(collection, 'root.request.tests', ''); let combinedPreReqScript = []; let combinedPostResScript = []; From 5cf12aec7efc1926909d4fbaaee81361f1caac23 Mon Sep 17 00:00:00 2001 From: Pooja Belaramani Date: Mon, 6 Jan 2025 18:14:17 +0530 Subject: [PATCH 21/31] fixes --- .../CollectionSettings/Auth/OAuth2/AuthorizationCode/index.js | 2 +- .../CollectionSettings/Auth/OAuth2/ClientCredentials/index.js | 2 +- .../src/components/CollectionSettings/Auth/OAuth2/index.js | 2 +- .../src/components/CollectionSettings/Headers/index.js | 2 +- .../src/components/CollectionSettings/Script/index.js | 2 +- packages/bruno-cli/src/utils/collection.js | 4 ++-- packages/bruno-electron/src/utils/collection.js | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/AuthorizationCode/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/AuthorizationCode/index.js index 3539d510e2..5dbea5e4bd 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/AuthorizationCode/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/AuthorizationCode/index.js @@ -14,7 +14,7 @@ const OAuth2AuthorizationCode = ({ collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); - const oAuth = collection.draft ? get(collection, 'draft.request.auth.oauth', {}) : get(collection, 'root.request.auth.oauth2', {}); + const oAuth = collection.draft ? get(collection, 'draft.request.auth.oauth2', {}) : get(collection, 'root.request.auth.oauth2', {}); const handleRun = async () => { dispatch(sendCollectionOauth2Request(collection.uid)); diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/ClientCredentials/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/ClientCredentials/index.js index aaae3b5b22..9447a6dd05 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/ClientCredentials/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/ClientCredentials/index.js @@ -12,7 +12,7 @@ const OAuth2ClientCredentials = ({ collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); - const oAuth = collection.draft ? get(collection, 'draft.request.auth.oauth', {}) : get(collection, 'root.request.auth.oauth2', {}); + const oAuth = collection.draft ? get(collection, 'draft.request.auth.oauth2', {}) : get(collection, 'root.request.auth.oauth2', {}); const handleRun = async () => { dispatch(sendCollectionOauth2Request(collection.uid)); diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/index.js index 2bf906a120..ee01d41a04 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/OAuth2/index.js @@ -24,7 +24,7 @@ const grantTypeComponentMap = (grantType, collection) => { }; const OAuth2 = ({ collection }) => { - const oAuth = collection.draft ? get(collection, 'draft.request.auth.oauth', {}) : get(collection, 'root.request.auth.oauth2', {}); + const oAuth = collection.draft ? get(collection, 'draft.request.auth.oauth2', {}) : get(collection, 'root.request.auth.oauth2', {}); return ( diff --git a/packages/bruno-app/src/components/CollectionSettings/Headers/index.js b/packages/bruno-app/src/components/CollectionSettings/Headers/index.js index c1bc16ca20..63c3a4fafe 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Headers/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Headers/index.js @@ -19,7 +19,7 @@ const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header); const Headers = ({ collection }) => { const dispatch = useDispatch(); const { storedTheme } = useTheme(); - const headers = collection.draft ? get(collection, 'draft.request.headers') : get(collection, 'root.request.headers', []); + const headers = collection.draft ? get(collection, 'draft.request.headers', []) : get(collection, 'root.request.headers', []); const addHeader = () => { dispatch( diff --git a/packages/bruno-app/src/components/CollectionSettings/Script/index.js b/packages/bruno-app/src/components/CollectionSettings/Script/index.js index 839b79afe6..8a69aacdf8 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Script/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Script/index.js @@ -9,7 +9,7 @@ import StyledWrapper from './StyledWrapper'; const Script = ({ collection }) => { const dispatch = useDispatch(); - const requestScript = collection.draft ? get(collection, 'draft.request.script.res', '') : get(collection, 'root.request.script.req', ''); + const requestScript = collection.draft ? get(collection, 'draft.request.script.req', '') : get(collection, 'root.request.script.req', ''); const responseScript = collection.draft ? get(collection, 'draft.request.script.res', '') : get(collection, 'root.request.script.res', ''); const { displayedTheme } = useTheme(); diff --git a/packages/bruno-cli/src/utils/collection.js b/packages/bruno-cli/src/utils/collection.js index a8b97df64b..1e0eed2193 100644 --- a/packages/bruno-cli/src/utils/collection.js +++ b/packages/bruno-cli/src/utils/collection.js @@ -113,8 +113,8 @@ const mergeVars = (collection, request, requestTreePath) => { }; const mergeScripts = (collection, request, requestTreePath, scriptFlow) => { - let collectionPreReqScript = collection.draft ? get(collection, 'draft.request.script.req') : get(collection, 'root.request.script.req', ''); - let collectionPostResScript = collection.draft ? get(collection, 'draft.request.script.res') : get(collection, 'root.request.script.res', ''); + let collectionPreReqScript = collection.draft ? get(collection, 'draft.request.script.req', '') : get(collection, 'root.request.script.req', ''); + let collectionPostResScript = collection.draft ? get(collection, 'draft.request.script.res', '') : get(collection, 'root.request.script.res', ''); let collectionTests = collection.draft ? get(collection, 'draft.request.tests', '') : get(collection, 'root.request.tests', ''); let combinedPreReqScript = []; diff --git a/packages/bruno-electron/src/utils/collection.js b/packages/bruno-electron/src/utils/collection.js index 3a98ef57b5..881c7e90ea 100644 --- a/packages/bruno-electron/src/utils/collection.js +++ b/packages/bruno-electron/src/utils/collection.js @@ -81,7 +81,7 @@ const mergeVars = (collection, request, requestTreePath) => { } let resVars = new Map(); - let collectionResponseVars = collection.draft ? get(collection, 'draft.request.vars.res') : get(collection, 'root.request.vars.res', []); + let collectionResponseVars = collection.draft ? get(collection, 'draft.request.vars.res', []) : get(collection, 'root.request.vars.res', []); collectionResponseVars.forEach((_var) => { if (_var.enabled) { resVars.set(_var.name, _var.value); @@ -118,7 +118,7 @@ const mergeVars = (collection, request, requestTreePath) => { const mergeScripts = (collection, request, requestTreePath, scriptFlow) => { let collectionPreReqScript = collection.draft ? get(collection, 'draft.request.script.req', '') : get(collection, 'root.request.script.req', ''); let collectionPostResScript = collection.draft ? get(collection, 'draft.request.script.res', '') : get(collection, 'root.request.script.res', ''); - let collectionTests = collection.draft ? get(collection, 'draft.request.tests') : get(collection, 'root.request.tests', ''); + let collectionTests = collection.draft ? get(collection, 'draft.request.tests', '') : get(collection, 'root.request.tests', ''); let combinedPreReqScript = []; let combinedPostResScript = []; From 84b565f44957de0c093e1ea4bb5737319eb01361 Mon Sep 17 00:00:00 2001 From: Pooja Belaramani Date: Tue, 7 Jan 2025 11:08:07 +0530 Subject: [PATCH 22/31] rm: document button changes --- .../CollectionSettings/Docs/StyledWrapper.js | 3 +++ .../CollectionSettings/Docs/index.js | 27 ++++++++----------- .../FolderSettings/Documentation/index.js | 27 ++++++++----------- 3 files changed, 25 insertions(+), 32 deletions(-) diff --git a/packages/bruno-app/src/components/CollectionSettings/Docs/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Docs/StyledWrapper.js index 262f068e78..f0ffee808e 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Docs/StyledWrapper.js +++ b/packages/bruno-app/src/components/CollectionSettings/Docs/StyledWrapper.js @@ -2,6 +2,9 @@ import styled from 'styled-components'; const StyledWrapper = styled.div` div.CodeMirror { + /* todo: find a better way */ + height: calc(100vh - 240px); + .CodeMirror-scroll { padding-bottom: 0px; } diff --git a/packages/bruno-app/src/components/CollectionSettings/Docs/index.js b/packages/bruno-app/src/components/CollectionSettings/Docs/index.js index a5ccbc6986..76572f5297 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Docs/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Docs/index.js @@ -35,26 +35,21 @@ const Docs = ({ collection }) => { return ( -
+
{isEditing ? 'Preview' : 'Edit'}
{isEditing ? ( -
- - -
+ ) : ( )} diff --git a/packages/bruno-app/src/components/FolderSettings/Documentation/index.js b/packages/bruno-app/src/components/FolderSettings/Documentation/index.js index 9253bb6967..827ea3c93a 100644 --- a/packages/bruno-app/src/components/FolderSettings/Documentation/index.js +++ b/packages/bruno-app/src/components/FolderSettings/Documentation/index.js @@ -41,26 +41,21 @@ const Documentation = ({ collection, folder }) => { return ( -
+
{isEditing ? 'Preview' : 'Edit'}
{isEditing ? ( -
- - -
+ ) : ( )} From 2dfc7e94427d23be303726cbef9ebcacaa0a2531 Mon Sep 17 00:00:00 2001 From: Pooja Belaramani Date: Tue, 7 Jan 2025 11:32:51 +0530 Subject: [PATCH 23/31] fix --- .../src/components/CollectionSettings/Docs/index.js | 2 +- .../src/components/FolderSettings/Documentation/index.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/bruno-app/src/components/CollectionSettings/Docs/index.js b/packages/bruno-app/src/components/CollectionSettings/Docs/index.js index 76572f5297..74c64b9511 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Docs/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Docs/index.js @@ -34,7 +34,7 @@ const Docs = ({ collection }) => { const onSave = () => dispatch(saveCollectionRoot(collection.uid)); return ( - +
{isEditing ? 'Preview' : 'Edit'}
diff --git a/packages/bruno-app/src/components/FolderSettings/Documentation/index.js b/packages/bruno-app/src/components/FolderSettings/Documentation/index.js index 827ea3c93a..11cf316e5d 100644 --- a/packages/bruno-app/src/components/FolderSettings/Documentation/index.js +++ b/packages/bruno-app/src/components/FolderSettings/Documentation/index.js @@ -40,8 +40,8 @@ const Documentation = ({ collection, folder }) => { } return ( - -
+ +
{isEditing ? 'Preview' : 'Edit'}
@@ -49,12 +49,12 @@ const Documentation = ({ collection, folder }) => { ) : ( From bdf9b1513e4cb3f7d385e9887675e0652ebf7b0f Mon Sep 17 00:00:00 2001 From: Pooja Belaramani Date: Tue, 7 Jan 2025 13:15:30 +0530 Subject: [PATCH 24/31] small fixes --- .../CollectionSettings/Docs/index.js | 1 - .../src/components/Documentation/index.js | 2 -- .../FolderSettings/Documentation/index.js | 3 --- .../ReduxStore/slices/collections/actions.js | 18 +++++++++--------- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/packages/bruno-app/src/components/CollectionSettings/Docs/index.js b/packages/bruno-app/src/components/CollectionSettings/Docs/index.js index 74c64b9511..66931cdf6f 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Docs/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Docs/index.js @@ -8,7 +8,6 @@ import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/acti import Markdown from 'components/MarkDown'; import CodeEditor from 'components/CodeEditor'; import StyledWrapper from './StyledWrapper'; -import { IconDeviceFloppy } from '@tabler/icons'; const Docs = ({ collection }) => { const dispatch = useDispatch(); diff --git a/packages/bruno-app/src/components/Documentation/index.js b/packages/bruno-app/src/components/Documentation/index.js index fd7b6339b4..bbce1bd9b1 100644 --- a/packages/bruno-app/src/components/Documentation/index.js +++ b/packages/bruno-app/src/components/Documentation/index.js @@ -8,7 +8,6 @@ import { saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import Markdown from 'components/MarkDown'; import CodeEditor from 'components/CodeEditor'; import StyledWrapper from './StyledWrapper'; -import { IconDeviceFloppy } from '@tabler/icons'; const Documentation = ({ item, collection }) => { const dispatch = useDispatch(); @@ -16,7 +15,6 @@ const Documentation = ({ item, collection }) => { const [isEditing, setIsEditing] = useState(false); const docs = item.draft ? get(item, 'draft.request.docs') : get(item, 'request.docs'); const preferences = useSelector((state) => state.app.preferences); - const { theme } = useTheme(); const toggleViewMode = () => { setIsEditing((prev) => !prev); diff --git a/packages/bruno-app/src/components/FolderSettings/Documentation/index.js b/packages/bruno-app/src/components/FolderSettings/Documentation/index.js index 11cf316e5d..275e8c8f3a 100644 --- a/packages/bruno-app/src/components/FolderSettings/Documentation/index.js +++ b/packages/bruno-app/src/components/FolderSettings/Documentation/index.js @@ -8,7 +8,6 @@ import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions' import Markdown from 'components/MarkDown'; import CodeEditor from 'components/CodeEditor'; import StyledWrapper from './StyledWrapper'; -import { IconDeviceFloppy } from '@tabler/icons'; const Documentation = ({ collection, folder }) => { const dispatch = useDispatch(); @@ -16,8 +15,6 @@ const Documentation = ({ collection, folder }) => { const preferences = useSelector((state) => state.app.preferences); const [isEditing, setIsEditing] = useState(false); const docs = folder.draft ? get(folder, 'draft.docs', '') : get(folder, 'root.docs', ''); - const { theme } = useTheme(); - const toggleViewMode = () => { setIsEditing((prev) => !prev); 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 53cc380d95..6d0e2f5857 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -128,19 +128,19 @@ export const saveMultipleRequests = (items) => (dispatch, getState) => { export const saveCollectionRoot = (collectionUid) => (dispatch, getState) => { const state = getState(); - const collection = findCollectionByUid(state.collections.collections, collectionUid); + const collectionRoot = findCollectionByUid(state.collections.collections, collectionUid); - const transformRoot = transformCollectionRootToSave(collection); + const transformRoot = transformCollectionRootToSave(collectionRoot); return new Promise((resolve, reject) => { - if (!collection) { + if (!collectionRoot) { return reject(new Error('Collection not found')); } const { ipcRenderer } = window; ipcRenderer - .invoke('renderer:save-collection-root', collection.pathname, transformRoot) + .invoke('renderer:save-collection-root', collectionRoot.pathname, transformRoot) .then(() => toast.success('Collection Settings saved successfully')) .then(resolve) .catch((err) => { @@ -153,25 +153,25 @@ export const saveCollectionRoot = (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); + const folderRoot = findItemInCollection(collection, folderUid); return new Promise((resolve, reject) => { if (!collection) { return reject(new Error('Collection not found')); } - if (!folder) { + if (!folderRoot) { return reject(new Error('Folder not found')); } console.log(collection); const { ipcRenderer } = window; - const transformFolderRoot = transformFolderRootToSave(folder); + const transformFolderRoot = transformFolderRootToSave(folderRoot); const folderData = { - name: folder.name, - pathname: folder.pathname, + name: folderRoot.name, + pathname: folderRoot.pathname, root: transformFolderRoot }; console.log(folderData); From 94f155af76719789f71956491bb675af0164afe6 Mon Sep 17 00:00:00 2001 From: Pooja Belaramani Date: Tue, 7 Jan 2025 17:34:49 +0530 Subject: [PATCH 25/31] rm --- packages/bruno-cli/src/utils/collection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bruno-cli/src/utils/collection.js b/packages/bruno-cli/src/utils/collection.js index 1e0eed2193..eec4626841 100644 --- a/packages/bruno-cli/src/utils/collection.js +++ b/packages/bruno-cli/src/utils/collection.js @@ -137,7 +137,7 @@ const mergeScripts = (collection, request, requestTreePath, scriptFlow) => { combinedTests.push(tests); } } - }s + } request.script.req = compact([collectionPreReqScript, ...combinedPreReqScript, request?.script?.req || '']).join(os.EOL); From f385cd2c2c9221a819d4c2b194049f22a05a8466 Mon Sep 17 00:00:00 2001 From: lohxt1 Date: Sun, 19 Jan 2025 15:51:07 +0530 Subject: [PATCH 26/31] file explorer and bru file linter ~ file explorer mode to modify the bru file content directly in app ~ added linter for bru file types in codemirror ~ command+shift+m hotkey for switching view modes (request/file) --- .../FileEditor/CodeEditor/StyledWrapper.js | 102 +++++++++ .../components/FileEditor/CodeEditor/index.js | 213 ++++++++++++++++++ .../src/components/FileEditor/index.js | 70 ++++++ .../RequestPane/HttpRequestPane/index.js | 7 + .../src/components/RequestTabPanel/index.js | 31 ++- .../RequestTabs/CollectionToolBar/index.js | 27 ++- .../bruno-app/src/providers/Hotkeys/index.js | 25 ++ .../src/providers/Hotkeys/keyMappings.js | 3 +- .../ReduxStore/slices/collections/actions.js | 17 +- .../ReduxStore/slices/collections/index.js | 45 +++- packages/bruno-electron/src/app/watcher.js | 8 +- packages/bruno-electron/src/ipc/collection.js | 40 ++++ packages/bruno-lang/src/index.js | 16 +- packages/bruno-lang/v2/src/bruToJson.js | 5 +- .../bruno-lang/v2/src/collectionBruToJson.js | 5 +- packages/bruno-lang/v2/src/envToJson.js | 5 +- 16 files changed, 599 insertions(+), 20 deletions(-) create mode 100644 packages/bruno-app/src/components/FileEditor/CodeEditor/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/FileEditor/CodeEditor/index.js create mode 100644 packages/bruno-app/src/components/FileEditor/index.js diff --git a/packages/bruno-app/src/components/FileEditor/CodeEditor/StyledWrapper.js b/packages/bruno-app/src/components/FileEditor/CodeEditor/StyledWrapper.js new file mode 100644 index 0000000000..e191e59894 --- /dev/null +++ b/packages/bruno-app/src/components/FileEditor/CodeEditor/StyledWrapper.js @@ -0,0 +1,102 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + div.CodeMirror { + height: 100%; + background: ${(props) => props.theme.codemirror.bg}; + border: solid 1px ${(props) => props.theme.codemirror.border}; + font-family: ${(props) => (props.font ? props.font : 'default')}; + font-size: ${(props) => (props.fontSize ? `${props.fontSize}px` : 'inherit')}; + line-break: anywhere; + flex: 1 1 0; + } + + /* Removes the glow outline around the folded json */ + .CodeMirror-foldmarker { + text-shadow: none; + color: ${(props) => props.theme.textLink}; + background: none; + padding: 0; + margin: 0; + } + + .CodeMirror-overlayscroll-horizontal div, + .CodeMirror-overlayscroll-vertical div { + background: #d2d7db; + } + + .CodeMirror-dialog { + overflow: visible; + input { + background: transparent; + border: 1px solid #d3d6db; + outline: none; + border-radius: 0px; + } + } + + #search-results-count { + display: inline-block; + position: absolute; + top: calc(100% + 1px); + right: 0; + border-width: 0 0 1px 1px; + border-style: solid; + border-color: ${(props) => props.theme.codemirror.border}; + padding: 0.1em 0.8em; + background-color: ${(props) => props.theme.codemirror.bg}; + color: rgb(102, 102, 102); + white-space: nowrap; + } + + textarea.cm-editor { + position: relative; + } + + // Todo: dark mode temporary fix + // Clean this + .CodeMirror.cm-s-monokai { + .CodeMirror-overlayscroll-horizontal div, + .CodeMirror-overlayscroll-vertical div { + background: #444444; + } + } + + .cm-s-monokai span.cm-property, + .cm-s-monokai span.cm-attribute { + color: #9cdcfe !important; + } + + .cm-s-monokai span.cm-string { + color: #ce9178 !important; + } + + .cm-s-monokai span.cm-number { + color: #b5cea8 !important; + } + + .cm-s-monokai span.cm-atom { + color: #569cd6 !important; + } + + .cm-variable-valid { + color: green; + } + .cm-variable-invalid { + color: red; + } + + .CodeMirror-search-hint { + display: inline; + } + + .cm-s-default span.cm-property { + color: #1f61a0 !important; + } + + .cm-s-default span.cm-variable { + color: #397d13 !important; + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/FileEditor/CodeEditor/index.js b/packages/bruno-app/src/components/FileEditor/CodeEditor/index.js new file mode 100644 index 0000000000..92482953d7 --- /dev/null +++ b/packages/bruno-app/src/components/FileEditor/CodeEditor/index.js @@ -0,0 +1,213 @@ +import React from 'react'; +import StyledWrapper from './StyledWrapper'; +import { debounce, escapeRegExp } from 'lodash'; +import toast from 'react-hot-toast'; +let CodeMirror = require('codemirror'); + +async function bruLinter(text, callback, type) { + try { + const errors = await ipcRenderer.invoke('renderer:grammar-match', { text, type }); + const annotations = errors?.map(error => ({ + message: error?.message, + severity: 'error', + from: CodeMirror.Pos(error?.errorLine - 1, error?.errorColumn), + to: CodeMirror.Pos(error?.errorLine - 1, error?.errorColumn + 1), + })); + callback(annotations || []); + } catch (err) { + console.error("Error in linter:", err); + callback([]); + } +} + +export default class CodeEditor extends React.Component { + constructor(props) { + super(props); + this.cachedValue = props.value || ''; + this.variables = {}; + this.searchResultsCountElementId = 'search-results-count'; + this.state = { + hasLintErrors: false, + }; + this.debouncedLint = debounce(this._checkLintErrors, 500); + this.lintOptions = { + getAnnotations: (text, callback) => bruLinter(text, callback, this.props.type || 'request'), + async: true, + }; + } + + componentDidMount() { + const editor = (this.editor = CodeMirror(this._node, { + value: this.props.value || '', + lineNumbers: true, + lineWrapping: true, + tabSize: 2, + mode: 'application/ld+json', + keyMap: 'sublime', + autoCloseBrackets: true, + matchBrackets: true, + showCursorWhenSelecting: true, + foldGutter: true, + gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'], + lint: this.lintOptions, + readOnly: this.props.readOnly, + scrollbarStyle: 'overlay', + theme: this.props.theme === 'dark' ? 'monokai' : 'default', + extraKeys: { + 'Cmd-S': () => this.handleSave(), + 'Ctrl-S': () => this.handleSave(), + 'Shift-Cmd-M': () => this.props.toggleFileMode && this.props.toggleFileMode(), + 'Shift-Ctrl-M': () => this.props.toggleFileMode && this.props.toggleFileMode(), + 'Cmd-F': (cm) => { + cm.execCommand('findPersistent'); + this._bindSearchHandler(); + this._appendSearchResultsCount(); + }, + 'Ctrl-F': (cm) => { + cm.execCommand('findPersistent'); + this._bindSearchHandler(); + this._appendSearchResultsCount(); + }, + 'Cmd-H': 'replace', + 'Ctrl-H': 'replace', + Tab: cm => { + cm.getSelection().includes('\n') || editor.getLine(cm.getCursor().line) == cm.getSelection() + ? cm.execCommand('indentMore') + : cm.replaceSelection(' ', 'end'); + }, + 'Shift-Tab': 'indentLess', + 'Ctrl-Space': 'autocomplete', + 'Cmd-Space': 'autocomplete', + 'Ctrl-Y': 'foldAll', + 'Cmd-Y': 'foldAll', + 'Ctrl-I': 'unfoldAll', + 'Cmd-I': 'unfoldAll', + }, + })); + + if (editor) { + editor.on('change', this._onEdit); + } + } + + componentDidUpdate(prevProps) { + if (this.props.value !== prevProps.value && this.props.value !== this.cachedValue && this.editor) { + this.cachedValue = this.props.value; + this.editor.setValue(this.props.value); + } + if (this.props.theme !== prevProps.theme && this.editor) { + this.editor.setOption('theme', this.props.theme === 'dark' ? 'monokai' : 'default'); + } + } + + componentWillUnmount() { + if (this.editor) { + this.editor.off('change', this._onEdit); + this.editor = null; + } + } + + render() { + if (this.editor) { + this.editor.refresh(); + } + return ( + { + this._node = node; + }} + /> + ); + } + + _checkLintErrors = async () => { + if (this.editor) { + const editorText = this.editor.getValue().trim(); + await this.lintOptions.getAnnotations(editorText, (errors) => { + const hasLintErrors = errors.length > 0; + this.setState({ hasLintErrors }); + }); + } + }; + + _onEdit = () => { + if (this.editor) { + this.cachedValue = this.editor.getValue(); + this.debouncedLint(); // Call the debounced linting function + if (this.props.onEdit) { + this.props.onEdit(this.cachedValue); + } + } + }; + + // Handle the save action, only allowing it if no lint errors exist + handleSave = () => { + if (!this.state.hasLintErrors && this.props.onSave) { + this.props.onSave(); + } else { + toast.error('invalid bru syntax'); + } + }; + + /** + * Bind handler to search input to count number of search results + */ + _bindSearchHandler = () => { + const searchInput = document.querySelector('.CodeMirror-search-field'); + + if (searchInput) { + searchInput.addEventListener('input', this._countSearchResults); + } + }; + + /** + * Unbind handler to search input to count number of search results + */ + _unbindSearchHandler = () => { + const searchInput = document.querySelector('.CodeMirror-search-field'); + + if (searchInput) { + searchInput.removeEventListener('input', this._countSearchResults); + } + }; + + /** + * Append search results count to search dialog + */ + _appendSearchResultsCount = () => { + const dialog = document.querySelector('.CodeMirror-dialog.CodeMirror-dialog-top'); + + if (dialog) { + const searchResultsCount = document.createElement('span'); + searchResultsCount.id = this.searchResultsCountElementId; + dialog.appendChild(searchResultsCount); + + this._countSearchResults(); + } + }; + + /** + * Count search results and update state + */ + _countSearchResults = () => { + let count = 0; + + const searchInput = document.querySelector('.CodeMirror-search-field'); + + if (searchInput && searchInput.value.length > 0) { + // Escape special characters in search input to prevent RegExp crashes. Fixes #3051 + const text = new RegExp(escapeRegExp(searchInput.value), 'gi'); + const matches = this.editor.getValue().match(text); + count = matches ? matches.length : 0; + } + + const searchResultsCountElement = document.querySelector(`#${this.searchResultsCountElementId}`); + + if (searchResultsCountElement) { + searchResultsCountElement.innerText = `${count} results`; + } + }; +} diff --git a/packages/bruno-app/src/components/FileEditor/index.js b/packages/bruno-app/src/components/FileEditor/index.js new file mode 100644 index 0000000000..9f2981985d --- /dev/null +++ b/packages/bruno-app/src/components/FileEditor/index.js @@ -0,0 +1,70 @@ +import get from 'lodash/get'; +import { useTheme } from 'providers/Theme'; +import { useDispatch, useSelector } from 'react-redux'; +import CodeEditor from './CodeEditor/index'; +import { saveFile } from 'providers/ReduxStore/slices/collections/actions'; +import { IconDeviceFloppy } from '@tabler/icons'; +import { updateFileContent } from "providers/ReduxStore/slices/collections"; +import { toggleCollectionFileMode } from 'providers/ReduxStore/slices/collections/index'; + +const FileEditor = ({ item, collection, type }) => { + const dispatch = useDispatch(); + const { displayedTheme, theme } = useTheme(); + const preferences = useSelector((state) => state.app.preferences); + + const content = item?.draft ? item?.draft?.raw : item?.raw || ""; + + const onEdit = (value) => { + dispatch( + updateFileContent({ + content: value, + itemUid: item?.uid, + collectionUid: collection.uid, + type + }) + ); + }; + + const onSave = () => { + dispatch(saveFile(content, item?.pathname)); + }; + + const _toggleFileMode = () => { + dispatch( + toggleCollectionFileMode({ + collectionUid: collection.uid + }) + ); + }; + + const hasChanges = item?.draft !== null; + + const editorMode = item?.type == 'js' ? 'javascript' : item?.type == 'json' ? 'javascript' : 'application/text'; + + return ( +
+ + +
+ ); +}; + +export default FileEditor; diff --git a/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js b/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js index 09a665e9f8..0e6b43d3af 100644 --- a/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js +++ b/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js @@ -15,6 +15,7 @@ import Tests from 'components/RequestPane/Tests'; import StyledWrapper from './StyledWrapper'; import { find, get } from 'lodash'; import Documentation from 'components/Documentation/index'; +import FileEditor from 'components/FileEditor/index'; const ContentIndicator = () => { return ( @@ -67,6 +68,9 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => { case 'docs': { return ; } + case 'file': { + return ; + } default: { return
404 | Not found
; } @@ -150,6 +154,9 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => { Docs {docs && docs.length > 0 && }
+
selectTab('file')}> + File +
{focusedTab.requestPaneTab === 'body' ? (
diff --git a/packages/bruno-app/src/components/RequestTabPanel/index.js b/packages/bruno-app/src/components/RequestTabPanel/index.js index 4bcfff1c3a..ed5d0893f9 100644 --- a/packages/bruno-app/src/components/RequestTabPanel/index.js +++ b/packages/bruno-app/src/components/RequestTabPanel/index.js @@ -22,6 +22,7 @@ import SecuritySettings from 'components/SecuritySettings'; import FolderSettings from 'components/FolderSettings'; import { getGlobalEnvironmentVariables, getGlobalEnvironmentVariablesMasked } from 'utils/collections/index'; import { produce } from 'immer'; +import FileEditor from 'components/FileEditor/index'; const MIN_LEFT_PANE_WIDTH = 300; const MIN_RIGHT_PANE_WIDTH = 350; @@ -150,23 +151,47 @@ const RequestTabPanel = () => { return ; } + if (focusedTab.type === 'security-settings') { + return ; + } + if (focusedTab.type === 'collection-settings') { + if (collection.fileMode) { + return ( + + + + ); + } return ; } + if (focusedTab.type === 'folder-settings') { const folder = findItemInCollection(collection, focusedTab.folderUid); + if (collection.fileMode) { + return ( + + + + ); + } return ; } - if (focusedTab.type === 'security-settings') { - return ; - } const item = findItemInCollection(collection, activeTabUid); if (!item || !item.uid) { return ; } + if (collection.fileMode) { + return ( + + + + ); + } + const handleRun = async () => { dispatch(sendRequest(item, collection.uid)).catch((err) => toast.custom((t) => toast.dismiss(t.id)} />, { diff --git a/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js b/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js index 8ca76b15e8..b825c77883 100644 --- a/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js +++ b/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js @@ -1,6 +1,6 @@ import React from 'react'; import { uuid } from 'utils/common'; -import { IconFiles, IconRun, IconEye, IconSettings } from '@tabler/icons'; +import { IconFiles, IconRun, IconEye, IconSettings, IconFileOff, IconFileCode } from '@tabler/icons'; import EnvironmentSelector from 'components/Environments/EnvironmentSelector'; import GlobalEnvironmentSelector from 'components/GlobalEnvironments/EnvironmentSelector'; import { addTab } from 'providers/ReduxStore/slices/tabs'; @@ -8,6 +8,7 @@ import { useDispatch } from 'react-redux'; import ToolHint from 'components/ToolHint'; import StyledWrapper from './StyledWrapper'; import JsSandboxMode from 'components/SecuritySettings/JsSandboxMode'; +import { toggleCollectionFileMode } from 'providers/ReduxStore/slices/collections/index'; const CollectionToolBar = ({ collection }) => { const dispatch = useDispatch(); @@ -42,6 +43,14 @@ const CollectionToolBar = ({ collection }) => { ); }; + const handleFileModeClick = () => { + dispatch( + toggleCollectionFileMode({ + collectionUid: collection.uid + }) + ); + }; + return (
@@ -50,6 +59,22 @@ const CollectionToolBar = ({ collection }) => { {collection?.name}
+ + {collection?.fileMode ? ( + + + + ) : ( + + + + )} + diff --git a/packages/bruno-app/src/providers/Hotkeys/index.js b/packages/bruno-app/src/providers/Hotkeys/index.js index f9316eb944..b24197925c 100644 --- a/packages/bruno-app/src/providers/Hotkeys/index.js +++ b/packages/bruno-app/src/providers/Hotkeys/index.js @@ -15,6 +15,7 @@ import { import { findCollectionByUid, findItemInCollection } from 'utils/collections'; import { closeTabs, switchTab } from 'providers/ReduxStore/slices/tabs'; import { getKeyBindingsForActionAllOS } from './keyMappings'; +import { toggleCollectionFileMode } from 'providers/ReduxStore/slices/collections/index'; export const HotkeysContext = React.createContext(); @@ -211,6 +212,30 @@ export const HotkeysProvider = (props) => { }; }, [activeTabUid, tabs, collections, dispatch]); + // switch view mode + useEffect(() => { + Mousetrap.bind([...getKeyBindingsForActionAllOS('switchViewMode')], (e) => { + const activeTab = find(tabs, (t) => t.uid === activeTabUid); + if (activeTab) { + const collection = findCollectionByUid(collections, activeTab.collectionUid); + + if (collection) { + dispatch( + toggleCollectionFileMode({ + collectionUid: collection.uid + }) + ); + } + } + + return false; // this stops the event bubbling + }); + + return () => { + Mousetrap.unbind([...getKeyBindingsForActionAllOS('switchViewMode')]); + }; + }, [activeTabUid, tabs, collections, setShowEnvSettingsModal]); + return ( {showEnvSettingsModal && ( diff --git a/packages/bruno-app/src/providers/Hotkeys/keyMappings.js b/packages/bruno-app/src/providers/Hotkeys/keyMappings.js index 05ad4531b9..8cb69342ba 100644 --- a/packages/bruno-app/src/providers/Hotkeys/keyMappings.js +++ b/packages/bruno-app/src/providers/Hotkeys/keyMappings.js @@ -20,7 +20,8 @@ const KeyMapping = { windows: 'ctrl+pagedown', name: 'Switch to Next Tab' }, - closeAllTabs: { mac: 'command+shift+w', windows: 'ctrl+shift+w', name: 'Close All Tabs' } + closeAllTabs: { mac: 'command+shift+w', windows: 'ctrl+shift+w', name: 'Close All Tabs' }, + switchViewMode: { mac: 'command+shift+m', windows: 'ctrl+shift+m', name: 'Switch View Mode' }, }; /** 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 e16ab2fc7c..26ef517db0 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -1228,4 +1228,19 @@ export const hydrateCollectionWithUiStateSnapshot = (payload) => (dispatch, getS reject(error); } }); - }; \ No newline at end of file + }; + +export const saveFile = (content, pathname) => (dispatch, getState) => { + return new Promise(async (resolve, reject) => { + ipcRenderer + .invoke('renderer:save-file', pathname, content) + .then((res) => { + toast.success('File saved successfully!'); + }) + .then(resolve) + .catch((err) => { + toast.error('Failed to save file!'); + reject(err); + }); + }); +}; \ No newline at end of file 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 5432ad16d0..a64efd62c0 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -1606,6 +1606,7 @@ export const collectionsSlice = createSlice({ currentItem.request = file.data.request; currentItem.filename = file.meta.name; currentItem.pathname = file.meta.pathname; + currentItem.raw = file.data.raw; currentItem.draft = null; } else { currentSubItems.push({ @@ -1616,6 +1617,7 @@ export const collectionsSlice = createSlice({ request: file.data.request, filename: file.meta.name, pathname: file.meta.pathname, + raw: file.data.raw, draft: null }); } @@ -1692,6 +1694,7 @@ export const collectionsSlice = createSlice({ item.request = file.data.request; item.filename = file.meta.name; item.pathname = file.meta.pathname; + item.raw = file.data.raw; item.draft = null; } } @@ -1902,6 +1905,44 @@ export const collectionsSlice = createSlice({ set(folder, 'root.docs', action.payload.docs); } } + }, + updateFileContent: (state, action) => { + const collection = findCollectionByUid( + state.collections, + action.payload.collectionUid + ); + const type = action.payload.type; + + if (collection) { + if (type == 'request') { + const item = findItemInCollection(collection, action.payload.itemUid); + + if (item) { + if (!item.draft) { + item.draft = cloneDeep(item); + } + item.draft.raw = action.payload.content; + } + } + else if(type == 'folder') { + const item = findItemInCollection(collection, action.payload.itemUid); + if (item?.root) { + item.root.raw = action.payload.content; + } + } + else if(type == 'collection') { + if (collection?.root) { + collection.root.raw = action.payload.content; + } + } + } + }, + toggleCollectionFileMode: (state, action) => { + const { collectionUid } = action.payload; + const collection = findCollectionByUid(state.collections, collectionUid); + if (collection) { + collection.fileMode = !collection.fileMode; + } } } }); @@ -2002,7 +2043,9 @@ export const { runFolderEvent, resetCollectionRunner, updateRequestDocs, - updateFolderDocs + updateFolderDocs, + updateFileContent, + toggleCollectionFileMode } = collectionsSlice.actions; export default collectionsSlice.reducer; diff --git a/packages/bruno-electron/src/app/watcher.js b/packages/bruno-electron/src/app/watcher.js index 80a9c7dae5..e7855d336c 100644 --- a/packages/bruno-electron/src/app/watcher.js +++ b/packages/bruno-electron/src/app/watcher.js @@ -229,6 +229,7 @@ const add = async (win, pathname, collectionUid, collectionPath) => { let bruContent = fs.readFileSync(pathname, 'utf8'); file.data = collectionBruToJson(bruContent); + file.data.raw = bruContent; hydrateBruCollectionFileWithUuid(file.data); win.webContents.send('main:collection-tree-updated', 'addFile', file); @@ -255,6 +256,7 @@ const add = async (win, pathname, collectionUid, collectionPath) => { let bruContent = fs.readFileSync(pathname, 'utf8'); file.data = collectionBruToJson(bruContent); + file.data.raw = bruContent; hydrateBruCollectionFileWithUuid(file.data); win.webContents.send('main:collection-tree-updated', 'addFile', file); @@ -276,8 +278,8 @@ const add = async (win, pathname, collectionUid, collectionPath) => { try { let bruContent = fs.readFileSync(pathname, 'utf8'); - file.data = bruToJson(bruContent); + file.data.raw = bruContent; hydrateRequestWithUuid(file.data, pathname); win.webContents.send('main:collection-tree-updated', 'addFile', file); @@ -370,6 +372,7 @@ const change = async (win, pathname, collectionUid, collectionPath) => { let bruContent = fs.readFileSync(pathname, 'utf8'); file.data = collectionBruToJson(bruContent); + file.data.raw = bruContent; hydrateBruCollectionFileWithUuid(file.data); win.webContents.send('main:collection-tree-updated', 'change', file); return; @@ -393,6 +396,7 @@ const change = async (win, pathname, collectionUid, collectionPath) => { let bruContent = fs.readFileSync(pathname, 'utf8'); file.data = collectionBruToJson(bruContent); + file.data.raw = bruContent; hydrateBruCollectionFileWithUuid(file.data); win.webContents.send('main:collection-tree-updated', 'change', file); @@ -415,7 +419,7 @@ const change = async (win, pathname, collectionUid, collectionPath) => { const bru = fs.readFileSync(pathname, 'utf8'); file.data = bruToJson(bru); - + file.data.raw = bru; hydrateRequestWithUuid(file.data, pathname); win.webContents.send('main:collection-tree-updated', 'change', file); } catch (err) { diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index 71489afab0..c617e2d0be 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -32,6 +32,7 @@ const { deleteCookiesForDomain, getDomainsWithCookies } = require('../utils/cook const EnvironmentSecretsStore = require('../store/env-secrets'); const CollectionSecurityStore = require('../store/collection-security'); const UiStateSnapshotStore = require('../store/ui-state-snapshot'); +const { bruToJsonV2Grammar, collectionBruToJsonGrammar } = require('@usebruno/lang'); const environmentSecretsStore = new EnvironmentSecretsStore(); const collectionSecurityStore = new CollectionSecurityStore(); @@ -831,6 +832,45 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection throw new Error(error.message); } }); + + ipcMain.handle('renderer:save-file', async (event, pathname, content) => { + try { + if (!fs.existsSync(pathname)) { + throw new Error(`path: ${pathname} does not exist`); + } + + await writeFile(pathname, content); + } catch (error) { + return Promise.reject(error); + } + }); + + ipcMain.handle('renderer:grammar-match', async (event, { type, text }) => { + try { + let errors = []; + let result; + if (type == 'request') { + result = bruToJsonV2Grammar.match(text); + } + else if (type == 'collection' || type == 'folder'){ + result = collectionBruToJsonGrammar.match(text); + } + + if (!result.succeeded()) { + const errorPos = result.getInterval().startIdx; + const errorLine = text.substring(0, errorPos).split('\n').length; + const errorColumn = errorPos - text.lastIndexOf('\n', errorPos - 1) - 1; + errors.push({ + message: result?.message, + errorLine, + errorColumn + }); + return errors; + } + } catch (error) { + return Promise.reject(error); + } + }); }; const registerMainEventHandlers = (mainWindow, watcher, lastOpenedCollections) => { diff --git a/packages/bruno-lang/src/index.js b/packages/bruno-lang/src/index.js index 55a9569d7f..f574dab706 100644 --- a/packages/bruno-lang/src/index.js +++ b/packages/bruno-lang/src/index.js @@ -1,10 +1,9 @@ -const bruToJsonV2 = require('../v2/src/bruToJson'); +const { parser: bruToJsonV2, grammar: bruToJsonV2Grammar } = require('../v2/src/bruToJson'); +const { parser: collectionBruToJson, grammar: collectionBruToJsonGrammar } = require('../v2/src/collectionBruToJson'); +const { parser: bruToEnvJsonV2, grammar: bruToEnvJsonV2Grammar } = require('../v2/src/envToJson'); const jsonToBruV2 = require('../v2/src/jsonToBru'); -const bruToEnvJsonV2 = require('../v2/src/envToJson'); const envJsonToBruV2 = require('../v2/src/jsonToEnv'); const dotenvToJson = require('../v2/src/dotenvToJson'); - -const collectionBruToJson = require('../v2/src/collectionBruToJson'); const jsonToCollectionBru = require('../v2/src/jsonToCollectionBru'); // Todo: remove V2 suffixes @@ -12,12 +11,13 @@ const jsonToCollectionBru = require('../v2/src/jsonToCollectionBru'); module.exports = { bruToJsonV2, - jsonToBruV2, + bruToJsonV2Grammar, + collectionBruToJson, + collectionBruToJsonGrammar, bruToEnvJsonV2, + bruToEnvJsonV2Grammar, + jsonToBruV2, envJsonToBruV2, - - collectionBruToJson, jsonToCollectionBru, - dotenvToJson }; diff --git a/packages/bruno-lang/v2/src/bruToJson.js b/packages/bruno-lang/v2/src/bruToJson.js index 2fe5fb472a..dbaef280fa 100644 --- a/packages/bruno-lang/v2/src/bruToJson.js +++ b/packages/bruno-lang/v2/src/bruToJson.js @@ -707,4 +707,7 @@ const parser = (input) => { } }; -module.exports = parser; +module.exports = { + parser, + grammar +}; \ No newline at end of file diff --git a/packages/bruno-lang/v2/src/collectionBruToJson.js b/packages/bruno-lang/v2/src/collectionBruToJson.js index 61d373d91e..5fb0faa60a 100644 --- a/packages/bruno-lang/v2/src/collectionBruToJson.js +++ b/packages/bruno-lang/v2/src/collectionBruToJson.js @@ -425,4 +425,7 @@ const parser = (input) => { } }; -module.exports = parser; +module.exports = { + parser, + grammar +}; \ No newline at end of file diff --git a/packages/bruno-lang/v2/src/envToJson.js b/packages/bruno-lang/v2/src/envToJson.js index eef4de375d..9dbb486dee 100644 --- a/packages/bruno-lang/v2/src/envToJson.js +++ b/packages/bruno-lang/v2/src/envToJson.js @@ -164,4 +164,7 @@ const parser = (input) => { } }; -module.exports = parser; +module.exports = { + parser, + grammar +}; \ No newline at end of file From 48c2c589b9fefeec63580471f5517183a664609a Mon Sep 17 00:00:00 2001 From: lohxt1 Date: Sun, 19 Jan 2025 21:35:08 +0530 Subject: [PATCH 27/31] updated ui for file explorer to show all files in a collection ~ watcher add/change logic for all file types ~ component for dynamically setting file icon based on file extension ~ ability to modify file contents in file explorer mode --- package-lock.json | 15 ++++- packages/bruno-app/package.json | 1 + .../bruno-app/src/components/Bruno/index.js | 4 +- .../components/FileEditor/CodeEditor/index.js | 2 +- .../src/components/FileEditor/index.js | 5 +- .../src/components/RequestTabPanel/index.js | 23 +++++++- .../RequestTabs/RequestTab/index.js | 7 ++- .../CollectionItemIcon/index.js | 30 ++++++++++ .../Collection/CollectionItem/index.js | 37 +++++++++--- .../Sidebar/Collections/Collection/index.js | 22 ++++---- .../ReduxStore/slices/collections/index.js | 17 ++++-- .../src/providers/ReduxStore/slices/tabs.js | 27 ++++++--- .../bruno-app/src/utils/collections/index.js | 4 ++ packages/bruno-electron/src/app/watcher.js | 56 +++++++++++++++++++ packages/bruno-electron/src/cache/fileUids.js | 32 +++++++++++ packages/bruno-electron/src/ipc/collection.js | 5 +- 16 files changed, 238 insertions(+), 49 deletions(-) create mode 100644 packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CollectionItemIcon/index.js create mode 100644 packages/bruno-electron/src/cache/fileUids.js diff --git a/package-lock.json b/package-lock.json index 98b2208634..baa93e1fab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9771,7 +9771,6 @@ "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "dev": true, "license": "MIT" }, "node_modules/colorette": { @@ -19610,6 +19609,19 @@ "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==", "license": "MIT" }, + "node_modules/react-file-icon": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/react-file-icon/-/react-file-icon-1.5.0.tgz", + "integrity": "sha512-6K2/nAI69CS838HOS+4S95MLXwf1neWywek1FgqcTFPTYjnM8XT7aBLz4gkjoqQKY9qPhu3A2tu+lvxhmZYY9w==", + "dependencies": { + "colord": "^2.9.3", + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "^18.0.0 || ^17.0.0 || ^16.2.0", + "react-dom": "^18.0.0 || ^17.0.0 || ^16.2.0" + } + }, "node_modules/react-hot-toast": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", @@ -24071,6 +24083,7 @@ "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dom": "19.0.0", + "react-file-icon": "^1.5.0", "react-hot-toast": "^2.4.0", "react-i18next": "^15.0.1", "react-inspector": "^6.0.2", diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json index d4fb47bcf4..f456f3cad9 100644 --- a/packages/bruno-app/package.json +++ b/packages/bruno-app/package.json @@ -61,6 +61,7 @@ "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dom": "19.0.0", + "react-file-icon": "^1.5.0", "react-hot-toast": "^2.4.0", "react-i18next": "^15.0.1", "react-inspector": "^6.0.2", diff --git a/packages/bruno-app/src/components/Bruno/index.js b/packages/bruno-app/src/components/Bruno/index.js index 58279cc337..3d02c3908c 100644 --- a/packages/bruno-app/src/components/Bruno/index.js +++ b/packages/bruno-app/src/components/Bruno/index.js @@ -1,8 +1,8 @@ import React from 'react'; -const Bruno = ({ width }) => { +const Bruno = ({ width, className }) => { return ( - + ({ message: error?.message, severity: 'error', diff --git a/packages/bruno-app/src/components/FileEditor/index.js b/packages/bruno-app/src/components/FileEditor/index.js index 9f2981985d..924de493bc 100644 --- a/packages/bruno-app/src/components/FileEditor/index.js +++ b/packages/bruno-app/src/components/FileEditor/index.js @@ -6,6 +6,7 @@ import { saveFile } from 'providers/ReduxStore/slices/collections/actions'; import { IconDeviceFloppy } from '@tabler/icons'; import { updateFileContent } from "providers/ReduxStore/slices/collections"; import { toggleCollectionFileMode } from 'providers/ReduxStore/slices/collections/index'; +import * as path from 'path'; const FileEditor = ({ item, collection, type }) => { const dispatch = useDispatch(); @@ -38,8 +39,8 @@ const FileEditor = ({ item, collection, type }) => { }; const hasChanges = item?.draft !== null; - - const editorMode = item?.type == 'js' ? 'javascript' : item?.type == 'json' ? 'javascript' : 'application/text'; + const fileExtname = path.extname(item?.filename); + const editorMode = fileExtname == '.js' ? 'javascript' : fileExtname == '.json' ? 'javascript' : 'application/text'; return (
diff --git a/packages/bruno-app/src/components/RequestTabPanel/index.js b/packages/bruno-app/src/components/RequestTabPanel/index.js index ed5d0893f9..7cdf77e149 100644 --- a/packages/bruno-app/src/components/RequestTabPanel/index.js +++ b/packages/bruno-app/src/components/RequestTabPanel/index.js @@ -157,9 +157,14 @@ const RequestTabPanel = () => { if (focusedTab.type === 'collection-settings') { if (collection.fileMode) { + let _item = { + ...collection?.root, + uid: collection?.uid, + pathname: `${collection?.pathname}/collection.bru` + }; return ( - + ); } @@ -169,21 +174,33 @@ const RequestTabPanel = () => { if (focusedTab.type === 'folder-settings') { const folder = findItemInCollection(collection, focusedTab.folderUid); if (collection.fileMode) { + let _item = { + ...folder?.root, + uid: folder?.uid, + pathname: `${folder?.pathname}/folder.bru` + }; return ( - + ); } return ; } - const item = findItemInCollection(collection, activeTabUid); if (!item || !item.uid) { return ; } + if (focusedTab.type === 'misc') { + return ( + + + + ); + } + if (collection.fileMode) { return ( diff --git a/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js b/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js index e73313c139..7a6926f056 100644 --- a/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js +++ b/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js @@ -18,6 +18,7 @@ import NewRequest from 'components/Sidebar/NewRequest/index'; import CloseTabIcon from './CloseTabIcon'; import DraftTabIcon from './DraftTabIcon'; import { flattenItems } from 'utils/collections/index'; +import CollectionItemIcon from 'components/Sidebar/Collections/Collection/CollectionItem/CollectionItemIcon/index'; const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUid }) => { const dispatch = useDispatch(); @@ -144,7 +145,7 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi /> )}
{ if (!item.draft) return handleMouseUp(e); @@ -157,10 +158,10 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi }} > - {method} + {collection?.fileMode ? : method} - {item.name} + {collection?.fileMode ? item?.filename : item.name} { + const extname = path.extname(filename)?.slice(1,); + + switch(extname) { + case 'bru': + return ; + break; + default: + return
+ +
; + break; + } +} + +export default CollectionItemIcon; \ No newline at end of file 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 76eb5fb5e3..dcf50f64dd 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 @@ -25,12 +25,16 @@ import toast from 'react-hot-toast'; import StyledWrapper from './StyledWrapper'; import NetworkError from 'components/ResponsePane/NetworkError/index'; import CollectionItemInfo from './CollectionItemInfo/index'; +import Bruno from 'components/Bruno/index'; +import CollectionItemIcon from './CollectionItemIcon/index'; +import { isItemARequestOrAMisc } from 'utils/collections/index'; const CollectionItem = ({ item, collection, searchText }) => { const tabs = useSelector((state) => state.tabs.tabs); const activeTabUid = useSelector((state) => state.tabs.activeTabUid); const isSidebarDragging = useSelector((state) => state.app.isDragging); const dispatch = useDispatch(); + const isCollectionInFileMode = collection?.fileMode; const [renameItemModalOpen, setRenameItemModalOpen] = useState(false); const [cloneItemModalOpen, setCloneItemModalOpen] = useState(false); @@ -129,6 +133,7 @@ const CollectionItem = ({ item, collection, searchText }) => { ); return; } + if (isItemAFolder(item)) { dispatch( addTab({ uid: item.uid, @@ -142,6 +147,23 @@ const CollectionItem = ({ item, collection, searchText }) => { collectionUid: collection.uid }) ); + } + if (itemIsOpenedInTabs(item, tabs)) { + dispatch( + focusTab({ + uid: item.uid + }) + ); + return; + } + dispatch( + addTab({ + uid: item.uid, + collectionUid: collection.uid, + type: 'misc' + }) + ); + return; }; const handleFolderCollapse = () => { @@ -230,6 +252,8 @@ const CollectionItem = ({ item, collection, searchText }) => { const requestItems = sortRequestItems(filter(item.items, (i) => isItemARequest(i))); const folderItems = sortFolderItems(filter(item.items, (i) => isItemAFolder(i))); + const requestAndMiscItems = sortFolderItems(filter(item?.items, (i) => isItemARequestOrAMisc(i))); + const allItems = collection?.fileMode ? [...folderItems, ...requestAndMiscItems] : [...folderItems, ...requestItems]; return ( @@ -303,9 +327,9 @@ const CollectionItem = ({ item, collection, searchText }) => { onContextMenu={handleRightClick} onDoubleClick={handleDoubleClick} > - + {isCollectionInFileMode && item?.type !== 'folder' ? : } - {item.name} + {isCollectionInFileMode? item?.filename : item.name}
@@ -418,13 +442,8 @@ const CollectionItem = ({ item, collection, searchText }) => { {!itemIsCollapsed ? (
- {folderItems && folderItems.length - ? folderItems.map((i) => { - return ; - }) - : null} - {requestItems && requestItems.length - ? requestItems.map((i) => { + {allItems && allItems.length + ? allItems.map((i) => { return ; }) : null} diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js index 61dd8b6ed7..083de3fd67 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js @@ -21,6 +21,7 @@ import exportCollection from 'utils/collections/export'; import RenameCollection from './RenameCollection'; import StyledWrapper from './StyledWrapper'; import CloneCollection from './CloneCollection/index'; +import { isItemARequestOrAMisc } from 'utils/collections/index'; const Collection = ({ collection, searchText }) => { const [showNewFolderModal, setShowNewFolderModal] = useState(false); @@ -130,8 +131,10 @@ const Collection = ({ collection, searchText }) => { return items.sort((a, b) => a.name.localeCompare(b.name)); }; - const requestItems = sortRequestItems(filter(collection.items, (i) => isItemARequest(i))); - const folderItems = sortFolderItems(filter(collection.items, (i) => isItemAFolder(i))); + const requestItems = sortRequestItems(filter(collection?.items, (i) => isItemARequest(i))); + const folderItems = sortFolderItems(filter(collection?.items, (i) => isItemAFolder(i))); + const requestAndMiscItems = sortFolderItems(filter(collection?.items, (i) => isItemARequestOrAMisc(i))); + const allItems = collection?.fileMode ? [...folderItems, ...requestAndMiscItems] : [...folderItems, ...requestItems]; return ( @@ -160,7 +163,7 @@ const Collection = ({ collection, searchText }) => { style={{ width: 16, minWidth: 16, color: 'rgb(160 160 160)' }} onClick={handleClick} /> -