diff --git a/packages/bruno-app/src/components/PathDisplay/StyledWrapper.js b/packages/bruno-app/src/components/PathDisplay/StyledWrapper.js index bdaca8bbf..326e9f8da 100644 --- a/packages/bruno-app/src/components/PathDisplay/StyledWrapper.js +++ b/packages/bruno-app/src/components/PathDisplay/StyledWrapper.js @@ -1,6 +1,7 @@ import styled from 'styled-components'; const StyledWrapper = styled.div` + width: 100%; .path-display { background: ${(props) => props.theme.requestTabPanel.url.bg}; border-radius: 4px; @@ -23,14 +24,14 @@ const StyledWrapper = styled.div` } - .filename, .file-extension { + .name-container, .file-extension { color: ${(props) => props.theme.colors.text.yellow}; } .separator { color: ${(props) => props.theme.text}; opacity: 0.6; - margin: 0 1px; + margin: 0 2px; } } `; diff --git a/packages/bruno-app/src/components/PathDisplay/index.js b/packages/bruno-app/src/components/PathDisplay/index.js index b7ffa087f..7413d5748 100644 --- a/packages/bruno-app/src/components/PathDisplay/index.js +++ b/packages/bruno-app/src/components/PathDisplay/index.js @@ -1,63 +1,22 @@ import React from 'react'; -import { IconEdit, IconFolder, IconFile } from '@tabler/icons'; +import { IconFolder, IconFile } from '@tabler/icons'; import path from 'utils/common/path'; import StyledWrapper from './StyledWrapper'; const PathDisplay = ({ - collection, - item, - filename, - extension = '.bru', - showExtension = true, - toggleEditingFilename, - showDirectory = false + baseName = '', + iconType = 'file' }) => { - const relativePath = item?.pathname && path.relative(collection?.pathname, showDirectory ? path.dirname(item?.pathname) : item?.pathname); - const pathSegments = relativePath?.split(path.sep).filter(Boolean); - return ( -
-
- - toggleEditingFilename(true)} - /> -
-
-
-
- {showExtension ? : } -
-
-
- {collection?.name} -
- - {pathSegments?.length > 0 && pathSegments?.map((segment, index) => ( - - / -
- {segment} -
-
- ))} - - {collection && ( - / - )} - - - {filename} - {showExtension && filename?.length ? ( - {extension} - ) : null} - -
+
+
+
+ {iconType === 'file' ? : }
+ + {baseName} +
diff --git a/packages/bruno-app/src/components/Preferences/index.js b/packages/bruno-app/src/components/Preferences/index.js index 3635ca5a9..2319d4c78 100644 --- a/packages/bruno-app/src/components/Preferences/index.js +++ b/packages/bruno-app/src/components/Preferences/index.js @@ -46,7 +46,7 @@ const Preferences = ({ onClose }) => { return ( -
+
setTab('general')}> General diff --git a/packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Body/index.js b/packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Body/index.js index 9feb49cc5..6f9d2f832 100644 --- a/packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Body/index.js +++ b/packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Body/index.js @@ -6,7 +6,7 @@ const BodyBlock = ({ collection, data, dataBuffer, headers, error, item, width } return (
toggleBody(!isBodyCollapsed)}> -
+      
         
{isBodyCollapsed ? '▼' : '▶'}
Body
diff --git a/packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Headers/index.js b/packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Headers/index.js index 91de2e5cc..bb8448f70 100644 --- a/packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Headers/index.js +++ b/packages/bruno-app/src/components/ResponsePane/Timeline/TimelineItem/Common/Headers/index.js @@ -6,7 +6,7 @@ const HeadersBlock = ({ headers, type }) => { return (
toggleHeaders(!areHeadersCollapsed)}> -
+        
           
{areHeadersCollapsed ? '▼' : '▶'}
Headers {headers && Object.keys(headers).length > 0 &&
({Object.keys(headers).length})
diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CloneCollection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CloneCollection/index.js index 30337e3cb..ab9fc1a7f 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CloneCollection/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CloneCollection/index.js @@ -7,20 +7,22 @@ import { cloneCollection } from 'providers/ReduxStore/slices/collections/actions import toast from 'react-hot-toast'; import Modal from 'components/Modal'; import { sanitizeName, validateName, validateNameError } from 'utils/common/regex'; -import PathDisplay from 'components/PathDisplay/index'; +import Help from 'components/Help'; +import PathDisplay from 'components/PathDisplay'; import { useState } from 'react'; -import { IconArrowBackUp } from "@tabler/icons"; +import { IconArrowBackUp, IconEdit } from "@tabler/icons"; const CloneCollection = ({ onClose, collection }) => { const inputRef = useRef(); const dispatch = useDispatch(); - const [isEditingFilename, toggleEditingFilename] = useState(false); + const [isEditing, toggleEditing] = useState(false); + const { name } = collection; const formik = useFormik({ enableReinitialize: true, initialValues: { - collectionName: '', - collectionFolderName: '', + collectionName: `${name} copy`, + collectionFolderName: `${sanitizeName(name)} copy`, collectionLocation: '' }, validationSchema: Yup.object({ @@ -31,7 +33,7 @@ const CloneCollection = ({ onClose, collection }) => { collectionFolderName: Yup.string() .min(1, 'must be at least 1 character') .max(255, 'must be 255 characters or less') - .test('is-valid-dir-name', function(value) { + .test('is-valid-collection-name', function(value) { const isValid = validateName(value); return isValid ? true : this.createError({ message: validateNameError(value) }); }) @@ -92,7 +94,7 @@ const CloneCollection = ({ onClose, collection }) => { className="block textbox mt-2 w-full" onChange={(e) => { formik.handleChange(e); - !isEditingFilename && formik.setFieldValue('collectionFolderName', sanitizeName(e.target.value)); + !isEditing && formik.setFieldValue('collectionFolderName', sanitizeName(e.target.value)); }} autoComplete="off" autoCorrect="off" @@ -124,49 +126,70 @@ const CloneCollection = ({ onClose, collection }) => {
{formik.errors.collectionLocation}
) : null}
- + Browse
- {isEditingFilename ? - <> -
-
- - toggleEditingFilename(false)} - /> -
- +
+ + {isEditing ? ( + toggleEditing(false)} + /> + ) : ( + toggleEditing(true)} + /> + )} +
+ {isEditing ? ( + + ) : ( +
+
- - : - - } - {formik.touched.collectionFolderName && formik.errors.collectionFolderName ? ( -
{formik.errors.collectionFolderName}
- ) : null} + )} + + {formik.touched.collectionFolderName && formik.errors.collectionFolderName ? ( +
{formik.errors.collectionFolderName}
+ ) : null} +
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 000000000..d46e186d2 --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CloneCollectionItem/StyledWrapper.js @@ -0,0 +1,12 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + .advanced-options { + .caret { + color: ${(props) => props.theme.textLink}; + fill: ${(props) => props.theme.textLink}; + } + } +`; + +export default StyledWrapper; \ No newline at end of file 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 428ef7dfc..9194e8a64 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, { useState, useRef, useEffect } from 'react'; +import React, { useState, useRef, useEffect, forwardRef } from 'react'; import toast from 'react-hot-toast'; import { useFormik } from 'formik'; import * as Yup from 'yup'; @@ -6,24 +6,32 @@ import Modal from 'components/Modal'; import { useDispatch } from 'react-redux'; import { isItemAFolder } from 'utils/tabs'; import { cloneItem } from 'providers/ReduxStore/slices/collections/actions'; -import { IconArrowBackUp } from '@tabler/icons'; -import path from "utils/common/path"; +import { IconArrowBackUp, IconEdit, IconCaretDown } from "@tabler/icons"; import { sanitizeName, validateName, validateNameError } from 'utils/common/regex'; +import Help from 'components/Help'; import PathDisplay from 'components/PathDisplay/index'; +import path from 'utils/common/path'; +import Portal from 'components/Portal'; +import Dropdown from 'components/Dropdown'; +import StyledWrapper from './StyledWrapper'; const CloneCollectionItem = ({ collection, item, onClose }) => { const dispatch = useDispatch(); const isFolder = isItemAFolder(item); const inputRef = useRef(); - const [isEditingFilename, toggleEditingFilename] = useState(false); + const [isEditing, toggleEditing] = useState(false); const itemName = item?.name; const itemType = item?.type; - const itemFilename = item?.filename ? path.parse(item?.filename).name : ''; + const [showFilesystemName, toggleShowFilesystemName] = useState(false); + + const dropdownTippyRef = useRef(); + const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); + const formik = useFormik({ enableReinitialize: true, initialValues: { - name: itemName, - filename: sanitizeName(itemFilename) + name: `${itemName} copy`, + filename: `${sanitizeName(itemName)} copy` }, validationSchema: Yup.object({ name: Yup.string() @@ -34,7 +42,7 @@ const CloneCollectionItem = ({ collection, item, onClose }) => { .min(1, 'must be at least 1 character') .max(255, 'must be 255 characters or less') .required('name is required') - .test('is-valid-filename', function(value) { + .test('is-valid-name', function(value) { const isValid = validateName(value); return isValid ? true : this.createError({ message: validateNameError(value) }); }) @@ -58,86 +66,159 @@ const CloneCollectionItem = ({ collection, item, onClose }) => { } }, [inputRef]); - const onSubmit = () => formik.handleSubmit(); + const AdvancedOptions = forwardRef((props, ref) => { + return ( +
+ + +
+ ); + }); return ( - -
e.preventDefault()}> -
- - { - formik.setFieldValue('name', 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} -
- {isEditingFilename ? ( -
-
- - toggleEditingFilename(false)} - /> + + + + +
+ + { + formik.setFieldValue('name', e.target.value); + !isEditing && formik.setFieldValue('filename', sanitizeName(e.target.value)); + }} + value={formik.values.name || ''} + /> + {formik.touched.name && formik.errors.name ?
{formik.errors.name}
: null} +
+ + {showFilesystemName && ( +
+
+ + {isEditing ? ( + toggleEditing(false)} + /> + ) : ( + toggleEditing(true)} + /> + )} +
+ {isEditing ? ( +
+ + {itemType !== 'folder' && .bru} +
+ ) : ( +
+ +
+ )} + {formik.touched.filename && formik.errors.filename ? ( +
{formik.errors.filename}
+ ) : null}
-
- - {itemType !== 'folder' && .bru} + )} + +
+
+ } placement="bottom-start"> +
{ + dropdownTippyRef.current.hide(); + toggleShowFilesystemName(!showFilesystemName); + }} + > + {showFilesystemName ? 'Hide Filesystem Name' : 'Show Filesystem Name'} +
+
+
+
+ + + + + +
- ) : ( - - )} - {formik.touched.filename && formik.errors.filename ? ( -
{formik.errors.filename}
- ) : null} - - + + + + ); }; 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 index 6df7f0634..ca46d0d79 100644 --- 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 @@ -1,11 +1,10 @@ import React from 'react'; import Modal from 'components/Modal'; -import path from "utils/common/path"; +import Help from 'components/Help'; + +const CollectionItemInfo = ({ item, onClose }) => { + const { name, filename, type } = item; -const CollectionItemInfo = ({ collection, item, onClose }) => { - const { pathname: collectionPathname } = collection; - const { name, filename, pathname, type } = item; - const relativePathname = path.relative(collectionPathname, pathname); return ( { hideCancel={true} hideFooter={true} > -
- - - - - - - - - - - - - - - -
Name :{name}
{type=='folder' ? 'Directory Name' : 'File Name'} :{filename}
Pathname :{relativePathname}
-
+
+ + + + + + + + + + + +
+ {type=='folder' ? 'Folder Name' : 'Request Name'} + + :{name} +
+ {type == 'folder' ? 'Folder Name' : 'File Name'} + (on filesystem) + {type == 'folder' ? ( + +

+ The name of the folder on your filesystem. +

+
+ ) : ( + +

+ Bruno saves each request as a file in your collection's folder. +

+
+ )} +
+ : + {filename} +
+
); }; 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 000000000..d46e186d2 --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/RenameCollectionItem/StyledWrapper.js @@ -0,0 +1,12 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + .advanced-options { + .caret { + color: ${(props) => props.theme.textLink}; + fill: ${(props) => props.theme.textLink}; + } + } +`; + +export default StyledWrapper; \ No newline at end of file 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 9d174560d..705c45c79 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,4 +1,4 @@ -import React, { useRef, useEffect, useState } from 'react'; +import React, { useRef, useEffect, useState, forwardRef } from 'react'; import { useFormik } from 'formik'; import * as Yup from 'yup'; import Modal from 'components/Modal'; @@ -6,20 +6,29 @@ import { useDispatch } from 'react-redux'; import { isItemAFolder } from 'utils/tabs'; import { renameItem, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import path from 'utils/common/path'; -import { IconArrowBackUp } from '@tabler/icons'; +import { IconArrowBackUp, IconEdit, IconCaretDown } from '@tabler/icons'; import { sanitizeName, validateName, validateNameError } from 'utils/common/regex'; import toast from 'react-hot-toast'; import { closeTabs } from 'providers/ReduxStore/slices/tabs'; +import Help from 'components/Help'; import PathDisplay from 'components/PathDisplay'; +import Portal from 'components/Portal'; +import Dropdown from 'components/Dropdown'; +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 [isEditing, toggleEditing] = useState(false); const itemName = item?.name; const itemType = item?.type; const itemFilename = item?.filename ? path.parse(item?.filename).name : ''; + const [showFilesystemName, toggleShowFilesystemName] = useState(false); + + const dropdownTippyRef = useRef(); + const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); + const formik = useFormik({ enableReinitialize: true, initialValues: { @@ -35,7 +44,7 @@ const RenameCollectionItem = ({ collection, item, onClose }) => { .min(1, 'must be at least 1 character') .max(255, 'must be 255 characters or less') .required('name is required') - .test('is-valid-filename', function(value) { + .test('is-valid-name', function(value) { const isValid = validateName(value); return isValid ? true : this.createError({ message: validateNameError(value) }); }) @@ -77,86 +86,157 @@ const RenameCollectionItem = ({ collection, item, onClose }) => { } }, [inputRef]); - const onSubmit = () => formik.handleSubmit(); + const AdvancedOptions = forwardRef((props, ref) => { + return ( +
+ + +
+ ); + }); return ( - -
{e.preventDefault()}}> -
- - { - formik.setFieldValue('name', 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} -
- - {isEditingFilename ? ( -
-
-
- ) : ( - - )} - {formik.touched.filename && formik.errors.filename ? ( -
{formik.errors.filename}
- ) : null} - - + + {showFilesystemName && ( +
+
+ + {isEditing ? ( + toggleEditing(false)} + /> + ) : ( + toggleEditing(true)} + /> + )} +
+ {isEditing ? ( +
+ + {itemType !== 'folder' && .bru} +
+ ) : ( +
+ +
+ )} + {formik.touched.filename && formik.errors.filename ? ( +
{formik.errors.filename}
+ ) : null} +
+ )} +
+
+ } placement="bottom-start"> +
{ + dropdownTippyRef.current.hide(); + toggleShowFilesystemName(!showFilesystemName); + }} + > + {showFilesystemName ? 'Hide Filesystem Name' : 'Show Filesystem Name'} +
+
+
+
+ + + + + + +
+
+ + + + ); }; 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 41a8a9d32..6201dc7c0 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,6 @@ import toast from 'react-hot-toast'; import StyledWrapper from './StyledWrapper'; import NetworkError from 'components/ResponsePane/NetworkError/index'; import CollectionItemInfo from './CollectionItemInfo/index'; -import { findItemInCollection } from 'utils/collections'; import CollectionItemIcon from './CollectionItemIcon'; import { scrollToTheActiveTab } from 'utils/tabs'; diff --git a/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js b/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js index a1b48843f..07f390901 100644 --- a/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js +++ b/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js @@ -9,13 +9,13 @@ import Modal from 'components/Modal'; import { sanitizeName, validateName, validateNameError } from 'utils/common/regex'; import PathDisplay from 'components/PathDisplay/index'; import { useState } from 'react'; -import { IconArrowBackUp } from '@tabler/icons'; +import { IconArrowBackUp, IconEdit } from '@tabler/icons'; import Help from 'components/Help'; const CreateCollection = ({ onClose }) => { const inputRef = useRef(); const dispatch = useDispatch(); - const [isEditingFilename, toggleEditingFilename] = useState(false); + const [isEditing, toggleEditing] = useState(false); const formik = useFormik({ enableReinitialize: true, @@ -32,7 +32,7 @@ const CreateCollection = ({ onClose }) => { collectionFolderName: Yup.string() .min(1, 'must be at least 1 character') .max(255, 'must be 255 characters or less') - .test('is-valid-dir-name', function(value) { + .test('is-valid-collection-name', function(value) { const isValid = validateName(value); return isValid ? true : this.createError({ message: validateNameError(value) }); }) @@ -86,7 +86,7 @@ const CreateCollection = ({ onClose }) => { className="block textbox mt-2 w-full" onChange={(e) => { formik.handleChange(e); - !isEditingFilename && formik.setFieldValue('collectionFolderName', sanitizeName(e.target.value)); + !isEditing && formik.setFieldValue('collectionFolderName', sanitizeName(e.target.value)); }} autoComplete="off" autoCorrect="off" @@ -105,7 +105,7 @@ const CreateCollection = ({ onClose }) => { Bruno stores your collections on your computer's filesystem.

- Choose where you want to store this collection. + Choose the location where you want to store this collection.

@@ -126,24 +126,46 @@ const CreateCollection = ({ onClose }) => {
{formik.errors.collectionLocation}
) : null}
- + Browse
- {isEditingFilename ? - <> -
-
- + {formik.values.collectionName?.trim()?.length > 0 && ( +
+
+ + {isEditing ? ( toggleEditingFilename(false)} + onClick={() => toggleEditing(false)} /> -
+ ) : ( + toggleEditing(true)} + /> + )} +
+ {isEditing ? ( { spellCheck="false" value={formik.values.collectionFolderName || ''} /> -
- - : - - } - {formik.touched.collectionFolderName && formik.errors.collectionFolderName ? ( -
{formik.errors.collectionFolderName}
- ) : null} + ) : ( +
+ +
+ )} + {formik.touched.collectionFolderName && formik.errors.collectionFolderName ? ( +
{formik.errors.collectionFolderName}
+ ) : null} +
+ )}
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 000000000..d46e186d2 --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/NewFolder/StyledWrapper.js @@ -0,0 +1,12 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + .advanced-options { + .caret { + color: ${(props) => props.theme.textLink}; + fill: ${(props) => props.theme.textLink}; + } + } +`; + +export default StyledWrapper; \ No newline at end of file diff --git a/packages/bruno-app/src/components/Sidebar/NewFolder/index.js b/packages/bruno-app/src/components/Sidebar/NewFolder/index.js index 3f6c4ccf4..c0b39b727 100644 --- a/packages/bruno-app/src/components/Sidebar/NewFolder/index.js +++ b/packages/bruno-app/src/components/Sidebar/NewFolder/index.js @@ -1,18 +1,27 @@ -import React, { useRef, useEffect, useState } from 'react'; +import React, { useRef, useEffect, useState, forwardRef } from 'react'; import { useFormik } from 'formik'; import toast from 'react-hot-toast'; import * as Yup from 'yup'; +import Portal from 'components/Portal'; import Modal from 'components/Modal'; import { useDispatch } from 'react-redux'; import { newFolder } from 'providers/ReduxStore/slices/collections/actions'; -import { IconArrowBackUp } from '@tabler/icons'; +import { IconArrowBackUp, IconEdit} from '@tabler/icons'; import { sanitizeName, validateName, validateNameError } from 'utils/common/regex'; -import PathDisplay from 'components/PathDisplay'; +import PathDisplay from 'components/PathDisplay/index'; +import Help from 'components/Help'; +import Dropdown from "components/Dropdown"; +import { IconCaretDown } from "@tabler/icons"; +import StyledWrapper from './StyledWrapper'; const NewFolder = ({ collection, item, onClose }) => { const dispatch = useDispatch(); const inputRef = useRef(); - const [isEditingFilename, toggleEditingFilename] = useState(false); + const [isEditing, toggleEditing] = useState(false); + const [showFilesystemName, toggleShowFilesystemName] = useState(false); + + const dropdownTippyRef = useRef(); + const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); const formik = useFormik({ enableReinitialize: true, @@ -58,80 +67,139 @@ const NewFolder = ({ collection, item, onClose }) => { } }, [inputRef]); - const onSubmit = () => formik.handleSubmit(); + const AdvancedOptions = forwardRef((props, ref) => { + return ( +
+ + +
+ ); + }); return ( - -
-
- - { - 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 ? ( -
-
- - toggleEditingFilename(false)} - /> + + + + + + { + formik.setFieldValue('folderName', e.target.value); + !isEditing && formik.setFieldValue('directoryName', sanitizeName(e.target.value)); + }} + value={formik.values.folderName || ''} + /> + {formik.touched.folderName && formik.errors.folderName ? ( +
{formik.errors.folderName}
+ ) : null} + + {showFilesystemName && ( +
+
+ + {isEditing ? ( + toggleEditing(false)} + /> + ): ( + toggleEditing(true)} + /> + )} +
+ {isEditing ? ( +
+ +
+ ) : ( +
+ +
+ )} + {formik.touched.directoryName && formik.errors.directoryName ? ( +
{formik.errors.directoryName}
+ ) : null} +
+ )} +
+
+ } placement="bottom-start"> +
{ + dropdownTippyRef.current.hide(); + toggleShowFilesystemName(!showFilesystemName); + }} + > + {showFilesystemName ? 'Hide Filesystem Name' : 'Show Filesystem Name'} +
+
+
+
+ + + + + + +
-
- -
-
- ) : ( - - )} - {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 872ba2877..338fb2e60 100644 --- a/packages/bruno-app/src/components/Sidebar/NewRequest/StyledWrapper.js +++ b/packages/bruno-app/src/components/Sidebar/NewRequest/StyledWrapper.js @@ -1,45 +1,53 @@ import styled from 'styled-components'; - const StyledWrapper = styled.div` - div.method-selector-container { - border: solid 1px ${(props) => props.theme.modal.input.border}; - border-right: none; - background-color: ${(props) => props.theme.modal.input.bg}; - border-top-left-radius: 3px; - border-bottom-left-radius: 3px; - .method-selector { - min-width: 80px; - } - } - div.method-selector-container, - div.input-container { - background-color: ${(props) => props.theme.modal.input.bg}; - height: 2.3rem; - } - div.input-container { - border: solid 1px ${(props) => props.theme.modal.input.border}; - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; - input { - background-color: ${(props) => props.theme.modal.input.bg}; - outline: none; - box-shadow: none; - &:focus { - outline: none !important; - box-shadow: none !important; - } - } - } - textarea.curl-command { - min-height: 150px; - } - .dropdown { - width: fit-content; - - .dropdown-item { - padding: 0.2rem 0.6rem !important; - } - } - `; +const StyledWrapper = styled.div` + div.method-selector-container { + border: solid 1px ${(props) => props.theme.modal.input.border}; + border-right: none; + background-color: ${(props) => props.theme.modal.input.bg}; + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; + .method-selector { + min-width: 80px; + } + } + div.method-selector-container, + div.input-container { + background-color: ${(props) => props.theme.modal.input.bg}; + height: 2.3rem; + } + div.input-container { + border: solid 1px ${(props) => props.theme.modal.input.border}; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + input { + background-color: ${(props) => props.theme.modal.input.bg}; + outline: none; + box-shadow: none; + &:focus { + outline: none !important; + box-shadow: none !important; + } + } + } - export default StyledWrapper; \ No newline at end of file + textarea.curl-command { + min-height: 150px; + } + .dropdown { + width: fit-content; + + .dropdown-item { + padding: 0.2rem 0.6rem !important; + } + } + + .advanced-options { + .caret { + color: ${(props) => props.theme.textLink}; + fill: ${(props) => props.theme.textLink}; + } + } +`; + +export default StyledWrapper; \ No newline at end of file diff --git a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js index 2f201f469..61fdcd22a 100644 --- a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js +++ b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js @@ -2,6 +2,7 @@ import React, { useRef, useEffect, useCallback, forwardRef, useState } from 'rea import { useFormik } from 'formik'; import * as Yup from 'yup'; import toast from 'react-hot-toast'; +import path from 'utils/common/path'; import { uuid } from 'utils/common'; import Modal from 'components/Modal'; import { useDispatch } from 'react-redux'; @@ -11,10 +12,12 @@ import { addTab } from 'providers/ReduxStore/slices/tabs'; import HttpMethodSelector from 'components/RequestPane/QueryUrl/HttpMethodSelector'; import { getDefaultRequestPaneTab } from 'utils/collections'; import { getRequestFromCurlCommand } from 'utils/curl'; -import { IconArrowBackUp, IconCaretDown } from '@tabler/icons'; +import { IconArrowBackUp, IconCaretDown, IconEdit } from '@tabler/icons'; import { sanitizeName, validateName, validateNameError } from 'utils/common/regex'; import Dropdown from 'components/Dropdown'; import PathDisplay from 'components/PathDisplay'; +import Portal from 'components/Portal'; +import Help from 'components/Help'; import StyledWrapper from './StyledWrapper'; const NewRequest = ({ collection, item, isEphemeral, onClose }) => { @@ -24,10 +27,14 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { brunoConfig: { presets: collectionPresets = {} } } = collection; const [curlRequestTypeDetected, setCurlRequestTypeDetected] = useState(null); + const [showFilesystemName, toggleShowFilesystemName] = useState(false); const dropdownTippyRef = useRef(); const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); + const advancedDropdownTippyRef = useRef(); + const onAdvancedDropdownCreate = (ref) => (advancedDropdownTippyRef.current = ref); + const Icon = forwardRef((props, ref) => { return (
@@ -57,7 +64,7 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { setCurlRequestTypeDetected(type); }; - const [isEditingFilename, toggleEditingFilename] = useState(false); + const [isEditing, toggleEditing] = useState(false); const getRequestType = (collectionPresets) => { if (!collectionPresets || !collectionPresets.requestType) { @@ -229,213 +236,279 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { } }; - return ( - - -
{ - if (e.key === 'Enter') { - e.preventDefault(); - formik.handleSubmit(); - } - }} + const AdvancedOptions = forwardRef((props, ref) => { + return ( +
+ + +
+ ); + }); -
- -
-
- - { - formik.setFieldValue('requestName', e.target.value); - !isEditingFilename && formik.setFieldValue('filename', sanitizeName(e.target.value)); - }} - value={formik.values.requestName || ''} - /> - {formik.touched.requestName && formik.errors.requestName ? ( -
{formik.errors.requestName}
- ) : null} -
- {isEditingFilename ? ( -
-
- - toggleEditingFilename(false)} - /> -
-
+
- .bru + + + { + formik.setFieldValue('requestMethod', 'POST'); + formik.handleChange(event); + }} + value="graphql-request" + checked={formik.values.requestType === 'graphql-request'} + /> + + + + +
- ) : ( - - )} - {formik.touched.filename && formik.errors.filename ? ( -
{formik.errors.filename}
- ) : null} - {formik.values.requestType !== 'from-curl' ? ( - <> +
+ + { + formik.setFieldValue('requestName', e.target.value); + !isEditing && formik.setFieldValue('filename', sanitizeName(e.target.value)); + }} + value={formik.values.requestName || ''} + /> + {formik.touched.requestName && formik.errors.requestName ? ( +
{formik.errors.requestName}
+ ) : null} +
+ {showFilesystemName && (
- -
-
- formik.setFieldValue('requestMethod', val)} +
+ + {isEditing ? ( + toggleEditing(false)} /> -
-
+ ) : ( + toggleEditing(true)} + /> + )} +
+ {isEditing ? ( +
+ .bru +
+ ) : ( +
+
-
- {formik.touched.requestUrl && formik.errors.requestUrl ? ( -
{formik.errors.requestUrl}
+ )} + {formik.touched.filename && formik.errors.filename ? ( +
{formik.errors.filename}
) : null}
- - ) : ( -
-
- - } placement="bottom-end"> -
{ - dropdownTippyRef.current.hide(); - curlRequestTypeChange('http-request'); - }} - > - HTTP + )} + {formik.values.requestType !== 'from-curl' ? ( + <> +
+ +
+
+ formik.setFieldValue('requestMethod', val)} + /> +
+
+ +
-
{formik.errors.requestUrl}
+ ) : null} +
+ + ) : ( +
+
+ + } placement="bottom-end"> +
{ + dropdownTippyRef.current.hide(); + curlRequestTypeChange('http-request'); + }} + > + HTTP +
+
{ + dropdownTippyRef.current.hide(); + curlRequestTypeChange('graphql-request'); + }} + > + GraphQL +
+
+
+ + {formik.touched.curlCommand && formik.errors.curlCommand ? ( +
{formik.errors.curlCommand}
+ ) : null} +
+ )} +
+
+ } placement="bottom-start"> +
{ - dropdownTippyRef.current.hide(); - curlRequestTypeChange('graphql-request'); + key="show-filesystem-name" + onClick={(e) => { + advancedDropdownTippyRef.current.hide(); + toggleShowFilesystemName(!showFilesystemName); }} > - GraphQL + {showFilesystemName ? 'Hide Filesystem Name' : 'Show Filesystem Name'}
- - {formik.touched.curlCommand && formik.errors.curlCommand ? ( -
{formik.errors.curlCommand}
- ) : null} +
+ + + + + + +
- )} - - - + + + + ); }; 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 43c80c49e..e2551e7c6 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -810,7 +810,7 @@ export const newHttpRequest = (params) => (dispatch, getState) => { file: null }, auth: auth ?? { - mode: 'none' + mode: 'inherit' } } }; diff --git a/packages/bruno-app/src/styles/globals.css b/packages/bruno-app/src/styles/globals.css index f81023479..ef2de6bbe 100644 --- a/packages/bruno-app/src/styles/globals.css +++ b/packages/bruno-app/src/styles/globals.css @@ -145,7 +145,7 @@ body { font-kerning: none; text-rendering: optimizeSpeed; letter-spacing: normal; - font-family: Inter, sans-serif !important; + font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important; overflow-x: hidden; } diff --git a/packages/bruno-app/src/utils/common/regex.js b/packages/bruno-app/src/utils/common/regex.js index 9338288f0..6bf5d7b4d 100644 --- a/packages/bruno-app/src/utils/common/regex.js +++ b/packages/bruno-app/src/utils/common/regex.js @@ -2,7 +2,7 @@ const invalidCharacters = /[<>:"/\\|?*\x00-\x1F]/g; // replace invalid character const reservedDeviceNames = /^(CON|PRN|AUX|NUL|COM[0-9]|LPT[0-9])$/i; const firstCharacter = /^[^.\s\-\<>:"/\\|?*\x00-\x1F]/; // no dot, space, or hyphen at start const middleCharacters = /^[^<>:"/\\|?*\x00-\x1F]*$/; // no invalid characters -const lastCharacter = /[^.\s]$/; // no dot or space at end, hyphen allowed +const lastCharacter = /^[^.\s\-\<>:"/\\|?*\x00-\x1F]/; // no dot or space at end, hyphen allowed export const variableNameRegex = /^[\w-.]*$/; @@ -29,6 +29,7 @@ export const validateName = (name) => { export const validateNameError = (name) => { if (!name) return "Name cannot be empty."; + if (name.length > 255) { return "Name cannot exceed 255 characters."; } diff --git a/packages/bruno-electron/src/app/about-bruno.js b/packages/bruno-electron/src/app/about-bruno.js new file mode 100644 index 000000000..484a062be --- /dev/null +++ b/packages/bruno-electron/src/app/about-bruno.js @@ -0,0 +1,176 @@ +module.exports = function aboutBruno({version}) { + return ` + + + + + + About Bruno + + + + + + + + + + + + + + + + + + + + + + + + + + +

Bruno ${version}

+ + + + `; +}; \ No newline at end of file diff --git a/packages/bruno-electron/src/app/menu-template.js b/packages/bruno-electron/src/app/menu-template.js index e662336ae..f2b4c82aa 100644 --- a/packages/bruno-electron/src/app/menu-template.js +++ b/packages/bruno-electron/src/app/menu-template.js @@ -1,7 +1,8 @@ const { ipcMain } = require('electron'); const os = require('os'); -const openAboutWindow = require('about-window').default; -const { join } = require('path'); +const { BrowserWindow } = require('electron'); +const { version } = require('../../package.json'); +const aboutBruno = require('./about-bruno'); const template = [ { @@ -77,14 +78,16 @@ const template = [ submenu: [ { label: 'About Bruno', - click: () => - openAboutWindow({ - product_name: 'Bruno', - icon_path: join(__dirname, '../about/256x256.png'), - css_path: join(__dirname, '../about/about.css'), - homepage: 'https://www.usebruno.com/', - package_json_dir: join(__dirname, '../..') - }) + click: () => { + const aboutWindow = new BrowserWindow({ + width: 350, + height: 250, + webPreferences: { + nodeIntegration: true, + }, + }); + aboutWindow.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(aboutBruno({version}))}`); + } }, { label: 'Documentation', click: () => ipcMain.emit('main:open-docs') } ] diff --git a/packages/bruno-electron/src/index.js b/packages/bruno-electron/src/index.js index 393da541c..8495875f5 100644 --- a/packages/bruno-electron/src/index.js +++ b/packages/bruno-electron/src/index.js @@ -32,7 +32,7 @@ const lastOpenedCollections = new LastOpenedCollections(); const contentSecurityPolicy = [ "default-src 'self'", "connect-src 'self' https://*.posthog.com", - "font-src 'self' https:", + "font-src 'self' https: data:;", "frame-src data:", // this has been commented out to make oauth2 work // "form-action 'none'", diff --git a/packages/bruno-electron/src/utils/filesystem.js b/packages/bruno-electron/src/utils/filesystem.js index aaff867b1..a95c116eb 100644 --- a/packages/bruno-electron/src/utils/filesystem.js +++ b/packages/bruno-electron/src/utils/filesystem.js @@ -131,7 +131,7 @@ const browseFiles = async (win, filters = [], properties = []) => { return []; } - return filePaths.map((path) => path.resolve(path)).filter((path) => isFile(path)); + return filePaths.map((filePath) => path.resolve(filePath)).filter((filePath) => isFile(filePath)); }; const chooseFileToSave = async (win, preferredFileName = '') => { diff --git a/packages/bruno-electron/src/utils/oauth2.js b/packages/bruno-electron/src/utils/oauth2.js index d483aac52..cbe7c2355 100644 --- a/packages/bruno-electron/src/utils/oauth2.js +++ b/packages/bruno-electron/src/utils/oauth2.js @@ -142,11 +142,13 @@ const getOAuth2TokenUsingAuthorizationCode = async ({ request, collectionUid, fo const axiosInstance = makeAxiosInstance(); // Interceptor to capture request data axiosInstance.interceptors.request.use((config) => { + const requestData = typeof config?.data === 'string' ? config?.data : safeStringifyJSON(config?.data); axiosRequestInfo = { method: config.method.toUpperCase(), url: config.url, headers: config.headers, - data: config.data, + data: requestData, + dataBuffer: Buffer.from(requestData), timestamp: Date.now(), }; return config; @@ -195,7 +197,8 @@ const getOAuth2TokenUsingAuthorizationCode = async ({ request, collectionUid, fo url: axiosRequestInfo?.url, method: axiosRequestInfo?.method, headers: axiosRequestInfo?.headers || {}, - body: axiosRequestInfo?.data, + data: axiosRequestInfo?.data, + dataBuffer: axiosRequestInfo?.dataBuffer, error: null }, response: { diff --git a/scripts/build-electron.js b/scripts/build-electron.js index ab44dcbdf..6d778ffdf 100644 --- a/scripts/build-electron.js +++ b/scripts/build-electron.js @@ -2,6 +2,7 @@ const os = require('os'); const fs = require('fs-extra'); const util = require('util'); const spawn = util.promisify(require('child_process').spawn); +const path = require('path'); async function deleteFileIfExists(filePath) { try { @@ -80,7 +81,7 @@ async function main() { // Copy build await copyFolderIfExists('packages/bruno-app/dist', 'packages/bruno-electron/web'); - // Change paths in next + // Update static paths const files = await fs.readdir('packages/bruno-electron/web'); for (const file of files) { if (file.endsWith('.html')) { @@ -90,6 +91,22 @@ async function main() { } } + // update font load paths + const cssDir = path.join('packages/bruno-electron/web/static/css'); + try { + const cssFiles = await fs.readdir(cssDir); + for (const file of cssFiles) { + if (file.endsWith('.css')) { + const filePath = path.join(cssDir, file); + let content = await fs.readFile(filePath, 'utf8'); + content = content.replace(/\/static\/font/g, '../../static/font'); + await fs.writeFile(filePath, content); + } + } + } catch (error) { + console.error(`Error updating font paths: ${error}`); + } + // Remove sourcemaps await removeSourceMapFiles('packages/bruno-electron/web'); diff --git a/scripts/build-electron.sh b/scripts/build-electron.sh index 42c19a2d8..7f3887c70 100755 --- a/scripts/build-electron.sh +++ b/scripts/build-electron.sh @@ -13,8 +13,9 @@ mkdir packages/bruno-electron/web cp -r packages/bruno-app/dist/* packages/bruno-electron/web -# Change paths in next +# Update static paths sed -i'' -e 's@/static/@static/@g' packages/bruno-electron/web/**.html +sed -i'' -e 's@/static/font@../../static/font@g' packages/bruno-electron/web/static/css/**.**.css # Remove sourcemaps find packages/bruno-electron/web -name '*.map' -type f -delete