feat(#197): prettier formatting on all files in packages/bruno-app

This commit is contained in:
Anoop M D 2023-09-18 13:37:00 +05:30
parent a103f41d85
commit 19a7f397bb
117 changed files with 1249 additions and 854 deletions

View File

@ -3,5 +3,5 @@
"tabWidth": 2, "tabWidth": 2,
"semi": true, "semi": true,
"singleQuote": true, "singleQuote": true,
"printWidth": 180 "printWidth": 120
} }

View File

@ -3,5 +3,5 @@
"tabWidth": 2, "tabWidth": 2,
"semi": true, "semi": true,
"singleQuote": true, "singleQuote": true,
"printWidth": 180 "printWidth": 120
} }

View File

@ -14,7 +14,11 @@ const Bruno = ({ width }) => {
stroke="none" stroke="none"
points="36,47.2521 32.9167,49.6688 30.4167,49.6688 30.3333,53.5021 31.0833,57.0021 32.1667,58.9188 35,60.4188 39.5833,59.8355 41.1667,58.0855 42.1667,53.8355 41.9167,49.8355 39.9167,50.0855" points="36,47.2521 32.9167,49.6688 30.4167,49.6688 30.3333,53.5021 31.0833,57.0021 32.1667,58.9188 35,60.4188 39.5833,59.8355 41.1667,58.0855 42.1667,53.8355 41.9167,49.8355 39.9167,50.0855"
/> />
<polygon fill="#3F3F3F" stroke="none" points="32.5,36.9188 30.9167,40.6688 33.0833,41.9188 34.3333,42.4188 38.6667,42.5855 41.5833,40.3355 39.8333,37.0855" /> <polygon
fill="#3F3F3F"
stroke="none"
points="32.5,36.9188 30.9167,40.6688 33.0833,41.9188 34.3333,42.4188 38.6667,42.5855 41.5833,40.3355 39.8333,37.0855"
/>
</g> </g>
<g id="hair" /> <g id="hair" />
<g id="skin" /> <g id="skin" />
@ -84,8 +88,27 @@ const Bruno = ({ width }) => {
strokeWidth="2" strokeWidth="2"
d="M52.6309,46.4628c0,0-3.0781,6.7216-7.8049,8.2712" d="M52.6309,46.4628c0,0-3.0781,6.7216-7.8049,8.2712"
/> />
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M19.437,46.969c0,0,3.0781,6.0823,7.8049,7.632" /> <path
<line x1="36.2078" x2="36.2078" y1="47.3393" y2="44.3093" fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" /> fill="none"
stroke="#000000"
strokeLinecap="round"
strokeLinejoin="round"
strokeMiterlimit="10"
strokeWidth="2"
d="M19.437,46.969c0,0,3.0781,6.0823,7.8049,7.632"
/>
<line
x1="36.2078"
x2="36.2078"
y1="47.3393"
y2="44.3093"
fill="none"
stroke="#000000"
strokeLinecap="round"
strokeLinejoin="round"
strokeMiterlimit="10"
strokeWidth="2"
/>
</g> </g>
</svg> </svg>
); );

View File

@ -6,7 +6,8 @@ const StyledWrapper = styled.div`
border: solid 1px ${(props) => props.theme.codemirror.border}; border: solid 1px ${(props) => props.theme.codemirror.border};
} }
.CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-vertical div { .CodeMirror-overlayscroll-horizontal div,
.CodeMirror-overlayscroll-vertical div {
background: #d2d7db; background: #d2d7db;
} }
@ -17,12 +18,14 @@ const StyledWrapper = styled.div`
// Todo: dark mode temporary fix // Todo: dark mode temporary fix
// Clean this // Clean this
.CodeMirror.cm-s-monokai { .CodeMirror.cm-s-monokai {
.CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-vertical div { .CodeMirror-overlayscroll-horizontal div,
.CodeMirror-overlayscroll-vertical div {
background: #444444; background: #444444;
} }
} }
.cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute { .cm-s-monokai span.cm-property,
.cm-s-monokai span.cm-attribute {
color: #9cdcfe !important; color: #9cdcfe !important;
} }
@ -30,16 +33,20 @@ const StyledWrapper = styled.div`
color: #ce9178 !important; color: #ce9178 !important;
} }
.cm-s-monokai span.cm-number{ .cm-s-monokai span.cm-number {
color: #b5cea8 !important; color: #b5cea8 !important;
} }
.cm-s-monokai span.cm-atom{ .cm-s-monokai span.cm-atom {
color: #569cd6 !important; color: #569cd6 !important;
} }
.cm-variable-valid{color: green} .cm-variable-valid {
.cm-variable-invalid{color: red} color: green;
}
.cm-variable-invalid {
color: red;
}
`; `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -43,7 +43,7 @@ export default class CodeEditor extends React.Component {
foldGutter: true, foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
readOnly: this.props.readOnly, readOnly: this.props.readOnly,
scrollbarStyle: "overlay", scrollbarStyle: 'overlay',
theme: this.props.theme === 'dark' ? 'monokai' : 'default', theme: this.props.theme === 'dark' ? 'monokai' : 'default',
extraKeys: { extraKeys: {
'Cmd-Enter': () => { 'Cmd-Enter': () => {
@ -96,7 +96,7 @@ export default class CodeEditor extends React.Component {
this.editor.setValue(this.props.value); this.editor.setValue(this.props.value);
} }
if(this.editor) { if (this.editor) {
let variables = getEnvironmentVariables(this.props.collection); let variables = getEnvironmentVariables(this.props.collection);
if (!isEqual(variables, this.variables)) { if (!isEqual(variables, this.variables)) {
this.addOverlay(); this.addOverlay();
@ -135,7 +135,7 @@ export default class CodeEditor extends React.Component {
defineCodeMirrorBrunoVariablesMode(variables, mode); defineCodeMirrorBrunoVariablesMode(variables, mode);
this.editor.setOption('mode', 'brunovariables'); this.editor.setOption('mode', 'brunovariables');
} };
_onEdit = () => { _onEdit = () => {
if (!this.ignoreChangeEvent && this.editor) { if (!this.ignoreChangeEvent && this.editor) {

View File

@ -5,7 +5,16 @@ import StyledWrapper from './StyledWrapper';
const Dropdown = ({ icon, children, onCreate, placement }) => { const Dropdown = ({ icon, children, onCreate, placement }) => {
return ( return (
<StyledWrapper className="dropdown"> <StyledWrapper className="dropdown">
<Tippy content={children} placement={placement || 'bottom-end'} animation={false} arrow={false} onCreate={onCreate} interactive={true} trigger="click" appendTo="parent"> <Tippy
content={children}
placement={placement || 'bottom-end'}
animation={false}
arrow={false}
onCreate={onCreate}
interactive={true}
trigger="click"
appendTo="parent"
>
{icon} {icon}
</Tippy> </Tippy>
</StyledWrapper> </StyledWrapper>

View File

@ -16,7 +16,10 @@ const CreateEnvironment = ({ collection, onClose }) => {
name: '' name: ''
}, },
validationSchema: Yup.object({ validationSchema: Yup.object({
name: Yup.string().min(1, 'must be atleast 1 characters').max(50, 'must be 50 characters or less').required('name is required') name: Yup.string()
.min(1, 'must be atleast 1 characters')
.max(50, 'must be 50 characters or less')
.required('name is required')
}), }),
onSubmit: (values) => { onSubmit: (values) => {
dispatch(addEnvironment(values.name, collection.uid)) dispatch(addEnvironment(values.name, collection.uid))
@ -40,7 +43,13 @@ const CreateEnvironment = ({ collection, onClose }) => {
return ( return (
<Portal> <Portal>
<Modal size="sm" title={'Create Environment'} confirmText="Create" handleConfirm={onSubmit} handleCancel={onClose}> <Modal
size="sm"
title={'Create Environment'}
confirmText="Create"
handleConfirm={onSubmit}
handleCancel={onClose}
>
<form className="bruno-form" onSubmit={formik.handleSubmit}> <form className="bruno-form" onSubmit={formik.handleSubmit}>
<div> <div>
<label htmlFor="name" className="block font-semibold"> <label htmlFor="name" className="block font-semibold">
@ -59,7 +68,9 @@ const CreateEnvironment = ({ collection, onClose }) => {
onChange={formik.handleChange} onChange={formik.handleChange}
value={formik.values.name || ''} value={formik.values.name || ''}
/> />
{formik.touched.name && formik.errors.name ? <div className="text-red-500">{formik.errors.name}</div> : null} {formik.touched.name && formik.errors.name ? (
<div className="text-red-500">{formik.errors.name}</div>
) : null}
</div> </div>
</form> </form>
</Modal> </Modal>

View File

@ -20,7 +20,13 @@ const DeleteEnvironment = ({ onClose, environment, collection }) => {
return ( return (
<Portal> <Portal>
<StyledWrapper> <StyledWrapper>
<Modal size="sm" title={'Delete Environment'} confirmText="Delete" handleConfirm={onConfirm} handleCancel={onClose}> <Modal
size="sm"
title={'Delete Environment'}
confirmText="Delete"
handleConfirm={onConfirm}
handleCancel={onClose}
>
Are you sure you want to delete <span className="font-semibold">{environment.name}</span> ? Are you sure you want to delete <span className="font-semibold">{environment.name}</span> ?
</Modal> </Modal>
</StyledWrapper> </StyledWrapper>

View File

@ -12,7 +12,7 @@ const Wrapper = styled.div`
} }
thead { thead {
color: ${(props) => props.theme.table.thead.color};; color: ${(props) => props.theme.table.thead.color};
font-size: 0.8125rem; font-size: 0.8125rem;
user-select: none; user-select: none;
} }

View File

@ -99,7 +99,12 @@ const EnvironmentVariables = ({ environment, collection }) => {
</td> </td>
<td> <td>
<div className="flex items-center"> <div className="flex items-center">
<input type="checkbox" checked={variable.enabled} className="mr-3 mousetrap" onChange={(e) => handleVarChange(e, variable, 'enabled')} /> <input
type="checkbox"
checked={variable.enabled}
className="mr-3 mousetrap"
onChange={(e) => handleVarChange(e, variable, 'enabled')}
/>
<button onClick={() => handleRemoveVars(variable)}> <button onClick={() => handleRemoveVars(variable)}>
<IconTrash strokeWidth={1.5} size={20} /> <IconTrash strokeWidth={1.5} size={20} />
</button> </button>
@ -119,7 +124,12 @@ const EnvironmentVariables = ({ environment, collection }) => {
</div> </div>
<div> <div>
<button type="submit" className="submit btn btn-md btn-secondary mt-2" disabled={!hasChanges} onClick={saveChanges}> <button
type="submit"
className="submit btn btn-md btn-secondary mt-2"
disabled={!hasChanges}
onClick={saveChanges}
>
Save Save
</button> </button>
</div> </div>

View File

@ -10,8 +10,16 @@ const EnvironmentDetails = ({ environment, collection }) => {
return ( return (
<div className="px-6 flex-grow flex flex-col pt-6" style={{ maxWidth: '700px' }}> <div className="px-6 flex-grow flex flex-col pt-6" style={{ maxWidth: '700px' }}>
{openEditModal && <RenameEnvironment onClose={() => setOpenEditModal(false)} environment={environment} collection={collection} />} {openEditModal && (
{openDeleteModal && <DeleteEnvironment onClose={() => setOpenDeleteModal(false)} environment={environment} collection={collection} />} <RenameEnvironment onClose={() => setOpenEditModal(false)} environment={environment} collection={collection} />
)}
{openDeleteModal && (
<DeleteEnvironment
onClose={() => setOpenDeleteModal(false)}
environment={environment}
collection={collection}
/>
)}
<div className="flex"> <div className="flex">
<div className="flex flex-grow items-center"> <div className="flex flex-grow items-center">
<IconDatabase className="cursor-pointer" size={20} strokeWidth={1.5} /> <IconDatabase className="cursor-pointer" size={20} strokeWidth={1.5} />

View File

@ -14,12 +14,12 @@ const EnvironmentList = ({ collection }) => {
const prevEnvUids = usePrevious(envUids); const prevEnvUids = usePrevious(envUids);
useEffect(() => { useEffect(() => {
if(selectedEnvironment) { if (selectedEnvironment) {
return; return;
} }
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid); const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
if(environment) { if (environment) {
setSelectedEnvironment(environment); setSelectedEnvironment(environment);
} else { } else {
setSelectedEnvironment(environments && environments.length ? environments[0] : null); setSelectedEnvironment(environments && environments.length ? environments[0] : null);
@ -30,7 +30,7 @@ const EnvironmentList = ({ collection }) => {
// check env add // check env add
if (prevEnvUids && prevEnvUids.length && envUids.length > prevEnvUids.length) { if (prevEnvUids && prevEnvUids.length && envUids.length > prevEnvUids.length) {
const newEnv = environments.find((env) => !prevEnvUids.includes(env.uid)); const newEnv = environments.find((env) => !prevEnvUids.includes(env.uid));
if(newEnv){ if (newEnv) {
setSelectedEnvironment(newEnv); setSelectedEnvironment(newEnv);
} }
} }

View File

@ -16,7 +16,10 @@ const RenameEnvironment = ({ onClose, environment, collection }) => {
name: environment.name name: environment.name
}, },
validationSchema: Yup.object({ validationSchema: Yup.object({
name: Yup.string().min(1, 'must be atleast 1 characters').max(50, 'must be 50 characters or less').required('name is required') name: Yup.string()
.min(1, 'must be atleast 1 characters')
.max(50, 'must be 50 characters or less')
.required('name is required')
}), }),
onSubmit: (values) => { onSubmit: (values) => {
dispatch(renameEnvironment(values.name, environment.uid, collection.uid)) dispatch(renameEnvironment(values.name, environment.uid, collection.uid))
@ -40,7 +43,13 @@ const RenameEnvironment = ({ onClose, environment, collection }) => {
return ( return (
<Portal> <Portal>
<Modal size="sm" title={'Rename Environment'} confirmText="Rename" handleConfirm={onSubmit} handleCancel={onClose}> <Modal
size="sm"
title={'Rename Environment'}
confirmText="Rename"
handleConfirm={onSubmit}
handleCancel={onClose}
>
<form className="bruno-form" onSubmit={formik.handleSubmit}> <form className="bruno-form" onSubmit={formik.handleSubmit}>
<div> <div>
<label htmlFor="name" className="block font-semibold"> <label htmlFor="name" className="block font-semibold">
@ -59,7 +68,9 @@ const RenameEnvironment = ({ onClose, environment, collection }) => {
onChange={formik.handleChange} onChange={formik.handleChange}
value={formik.values.name || ''} value={formik.values.name || ''}
/> />
{formik.touched.name && formik.errors.name ? <div className="text-red-500">{formik.errors.name}</div> : null} {formik.touched.name && formik.errors.name ? (
<div className="text-red-500">{formik.errors.name}</div>
) : null}
</div> </div>
</form> </form>
</Modal> </Modal>

View File

@ -11,11 +11,21 @@ const EnvironmentSettings = ({ collection, onClose }) => {
if (!environments || !environments.length) { if (!environments || !environments.length) {
return ( return (
<StyledWrapper> <StyledWrapper>
<Modal size="md" title="Environments" confirmText={'Close'} handleConfirm={onClose} handleCancel={onClose} hideCancel={true}> <Modal
size="md"
title="Environments"
confirmText={'Close'}
handleConfirm={onClose}
handleCancel={onClose}
hideCancel={true}
>
{openCreateModal && <CreateEnvironment collection={collection} onClose={() => setOpenCreateModal(false)} />} {openCreateModal && <CreateEnvironment collection={collection} onClose={() => setOpenCreateModal(false)} />}
<div className="text-center"> <div className="text-center">
<p>No environments found!</p> <p>No environments found!</p>
<button className="btn-create-environment text-link pr-2 py-3 mt-2 select-none" onClick={() => setOpenCreateModal(true)}> <button
className="btn-create-environment text-link pr-2 py-3 mt-2 select-none"
onClick={() => setOpenCreateModal(true)}
>
+ <span>Create Environment</span> + <span>Create Environment</span>
</button> </button>
</div> </div>

View File

@ -1,17 +1,12 @@
import React from 'react'; import React from 'react';
const SendIcon = ({color, width}) => { const SendIcon = ({ color, width }) => {
return ( return (
<svg <svg xmlns="http://www.w3.org/2000/svg" width={width} height={width} viewBox="0 0 48 48">
xmlns="http://www.w3.org/2000/svg" <path fill={color} d="M4.02 42l41.98-18-41.98-18-.02 14 30 4-30 4z" />
width={width} <path d="M0 0h48v48h-48z" fill="none" />
height={width}
viewBox="0 0 48 48"
>
<path fill={color} d="M4.02 42l41.98-18-41.98-18-.02 14 30 4-30 4z"/>
<path d="M0 0h48v48h-48z" fill="none"/>
</svg> </svg>
); );
} };
export default SendIcon; export default SendIcon;

View File

@ -100,7 +100,7 @@ const Wrapper = styled.div`
border-radius: 0px; border-radius: 0px;
outline: none; outline: none;
box-shadow: none; box-shadow: none;
transition: border-color ease-in-out .1s; transition: border-color ease-in-out 0.1s;
border-radius: 3px; border-radius: 3px;
background-color: ${(props) => props.theme.modal.input.bg}; background-color: ${(props) => props.theme.modal.input.bg};
border: 1px solid ${(props) => props.theme.modal.input.border}; border: 1px solid ${(props) => props.theme.modal.input.border};

View File

@ -14,7 +14,15 @@ const ModalHeader = ({ title, handleCancel }) => (
const ModalContent = ({ children }) => <div className="bruno-modal-content px-4 py-6">{children}</div>; const ModalContent = ({ children }) => <div className="bruno-modal-content px-4 py-6">{children}</div>;
const ModalFooter = ({ confirmText, cancelText, handleSubmit, handleCancel, confirmDisabled, hideCancel, hideFooter }) => { const ModalFooter = ({
confirmText,
cancelText,
handleSubmit,
handleCancel,
confirmDisabled,
hideCancel,
hideFooter
}) => {
confirmText = confirmText || 'Save'; confirmText = confirmText || 'Save';
cancelText = cancelText || 'Cancel'; cancelText = cancelText || 'Cancel';
@ -30,7 +38,12 @@ const ModalFooter = ({ confirmText, cancelText, handleSubmit, handleCancel, conf
</button> </button>
</span> </span>
<span> <span>
<button type="submit" className="submit btn btn-md btn-secondary" disabled={confirmDisabled} onClick={handleSubmit}> <button
type="submit"
className="submit btn btn-md btn-secondary"
disabled={confirmDisabled}
onClick={handleSubmit}
>
{confirmText} {confirmText}
</button> </button>
</span> </span>
@ -38,7 +51,18 @@ const ModalFooter = ({ confirmText, cancelText, handleSubmit, handleCancel, conf
); );
}; };
const Modal = ({ size, title, confirmText, cancelText, handleCancel, handleConfirm, children, confirmDisabled, hideCancel, hideFooter }) => { const Modal = ({
size,
title,
confirmText,
cancelText,
handleCancel,
handleConfirm,
children,
confirmDisabled,
hideCancel,
hideFooter
}) => {
const [isClosing, setIsClosing] = useState(false); const [isClosing, setIsClosing] = useState(false);
const escFunction = (event) => { const escFunction = (event) => {
const escKeyCode = 27; const escKeyCode = 27;
@ -64,7 +88,7 @@ const Modal = ({ size, title, confirmText, cancelText, handleCancel, handleConfi
if (isClosing) { if (isClosing) {
classes += ' modal--animate-out'; classes += ' modal--animate-out';
} }
if(hideFooter) { if (hideFooter) {
classes += ' modal-footer-none'; classes += ' modal-footer-none';
} }
return ( return (

View File

@ -3,22 +3,17 @@ import { usePreferences } from 'providers/Preferences';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const General = () => { const General = () => {
const { const { preferences, setPreferences } = usePreferences();
preferences,
setPreferences,
} = usePreferences();
const [sslVerification, setSslVerification] = useState( const [sslVerification, setSslVerification] = useState(preferences.request.sslVerification);
preferences.request.sslVerification
);
const handleCheckboxChange = () => { const handleCheckboxChange = () => {
const updatedPreferences = { const updatedPreferences = {
...preferences, ...preferences,
request: { request: {
...preferences.request, ...preferences.request,
sslVerification: !sslVerification, sslVerification: !sslVerification
}, }
}; };
setPreferences(updatedPreferences) setPreferences(updatedPreferences)
@ -33,12 +28,7 @@ const General = () => {
return ( return (
<StyledWrapper> <StyledWrapper>
<div className="flex items-center mt-2"> <div className="flex items-center mt-2">
<input <input type="checkbox" checked={sslVerification} onChange={handleCheckboxChange} className="mr-3 mousetrap" />
type="checkbox"
checked={sslVerification}
onChange={handleCheckboxChange}
className="mr-3 mousetrap"
/>
SSL Certificate Verification SSL Certificate Verification
</div> </div>
</StyledWrapper> </StyledWrapper>

View File

@ -22,7 +22,7 @@ const Theme = () => {
return ( return (
<StyledWrapper> <StyledWrapper>
<div className='bruno-form'> <div className="bruno-form">
<div className="flex items-center mt-2"> <div className="flex items-center mt-2">
<input <input
id="light-theme" id="light-theme"
@ -31,7 +31,7 @@ const Theme = () => {
name="theme" name="theme"
onChange={(e) => { onChange={(e) => {
formik.handleChange(e); formik.handleChange(e);
formik.handleSubmit() formik.handleSubmit();
}} }}
value="light" value="light"
checked={formik.values.theme === 'light'} checked={formik.values.theme === 'light'}
@ -47,7 +47,7 @@ const Theme = () => {
name="theme" name="theme"
onChange={(e) => { onChange={(e) => {
formik.handleChange(e); formik.handleChange(e);
formik.handleSubmit() formik.handleSubmit();
}} }}
value="dark" value="dark"
checked={formik.values.theme === 'dark'} checked={formik.values.theme === 'dark'}

View File

@ -33,10 +33,32 @@ import React from 'react';
const AssertionOperator = ({ operator, onChange }) => { const AssertionOperator = ({ operator, onChange }) => {
const operators = [ const operators = [
'eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'in', 'notIn', 'eq',
'contains', 'notContains', 'length', 'matches', 'notMatches', 'neq',
'startsWith', 'endsWith', 'between', 'isEmpty', 'isNull', 'isUndefined', 'gt',
'isDefined', 'isTruthy', 'isFalsy', 'isJson', 'isNumber', 'isString', 'isBoolean' 'gte',
'lt',
'lte',
'in',
'notIn',
'contains',
'notContains',
'length',
'matches',
'notMatches',
'startsWith',
'endsWith',
'between',
'isEmpty',
'isNull',
'isUndefined',
'isDefined',
'isTruthy',
'isFalsy',
'isJson',
'isNumber',
'isString',
'isBoolean'
]; ];
const handleChange = (e) => { const handleChange = (e) => {
@ -44,7 +66,7 @@ const AssertionOperator = ({ operator, onChange }) => {
}; };
const getLabel = (operator) => { const getLabel = (operator) => {
switch(operator) { switch (operator) {
case 'eq': case 'eq':
return 'equals'; return 'equals';
case 'neq': case 'neq':

View File

@ -35,7 +35,7 @@ import { useTheme } from 'providers/Theme';
* isBoolean : is boolean * isBoolean : is boolean
*/ */
const parseAssertionOperator = (str = '') => { const parseAssertionOperator = (str = '') => {
if(!str || typeof str !== 'string' || !str.length) { if (!str || typeof str !== 'string' || !str.length) {
return { return {
operator: 'eq', operator: 'eq',
value: str value: str
@ -43,27 +43,58 @@ const parseAssertionOperator = (str = '') => {
} }
const operators = [ const operators = [
'eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'in', 'notIn', 'eq',
'contains', 'notContains', 'length', 'matches', 'notMatches', 'neq',
'startsWith', 'endsWith', 'between', 'isEmpty', 'isNull', 'isUndefined', 'gt',
'isDefined', 'isTruthy', 'isFalsy', 'isJson', 'isNumber', 'isString', 'isBoolean' 'gte',
'lt',
'lte',
'in',
'notIn',
'contains',
'notContains',
'length',
'matches',
'notMatches',
'startsWith',
'endsWith',
'between',
'isEmpty',
'isNull',
'isUndefined',
'isDefined',
'isTruthy',
'isFalsy',
'isJson',
'isNumber',
'isString',
'isBoolean'
]; ];
const unaryOperators = [ const unaryOperators = [
'isEmpty', 'isNull', 'isUndefined', 'isDefined', 'isTruthy', 'isFalsy', 'isJson', 'isNumber', 'isString', 'isBoolean' 'isEmpty',
'isNull',
'isUndefined',
'isDefined',
'isTruthy',
'isFalsy',
'isJson',
'isNumber',
'isString',
'isBoolean'
]; ];
const [operator, ...rest] = str.trim().split(' '); const [operator, ...rest] = str.trim().split(' ');
const value = rest.join(' '); const value = rest.join(' ');
if(unaryOperators.includes(operator)) { if (unaryOperators.includes(operator)) {
return { return {
operator, operator,
value: '' value: ''
}; };
} }
if(operators.includes(operator)) { if (operators.includes(operator)) {
return { return {
operator, operator,
value value
@ -78,22 +109,33 @@ const parseAssertionOperator = (str = '') => {
const isUnaryOperator = (operator) => { const isUnaryOperator = (operator) => {
const unaryOperators = [ const unaryOperators = [
'isEmpty', 'isNull', 'isUndefined', 'isDefined', 'isTruthy', 'isFalsy', 'isJson', 'isNumber', 'isString', 'isBoolean' 'isEmpty',
'isNull',
'isUndefined',
'isDefined',
'isTruthy',
'isFalsy',
'isJson',
'isNumber',
'isString',
'isBoolean'
]; ];
return unaryOperators.includes(operator); return unaryOperators.includes(operator);
}; };
const AssertionRow = ({ const AssertionRow = ({
item, collection, assertion, handleAssertionChange, handleRemoveAssertion, item,
onSave, handleRun collection,
assertion,
handleAssertionChange,
handleRemoveAssertion,
onSave,
handleRun
}) => { }) => {
const { storedTheme } = useTheme(); const { storedTheme } = useTheme();
const { const { operator, value } = parseAssertionOperator(assertion.value);
operator,
value
} = parseAssertionOperator(assertion.value);
return ( return (
<tr key={assertion.uid}> <tr key={assertion.uid}>
@ -112,11 +154,17 @@ const AssertionRow = ({
<td> <td>
<AssertionOperator <AssertionOperator
operator={operator} operator={operator}
onChange={(op) => handleAssertionChange({ onChange={(op) =>
handleAssertionChange(
{
target: { target: {
value: `${op} ${value}` value: `${op} ${value}`
} }
}, assertion, 'value')} },
assertion,
'value'
)
}
/> />
</td> </td>
<td> <td>
@ -126,20 +174,22 @@ const AssertionRow = ({
theme={storedTheme} theme={storedTheme}
readOnly={true} readOnly={true}
onSave={onSave} onSave={onSave}
onChange={(newValue) => handleAssertionChange({ onChange={(newValue) =>
handleAssertionChange(
{
target: { target: {
value: newValue value: newValue
} }
}, assertion, 'value')} },
assertion,
'value'
)
}
onRun={handleRun} onRun={handleRun}
collection={collection} collection={collection}
/> />
) : ( ) : (
<input <input type="text" className="cursor-default" disabled />
type="text"
className='cursor-default'
disabled
/>
)} )}
</td> </td>
<td> <td>

View File

@ -4,7 +4,11 @@ import cloneDeep from 'lodash/cloneDeep';
import { IconTrash } from '@tabler/icons'; import { IconTrash } from '@tabler/icons';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { useTheme } from 'providers/Theme'; import { useTheme } from 'providers/Theme';
import { addFormUrlEncodedParam, updateFormUrlEncodedParam, deleteFormUrlEncodedParam } from 'providers/ReduxStore/slices/collections'; import {
addFormUrlEncodedParam,
updateFormUrlEncodedParam,
deleteFormUrlEncodedParam
} from 'providers/ReduxStore/slices/collections';
import SingleLineEditor from 'components/SingleLineEditor'; import SingleLineEditor from 'components/SingleLineEditor';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
@ -92,18 +96,29 @@ const FormUrlEncodedParams = ({ item, collection }) => {
value={param.value} value={param.value}
theme={storedTheme} theme={storedTheme}
onSave={onSave} onSave={onSave}
onChange={(newValue) => handleParamChange({ onChange={(newValue) =>
handleParamChange(
{
target: { target: {
value: newValue value: newValue
} }
}, param, 'value')} },
param,
'value'
)
}
onRun={handleRun} onRun={handleRun}
collection={collection} collection={collection}
/> />
</td> </td>
<td> <td>
<div className="flex items-center"> <div className="flex items-center">
<input type="checkbox" checked={param.enabled} className="mr-3 mousetrap" onChange={(e) => handleParamChange(e, param, 'enabled')} /> <input
type="checkbox"
checked={param.enabled}
className="mr-3 mousetrap"
onChange={(e) => handleParamChange(e, param, 'enabled')}
/>
<button onClick={() => handleRemoveParams(param)}> <button onClick={() => handleRemoveParams(param)}>
<IconTrash strokeWidth={1.5} size={20} /> <IconTrash strokeWidth={1.5} size={20} />
</button> </button>

View File

@ -24,29 +24,24 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
const tabs = useSelector((state) => state.tabs.tabs); const tabs = useSelector((state) => state.tabs.tabs);
const activeTabUid = useSelector((state) => state.tabs.activeTabUid); const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
const query = item.draft ? get(item, 'draft.request.body.graphql.query') : get(item, 'request.body.graphql.query'); const query = item.draft ? get(item, 'draft.request.body.graphql.query') : get(item, 'request.body.graphql.query');
const variables = item.draft ? get(item, 'draft.request.body.graphql.variables') : get(item, 'request.body.graphql.variables'); const variables = item.draft
? get(item, 'draft.request.body.graphql.variables')
: get(item, 'request.body.graphql.variables');
const url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url'); const url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
const { const { storedTheme } = useTheme();
storedTheme
} = useTheme();
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid); const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
let { let { schema, loadSchema, isLoading: isSchemaLoading, error: schemaError } = useGraphqlSchema(url, environment);
schema,
loadSchema,
isLoading: isSchemaLoading,
error: schemaError
} = useGraphqlSchema(url, environment);
const loadGqlSchema = () => { const loadGqlSchema = () => {
if(!isSchemaLoading) { if (!isSchemaLoading) {
loadSchema(); loadSchema();
} }
}; };
useEffect(() => { useEffect(() => {
if(onSchemaLoad) { if (onSchemaLoad) {
onSchemaLoad(schema); onSchemaLoad(schema);
} }
}, [schema]); }, [schema]);
@ -75,7 +70,8 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
const getTabPanel = (tab) => { const getTabPanel = (tab) => {
switch (tab) { switch (tab) {
case 'query': { case 'query': {
return <QueryEditor return (
<QueryEditor
collection={collection} collection={collection}
theme={storedTheme} theme={storedTheme}
schema={schema} schema={schema}
@ -85,7 +81,8 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
onRun={onRun} onRun={onRun}
onEdit={onQueryChange} onEdit={onQueryChange}
onClickReference={handleGqlClickReference} onClickReference={handleGqlClickReference}
/>; />
);
} }
case 'variables': { case 'variables': {
return <GraphQLVariables item={item} variables={variables} collection={collection} />; return <GraphQLVariables item={item} variables={variables} collection={collection} />;
@ -150,20 +147,16 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
<div className={getTabClassname('tests')} role="tab" onClick={() => selectTab('tests')}> <div className={getTabClassname('tests')} role="tab" onClick={() => selectTab('tests')}>
Tests Tests
</div> </div>
<div className="flex flex-grow justify-end items-center" style={{fontSize: 13}}> <div className="flex flex-grow justify-end items-center" style={{ fontSize: 13 }}>
<div className='flex items-center cursor-pointer hover:underline' onClick={loadGqlSchema}> <div className="flex items-center cursor-pointer hover:underline" onClick={loadGqlSchema}>
{isSchemaLoading ? ( {isSchemaLoading ? <IconLoader2 className="animate-spin" size={18} strokeWidth={1.5} /> : null}
<IconLoader2 className="animate-spin" size={18} strokeWidth={1.5}/> {!isSchemaLoading && !schema ? <IconDownload size={18} strokeWidth={1.5} /> : null}
) : null} {!isSchemaLoading && schema ? <IconRefresh size={18} strokeWidth={1.5} /> : null}
{!isSchemaLoading && !schema ? <IconDownload size={18} strokeWidth={1.5}/> : null } <span className="ml-1">Schema</span>
{!isSchemaLoading && schema ? <IconRefresh size={18} strokeWidth={1.5}/> : null }
<span className='ml-1'>Schema</span>
</div> </div>
<div <div className="flex items-center cursor-pointer hover:underline ml-2" onClick={toggleDocs}>
className='flex items-center cursor-pointer hover:underline ml-2' <IconBook size={18} strokeWidth={1.5} />
onClick={toggleDocs} <span className="ml-1">Docs</span>
>
<IconBook size={18} strokeWidth={1.5} /><span className='ml-1'>Docs</span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -13,7 +13,7 @@ const useGraphqlSchema = (endpoint, environment) => {
const [schema, setSchema] = useState(() => { const [schema, setSchema] = useState(() => {
try { try {
const saved = localStorage.getItem(localStorageKey); const saved = localStorage.getItem(localStorageKey);
if(!saved) { if (!saved) {
return null; return null;
} }
return buildClientSchema(JSON.parse(saved)); return buildClientSchema(JSON.parse(saved));

View File

@ -9,9 +9,7 @@ import StyledWrapper from './StyledWrapper';
const GraphQLVariables = ({ variables, item, collection }) => { const GraphQLVariables = ({ variables, item, collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { const { storedTheme } = useTheme();
storedTheme
} = useTheme();
const onEdit = (value) => { const onEdit = (value) => {
dispatch( dispatch(
@ -29,10 +27,11 @@ const GraphQLVariables = ({ variables, item, collection }) => {
return ( return (
<StyledWrapper className="w-full"> <StyledWrapper className="w-full">
<CodeEditor <CodeEditor
collection={collection} value={variables || ''} collection={collection}
value={variables || ''}
theme={storedTheme} theme={storedTheme}
onEdit={onEdit} onEdit={onEdit}
mode='javascript' mode="javascript"
onRun={onRun} onRun={onRun}
onSave={onSave} onSave={onSave}
/> />

View File

@ -103,7 +103,9 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
</div> </div>
) : null} ) : null}
</div> </div>
<section className={`flex w-full ${['script', 'vars'].includes(focusedTab.requestPaneTab) ? '' : 'mt-5'}`}>{getTabPanel(focusedTab.requestPaneTab)}</section> <section className={`flex w-full ${['script', 'vars'].includes(focusedTab.requestPaneTab) ? '' : 'mt-5'}`}>
{getTabPanel(focusedTab.requestPaneTab)}
</section>
</StyledWrapper> </StyledWrapper>
); );
}; };

View File

@ -41,7 +41,6 @@ const Wrapper = styled.div`
color: ${(props) => props.theme.table.input.color}; color: ${(props) => props.theme.table.input.color};
background: transparent; background: transparent;
&:focus { &:focus {
outline: none !important; outline: none !important;
border: solid 1px transparent; border: solid 1px transparent;

View File

@ -4,7 +4,11 @@ import cloneDeep from 'lodash/cloneDeep';
import { IconTrash } from '@tabler/icons'; import { IconTrash } from '@tabler/icons';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { useTheme } from 'providers/Theme'; import { useTheme } from 'providers/Theme';
import { addMultipartFormParam, updateMultipartFormParam, deleteMultipartFormParam } from 'providers/ReduxStore/slices/collections'; import {
addMultipartFormParam,
updateMultipartFormParam,
deleteMultipartFormParam
} from 'providers/ReduxStore/slices/collections';
import SingleLineEditor from 'components/SingleLineEditor'; import SingleLineEditor from 'components/SingleLineEditor';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
@ -92,18 +96,29 @@ const MultipartFormParams = ({ item, collection }) => {
onSave={onSave} onSave={onSave}
theme={storedTheme} theme={storedTheme}
value={param.value} value={param.value}
onChange={(newValue) => handleParamChange({ onChange={(newValue) =>
handleParamChange(
{
target: { target: {
value: newValue value: newValue
} }
}, param, 'value')} },
param,
'value'
)
}
onRun={handleRun} onRun={handleRun}
collection={collection} collection={collection}
/> />
</td> </td>
<td> <td>
<div className="flex items-center"> <div className="flex items-center">
<input type="checkbox" checked={param.enabled} className="mr-3 mousetrap" onChange={(e) => handleParamChange(e, param, 'enabled')} /> <input
type="checkbox"
checked={param.enabled}
className="mr-3 mousetrap"
onChange={(e) => handleParamChange(e, param, 'enabled')}
/>
<button onClick={() => handleRemoveParams(param)}> <button onClick={() => handleRemoveParams(param)}>
<IconTrash strokeWidth={1.5} size={20} /> <IconTrash strokeWidth={1.5} size={20} />
</button> </button>

View File

@ -15,16 +15,19 @@ const StyledWrapper = styled.div`
// Todo: dark mode temporary fix // Todo: dark mode temporary fix
// Clean this // Clean this
.CodeMirror.cm-s-monokai { .CodeMirror.cm-s-monokai {
.CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-vertical div { .CodeMirror-overlayscroll-horizontal div,
.CodeMirror-overlayscroll-vertical div {
background: #444444; background: #444444;
} }
} }
.cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute { .cm-s-monokai span.cm-property,
.cm-s-monokai span.cm-attribute {
color: #9cdcfe !important; color: #9cdcfe !important;
} }
.cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute { .cm-s-monokai span.cm-property,
.cm-s-monokai span.cm-attribute {
color: #9cdcfe !important; color: #9cdcfe !important;
} }
@ -32,16 +35,20 @@ const StyledWrapper = styled.div`
color: #ce9178 !important; color: #ce9178 !important;
} }
.cm-s-monokai span.cm-number{ .cm-s-monokai span.cm-number {
color: #b5cea8 !important; color: #b5cea8 !important;
} }
.cm-s-monokai span.cm-atom{ .cm-s-monokai span.cm-atom {
color: #569cd6 !important; color: #569cd6 !important;
} }
.cm-variable-valid{color: green} .cm-variable-valid {
.cm-variable-invalid{color: red} color: green;
}
.cm-variable-invalid {
color: red;
}
`; `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -43,7 +43,7 @@ export default class QueryEditor extends React.Component {
mode: 'graphql', mode: 'graphql',
// mode: 'brunovariables', // mode: 'brunovariables',
brunoVarInfo: { brunoVarInfo: {
variables: getAllVariables(this.props.collection), variables: getAllVariables(this.props.collection)
}, },
theme: this.props.editorTheme || 'graphiql', theme: this.props.editorTheme || 'graphiql',
theme: this.props.theme === 'dark' ? 'monokai' : 'default', theme: this.props.theme === 'dark' ? 'monokai' : 'default',
@ -51,7 +51,7 @@ export default class QueryEditor extends React.Component {
autoCloseBrackets: true, autoCloseBrackets: true,
matchBrackets: true, matchBrackets: true,
showCursorWhenSelecting: true, showCursorWhenSelecting: true,
scrollbarStyle: "overlay", scrollbarStyle: 'overlay',
readOnly: this.props.readOnly ? 'nocursor' : false, readOnly: this.props.readOnly ? 'nocursor' : false,
foldGutter: { foldGutter: {
minFoldSize: 4 minFoldSize: 4
@ -183,10 +183,9 @@ export default class QueryEditor extends React.Component {
addOverlay = () => { addOverlay = () => {
// let variables = getAllVariables(this.props.collection); // let variables = getAllVariables(this.props.collection);
// this.variables = variables; // this.variables = variables;
// defineCodeMirrorBrunoVariablesMode(variables, 'graphql'); // defineCodeMirrorBrunoVariablesMode(variables, 'graphql');
// this.editor.setOption('mode', 'brunovariables'); // this.editor.setOption('mode', 'brunovariables');
} };
render() { render() {
return ( return (

View File

@ -59,7 +59,10 @@ export default function onHasCompletion(_cm, data, onHintInformationRender) {
const description = ctx.description ? md.render(ctx.description) : 'Self descriptive.'; const description = ctx.description ? md.render(ctx.description) : 'Self descriptive.';
const type = ctx.type ? '<span className="infoType">' + renderType(ctx.type) + '</span>' : ''; const type = ctx.type ? '<span className="infoType">' + renderType(ctx.type) + '</span>' : '';
information.innerHTML = '<div className="content">' + (description.slice(0, 3) === '<p>' ? '<p>' + type + description.slice(3) : type + description) + '</div>'; information.innerHTML =
'<div className="content">' +
(description.slice(0, 3) === '<p>' ? '<p>' + type + description.slice(3) : type + description) +
'</div>';
if (ctx && deprecation && ctx.deprecationReason) { if (ctx && deprecation && ctx.deprecationReason) {
const reason = ctx.deprecationReason ? md.render(ctx.deprecationReason) : ''; const reason = ctx.deprecationReason ? md.render(ctx.deprecationReason) : '';

View File

@ -13,7 +13,7 @@ const Wrapper = styled.div`
} }
thead { thead {
color: ${(props) => props.theme.table.thead.color};; color: ${(props) => props.theme.table.thead.color};
font-size: 0.8125rem; font-size: 0.8125rem;
user-select: none; user-select: none;
} }

View File

@ -95,18 +95,29 @@ const QueryParams = ({ item, collection }) => {
value={param.value} value={param.value}
theme={storedTheme} theme={storedTheme}
onSave={onSave} onSave={onSave}
onChange={(newValue) => handleParamChange({ onChange={(newValue) =>
handleParamChange(
{
target: { target: {
value: newValue value: newValue
} }
}, param, 'value')} },
param,
'value'
)
}
onRun={handleRun} onRun={handleRun}
collection={collection} collection={collection}
/> />
</td> </td>
<td> <td>
<div className="flex items-center"> <div className="flex items-center">
<input type="checkbox" checked={param.enabled} className="mr-3 mousetrap" onChange={(e) => handleParamChange(e, param, 'enabled')} /> <input
type="checkbox"
checked={param.enabled}
className="mr-3 mousetrap"
onChange={(e) => handleParamChange(e, param, 'enabled')}
/>
<button onClick={() => handleRemoveParam(param)}> <button onClick={() => handleRemoveParam(param)}>
<IconTrash strokeWidth={1.5} size={20} /> <IconTrash strokeWidth={1.5} size={20} />
</button> </button>

View File

@ -10,7 +10,9 @@ const HttpMethodSelector = ({ method, onMethodSelect }) => {
const Icon = forwardRef((props, ref) => { const Icon = forwardRef((props, ref) => {
return ( return (
<div ref={ref} className="flex w-full items-center pl-3 py-1 select-none uppercase"> <div ref={ref} className="flex w-full items-center pl-3 py-1 select-none uppercase">
<div className="flex-grow font-medium" id="create-new-request-method">{method}</div> <div className="flex-grow font-medium" id="create-new-request-method">
{method}
</div>
<div> <div>
<IconCaretDown className="caret ml-2 mr-2" size={14} strokeWidth={2} /> <IconCaretDown className="caret ml-2 mr-2" size={14} strokeWidth={2} />
</div> </div>

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect} from 'react'; import React, { useState, useEffect } from 'react';
import get from 'lodash/get'; import get from 'lodash/get';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { requestUrlChanged, updateRequestMethod } from 'providers/ReduxStore/slices/collections'; import { requestUrlChanged, updateRequestMethod } from 'providers/ReduxStore/slices/collections';
@ -18,7 +18,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
const [methodSelectorWidth, setMethodSelectorWidth] = useState(90); const [methodSelectorWidth, setMethodSelectorWidth] = useState(90);
useEffect(() => { useEffect(() => {
const el = document.querySelector(".method-selector-container"); const el = document.querySelector('.method-selector-container');
setMethodSelectorWidth(el.offsetWidth); setMethodSelectorWidth(el.offsetWidth);
}, [method]); }, [method]);
@ -65,7 +65,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
collection={collection} collection={collection}
/> />
<div className="flex items-center h-full mr-2 cursor-pointer" id="send-request" onClick={handleRun}> <div className="flex items-center h-full mr-2 cursor-pointer" id="send-request" onClick={handleRun}>
<SendIcon color={theme.requestTabPanel.url.icon} width={22}/> <SendIcon color={theme.requestTabPanel.url.icon} width={22} />
</div> </div>
</div> </div>
</StyledWrapper> </StyledWrapper>

View File

@ -13,9 +13,7 @@ const RequestBody = ({ item, collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const body = item.draft ? get(item, 'draft.request.body') : get(item, 'request.body'); const body = item.draft ? get(item, 'draft.request.body') : get(item, 'request.body');
const bodyMode = item.draft ? get(item, 'draft.request.body.mode') : get(item, 'request.body.mode'); const bodyMode = item.draft ? get(item, 'draft.request.body.mode') : get(item, 'request.body.mode');
const { const { storedTheme } = useTheme();
storedTheme
} = useTheme();
const onEdit = (value) => { const onEdit = (value) => {
dispatch( dispatch(
@ -45,7 +43,15 @@ const RequestBody = ({ item, collection }) => {
return ( return (
<StyledWrapper className="w-full"> <StyledWrapper className="w-full">
<CodeEditor collection={collection} theme={storedTheme} value={bodyContent[bodyMode] || ''} onEdit={onEdit} onRun={onRun} onSave={onSave} mode={codeMirrorMode[bodyMode]} /> <CodeEditor
collection={collection}
theme={storedTheme}
value={bodyContent[bodyMode] || ''}
onEdit={onEdit}
onRun={onRun}
onSave={onSave}
mode={codeMirrorMode[bodyMode]}
/>
</StyledWrapper> </StyledWrapper>
); );
} }

View File

@ -92,18 +92,29 @@ const RequestHeaders = ({ item, collection }) => {
value={header.value} value={header.value}
theme={storedTheme} theme={storedTheme}
onSave={onSave} onSave={onSave}
onChange={(newValue) => handleHeaderValueChange({ onChange={(newValue) =>
handleHeaderValueChange(
{
target: { target: {
value: newValue value: newValue
} }
}, header, 'value')} },
header,
'value'
)
}
onRun={handleRun} onRun={handleRun}
collection={collection} collection={collection}
/> />
</td> </td>
<td> <td>
<div className="flex items-center"> <div className="flex items-center">
<input type="checkbox" checked={header.enabled} className="mr-3 mousetrap" onChange={(e) => handleHeaderValueChange(e, header, 'enabled')} /> <input
type="checkbox"
checked={header.enabled}
className="mr-3 mousetrap"
onChange={(e) => handleHeaderValueChange(e, header, 'enabled')}
/>
<button onClick={() => handleRemoveHeader(header)}> <button onClick={() => handleRemoveHeader(header)}>
<IconTrash strokeWidth={1.5} size={20} /> <IconTrash strokeWidth={1.5} size={20} />
</button> </button>

View File

@ -28,7 +28,14 @@ const SaveRequest = ({ items, onClose }) => {
return ( return (
<StyledWrapper> <StyledWrapper>
<Modal size="md" title="Save Request" confirmText="Save" cancelText="Cancel" handleCancel={onClose} handleConfirm={onClose}> <Modal
size="md"
title="Save Request"
confirmText="Save"
cancelText="Cancel"
handleCancel={onClose}
handleConfirm={onClose}
>
<p className="mb-2">Select a folder to save request:</p> <p className="mb-2">Select a folder to save request:</p>
<div className="folder-list"> <div className="folder-list">
{showFolders && showFolders.length {showFolders && showFolders.length

View File

@ -12,9 +12,7 @@ const Script = ({ item, collection }) => {
const requestScript = item.draft ? get(item, 'draft.request.script.req') : get(item, 'request.script.req'); const requestScript = item.draft ? get(item, 'draft.request.script.req') : get(item, 'request.script.req');
const responseScript = item.draft ? get(item, 'draft.request.script.res') : get(item, 'request.script.res'); const responseScript = item.draft ? get(item, 'draft.request.script.res') : get(item, 'request.script.res');
const { const { storedTheme } = useTheme();
storedTheme
} = useTheme();
const onRequestScriptEdit = (value) => { const onRequestScriptEdit = (value) => {
dispatch( dispatch(
@ -34,31 +32,33 @@ const Script = ({ item, collection }) => {
collectionUid: collection.uid collectionUid: collection.uid
}) })
); );
}; };
const onRun = () => dispatch(sendRequest(item, collection.uid)); const onRun = () => dispatch(sendRequest(item, collection.uid));
const onSave = () => dispatch(saveRequest(item.uid, collection.uid)); const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
return ( return (
<StyledWrapper className="w-full flex flex-col"> <StyledWrapper className="w-full flex flex-col">
<div className='flex-1 mt-2'> <div className="flex-1 mt-2">
<div className='mb-1 title text-xs'>Pre Request</div> <div className="mb-1 title text-xs">Pre Request</div>
<CodeEditor <CodeEditor
collection={collection} value={requestScript || ''} collection={collection}
value={requestScript || ''}
theme={storedTheme} theme={storedTheme}
onEdit={onRequestScriptEdit} onEdit={onRequestScriptEdit}
mode='javascript' mode="javascript"
onRun={onRun} onRun={onRun}
onSave={onSave} onSave={onSave}
/> />
</div> </div>
<div className='flex-1 mt-6'> <div className="flex-1 mt-6">
<div className='mt-1 mb-1 title text-xs'>Post Response</div> <div className="mt-1 mb-1 title text-xs">Post Response</div>
<CodeEditor <CodeEditor
collection={collection} value={responseScript || ''} collection={collection}
value={responseScript || ''}
theme={storedTheme} theme={storedTheme}
onEdit={onResponseScriptEdit} onEdit={onResponseScriptEdit}
mode='javascript' mode="javascript"
onRun={onRun} onRun={onRun}
onSave={onSave} onSave={onSave}
/> />

View File

@ -11,9 +11,7 @@ const Tests = ({ item, collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const tests = item.draft ? get(item, 'draft.request.tests') : get(item, 'request.tests'); const tests = item.draft ? get(item, 'draft.request.tests') : get(item, 'request.tests');
const { const { storedTheme } = useTheme();
storedTheme
} = useTheme();
const onEdit = (value) => { const onEdit = (value) => {
dispatch( dispatch(
@ -31,10 +29,11 @@ const Tests = ({ item, collection }) => {
return ( return (
<StyledWrapper className="w-full"> <StyledWrapper className="w-full">
<CodeEditor <CodeEditor
collection={collection} value={tests || ''} collection={collection}
value={tests || ''}
theme={storedTheme} theme={storedTheme}
onEdit={onEdit} onEdit={onEdit}
mode='javascript' mode="javascript"
onRun={onRun} onRun={onRun}
onSave={onSave} onSave={onSave}
/> />

View File

@ -68,18 +68,18 @@ const VarsTable = ({ item, collection, vars, varType }) => {
<thead> <thead>
<tr> <tr>
<td>Name</td> <td>Name</td>
{ varType === 'request' ? ( {varType === 'request' ? (
<td> <td>
<div className='flex items-center'> <div className="flex items-center">
<span>Value</span> <span>Value</span>
<Tooltip text="You can write any valid JS Template Literal here" tooltipId="request-var"/> <Tooltip text="You can write any valid JS Template Literal here" tooltipId="request-var" />
</div> </div>
</td> </td>
) : ( ) : (
<td> <td>
<div className='flex items-center'> <div className="flex items-center">
<span>Expr</span> <span>Expr</span>
<Tooltip text="You can write any valid JS expression here" tooltipId="response-var"/> <Tooltip text="You can write any valid JS expression here" tooltipId="response-var" />
</div> </div>
</td> </td>
)} )}
@ -108,11 +108,17 @@ const VarsTable = ({ item, collection, vars, varType }) => {
value={_var.value} value={_var.value}
theme={storedTheme} theme={storedTheme}
onSave={onSave} onSave={onSave}
onChange={(newValue) => handleVarChange({ onChange={(newValue) =>
handleVarChange(
{
target: { target: {
value: newValue value: newValue
} }
}, _var, 'value')} },
_var,
'value'
)
}
onRun={handleRun} onRun={handleRun}
collection={collection} collection={collection}
/> />

View File

@ -12,9 +12,7 @@ const Vars = ({ item, collection }) => {
const requestVars = item.draft ? get(item, 'draft.request.vars.req') : get(item, 'request.vars.req'); const requestVars = item.draft ? get(item, 'draft.request.vars.req') : get(item, 'request.vars.req');
const responseVars = item.draft ? get(item, 'draft.request.vars.res') : get(item, 'request.vars.res'); const responseVars = item.draft ? get(item, 'draft.request.vars.res') : get(item, 'request.vars.res');
const { const { storedTheme } = useTheme();
storedTheme
} = useTheme();
const onRequestScriptEdit = (value) => { const onRequestScriptEdit = (value) => {
dispatch( dispatch(
@ -34,20 +32,20 @@ const Vars = ({ item, collection }) => {
collectionUid: collection.uid collectionUid: collection.uid
}) })
); );
}; };
const onRun = () => dispatch(sendRequest(item, collection.uid)); const onRun = () => dispatch(sendRequest(item, collection.uid));
const onSave = () => dispatch(saveRequest(item.uid, collection.uid)); const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
return ( return (
<StyledWrapper className="w-full flex flex-col"> <StyledWrapper className="w-full flex flex-col">
<div className='flex-1 mt-2'> <div className="flex-1 mt-2">
<div className='mb-1 title text-xs'>Pre Request</div> <div className="mb-1 title text-xs">Pre Request</div>
<VarsTable item={item} collection={collection} vars={requestVars} varType='request'/> <VarsTable item={item} collection={collection} vars={requestVars} varType="request" />
</div> </div>
<div className='flex-1'> <div className="flex-1">
<div className='mt-1 mb-1 title text-xs'>Post Response</div> <div className="mt-1 mb-1 title text-xs">Post Response</div>
<VarsTable item={item} collection={collection} vars={responseVars} varType='response'/> <VarsTable item={item} collection={collection} vars={responseVars} varType="response" />
</div> </div>
</StyledWrapper> </StyledWrapper>
); );

View File

@ -24,7 +24,7 @@ const RequestNotFound = ({ itemUid }) => {
// and then shows the error message after a delay // and then shows the error message after a delay
// this will prevent the error message from flashing on the screen // this will prevent the error message from flashing on the screen
if(!showErrorMessage) { if (!showErrorMessage) {
return null; return null;
} }
@ -32,7 +32,9 @@ const RequestNotFound = ({ itemUid }) => {
<div className="mt-6 px-6"> <div className="mt-6 px-6">
<div className="p-4 bg-orange-100 border-l-4 border-yellow-500 text-yellow-700 bg-yellow-100 p-4"> <div className="p-4 bg-orange-100 border-l-4 border-yellow-500 text-yellow-700 bg-yellow-100 p-4">
<div>Request no longer exists.</div> <div>Request no longer exists.</div>
<div className="mt-2">This can happen when the .bru file associated with this request was deleted on your filesystem.</div> <div className="mt-2">
This can happen when the .bru file associated with this request was deleted on your filesystem.
</div>
</div> </div>
<button className="btn btn-md btn-secondary mt-6" onClick={closeTab}> <button className="btn btn-md btn-secondary mt-6" onClick={closeTab}>
Close Tab Close Tab

View File

@ -33,7 +33,9 @@ const RequestTabPanel = () => {
let asideWidth = useSelector((state) => state.app.leftSidebarWidth); let asideWidth = useSelector((state) => state.app.leftSidebarWidth);
const focusedTab = find(tabs, (t) => t.uid === activeTabUid); const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
const [leftPaneWidth, setLeftPaneWidth] = useState(focusedTab && focusedTab.requestPaneWidth ? focusedTab.requestPaneWidth : (screenWidth - asideWidth) / 2.2); // 2.2 so that request pane is relatively smaller const [leftPaneWidth, setLeftPaneWidth] = useState(
focusedTab && focusedTab.requestPaneWidth ? focusedTab.requestPaneWidth : (screenWidth - asideWidth) / 2.2
); // 2.2 so that request pane is relatively smaller
const [rightPaneWidth, setRightPaneWidth] = useState(screenWidth - asideWidth - leftPaneWidth - DEFAULT_PADDING); const [rightPaneWidth, setRightPaneWidth] = useState(screenWidth - asideWidth - leftPaneWidth - DEFAULT_PADDING);
const [dragging, setDragging] = useState(false); const [dragging, setDragging] = useState(false);
@ -45,10 +47,10 @@ const RequestTabPanel = () => {
const onSchemaLoad = (schema) => setSchema(schema); const onSchemaLoad = (schema) => setSchema(schema);
const toggleDocs = () => setShowGqlDocs((showGqlDocs) => !showGqlDocs); const toggleDocs = () => setShowGqlDocs((showGqlDocs) => !showGqlDocs);
const handleGqlClickReference = (reference) => { const handleGqlClickReference = (reference) => {
if(docExplorerRef.current) { if (docExplorerRef.current) {
docExplorerRef.current.showDocForReference(reference); docExplorerRef.current.showDocForReference(reference);
} }
if(!showGqlDocs) { if (!showGqlDocs) {
setShowGqlDocs(true); setShowGqlDocs(true);
} }
}; };
@ -66,10 +68,13 @@ const RequestTabPanel = () => {
if (dragging) { if (dragging) {
e.preventDefault(); e.preventDefault();
let leftPaneXPosition = e.clientX + 2; let leftPaneXPosition = e.clientX + 2;
if (leftPaneXPosition < (asideWidth+ DEFAULT_PADDING + MIN_LEFT_PANE_WIDTH) || leftPaneXPosition > (screenWidth - MIN_RIGHT_PANE_WIDTH )) { if (
leftPaneXPosition < asideWidth + DEFAULT_PADDING + MIN_LEFT_PANE_WIDTH ||
leftPaneXPosition > screenWidth - MIN_RIGHT_PANE_WIDTH
) {
return; return;
} }
setLeftPaneWidth(leftPaneXPosition- asideWidth); setLeftPaneWidth(leftPaneXPosition - asideWidth);
setRightPaneWidth(screenWidth - e.clientX - DEFAULT_PADDING); setRightPaneWidth(screenWidth - e.clientX - DEFAULT_PADDING);
} }
}; };
@ -114,8 +119,8 @@ const RequestTabPanel = () => {
} }
const showRunner = collection.showRunner; const showRunner = collection.showRunner;
if(showRunner) { if (showRunner) {
return <RunnerResults collection={collection}/>; return <RunnerResults collection={collection} />;
} }
const item = findItemInCollection(collection, activeTabUid); const item = findItemInCollection(collection, activeTabUid);
@ -138,7 +143,13 @@ const RequestTabPanel = () => {
</div> </div>
<section className="main flex flex-grow pb-4 relative"> <section className="main flex flex-grow pb-4 relative">
<section className="request-pane"> <section className="request-pane">
<div className="px-4" style={{ width: `${Math.max(leftPaneWidth, MIN_LEFT_PANE_WIDTH)}px`, height: `calc(100% - ${DEFAULT_PADDING}px)` }}> <div
className="px-4"
style={{
width: `${Math.max(leftPaneWidth, MIN_LEFT_PANE_WIDTH)}px`,
height: `calc(100% - ${DEFAULT_PADDING}px)`
}}
>
{item.type === 'graphql-request' ? ( {item.type === 'graphql-request' ? (
<GraphQLRequestPane <GraphQLRequestPane
item={item} item={item}
@ -150,7 +161,9 @@ const RequestTabPanel = () => {
/> />
) : null} ) : null}
{item.type === 'http-request' ? <HttpRequestPane item={item} collection={collection} leftPaneWidth={leftPaneWidth} /> : null} {item.type === 'http-request' ? (
<HttpRequestPane item={item} collection={collection} leftPaneWidth={leftPaneWidth} />
) : null}
</div> </div>
</section> </section>
@ -165,17 +178,13 @@ const RequestTabPanel = () => {
{item.type === 'graphql-request' ? ( {item.type === 'graphql-request' ? (
<div className={`graphql-docs-explorer-container ${showGqlDocs ? '' : 'hidden'}`}> <div className={`graphql-docs-explorer-container ${showGqlDocs ? '' : 'hidden'}`}>
<DocExplorer schema={schema} ref={(r) => docExplorerRef.current = r}> <DocExplorer schema={schema} ref={(r) => (docExplorerRef.current = r)}>
<button <button className="mr-2" onClick={toggleDocs} aria-label="Close Documentation Explorer">
className='mr-2'
onClick={toggleDocs}
aria-label="Close Documentation Explorer"
>
{'\u2715'} {'\u2715'}
</button> </button>
</DocExplorer> </DocExplorer>
</div> </div>
): null} ) : null}
</StyledWrapper> </StyledWrapper>
); );
}; };

View File

@ -10,9 +10,11 @@ const CollectionToolBar = ({ collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const handleRun = () => { const handleRun = () => {
dispatch(toggleRunnerView({ dispatch(
toggleRunnerView({
collectionUid: collection.uid collectionUid: collection.uid
})); })
);
}; };
return ( return (
@ -26,7 +28,7 @@ const CollectionToolBar = ({ collection }) => {
<span className="mr-2"> <span className="mr-2">
<IconRun className="cursor-pointer" size={20} strokeWidth={1.5} onClick={handleRun} /> <IconRun className="cursor-pointer" size={20} strokeWidth={1.5} onClick={handleRun} />
</span> </span>
<VariablesView collection={collection}/> <VariablesView collection={collection} />
<EnvironmentSelector collection={collection} /> <EnvironmentSelector collection={collection} />
</div> </div>
</div> </div>

View File

@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { IconAlertTriangle } from '@tabler/icons'; import { IconAlertTriangle } from '@tabler/icons';
const RequestTabNotFound = ({handleCloseClick}) => { const RequestTabNotFound = ({ handleCloseClick }) => {
const [showErrorMessage, setShowErrorMessage] = useState(false); const [showErrorMessage, setShowErrorMessage] = useState(false);
// add a delay component in react that shows a loading spinner // add a delay component in react that shows a loading spinner
@ -13,7 +13,7 @@ const RequestTabNotFound = ({handleCloseClick}) => {
}, 300); }, 300);
}, []); }, []);
if(!showErrorMessage) { if (!showErrorMessage) {
return null; return null;
} }

View File

@ -87,7 +87,15 @@ const RequestTab = ({ tab, collection }) => {
></path> ></path>
</svg> </svg>
) : ( ) : (
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" width="8" height="16" fill="#cc7b1b" className="has-changes-icon" viewBox="0 0 8 8"> <svg
focusable="false"
xmlns="http://www.w3.org/2000/svg"
width="8"
height="16"
fill="#cc7b1b"
className="has-changes-icon"
viewBox="0 0 8 8"
>
<circle cx="4" cy="4" r="3" /> <circle cx="4" cy="4" r="3" />
</svg> </svg>
)} )}

View File

@ -81,7 +81,9 @@ const RequestTabs = () => {
// Todo: Must support ephermal requests // Todo: Must support ephermal requests
return ( return (
<StyledWrapper className={getRootClassname()}> <StyledWrapper className={getRootClassname()}>
{newRequestModalOpen && <NewRequest collection={activeCollection} onClose={() => setNewRequestModalOpen(false)} />} {newRequestModalOpen && (
<NewRequest collection={activeCollection} onClose={() => setNewRequestModalOpen(false)} />
)}
{collectionRequestTabs && collectionRequestTabs.length ? ( {collectionRequestTabs && collectionRequestTabs.length ? (
<> <>
<CollectionToolBar collection={activeCollection} /> <CollectionToolBar collection={activeCollection} />
@ -106,7 +108,12 @@ const RequestTabs = () => {
{collectionRequestTabs && collectionRequestTabs.length {collectionRequestTabs && collectionRequestTabs.length
? collectionRequestTabs.map((tab, index) => { ? collectionRequestTabs.map((tab, index) => {
return ( return (
<li key={tab.uid} className={getTabClassname(tab, index)} role="tab" onClick={() => handleClick(tab)}> <li
key={tab.uid}
className={getTabClassname(tab, index)}
role="tab"
onClick={() => handleClick(tab)}
>
<RequestTab key={tab.uid} tab={tab} collection={activeCollection} activeTab={activeTab} /> <RequestTab key={tab.uid} tab={tab} collection={activeCollection} activeTab={activeTab} />
</li> </li>
); );
@ -124,7 +131,13 @@ const RequestTabs = () => {
) : null} ) : null}
<li className="select-none short-tab" id="create-new-tab" onClick={createNewTab}> <li className="select-none short-tab" id="create-new-tab" onClick={createNewTab}>
<div className="flex items-center"> <div className="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="currentColor" viewBox="0 0 16 16"> <svg
xmlns="http://www.w3.org/2000/svg"
width="22"
height="22"
fill="currentColor"
viewBox="0 0 16 16"
>
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z" /> <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z" />
</svg> </svg>
</div> </div>

View File

@ -20,7 +20,14 @@ const QueryResult = ({ item, collection, value, width, disableRunEventListener,
return ( return (
<StyledWrapper className="px-3 w-full" style={{ maxWidth: width }}> <StyledWrapper className="px-3 w-full" style={{ maxWidth: width }}>
<div className="h-full"> <div className="h-full">
<CodeEditor collection={collection} theme={storedTheme} onRun={onRun} value={value || ''} mode={mode} readOnly /> <CodeEditor
collection={collection}
theme={storedTheme}
onRun={onRun}
value={value || ''}
mode={mode}
readOnly
/>
</div> </div>
</StyledWrapper> </StyledWrapper>
); );

View File

@ -5,11 +5,7 @@ const TestResults = ({ results, assertionResults }) => {
results = results || []; results = results || [];
assertionResults = assertionResults || []; assertionResults = assertionResults || [];
if (!results.length && !assertionResults.length) { if (!results.length && !assertionResults.length) {
return ( return <div className="px-3">No tests found</div>;
<div className="px-3">
No tests found
</div>
);
} }
const passedTests = results.filter((result) => result.status === 'pass'); const passedTests = results.filter((result) => result.status === 'pass');
@ -19,7 +15,7 @@ const TestResults = ({ results, assertionResults }) => {
const failedAssertions = assertionResults.filter((result) => result.status === 'fail'); const failedAssertions = assertionResults.filter((result) => result.status === 'fail');
return ( return (
<StyledWrapper className='flex flex-col px-3'> <StyledWrapper className="flex flex-col px-3">
<div className="py-2 font-medium test-summary"> <div className="py-2 font-medium test-summary">
Tests ({results.length}/{results.length}), Passed: {passedTests.length}, Failed: {failedTests.length} Tests ({results.length}/{results.length}), Passed: {passedTests.length}, Failed: {failedTests.length}
</div> </div>
@ -27,18 +23,12 @@ const TestResults = ({ results, assertionResults }) => {
{results.map((result) => ( {results.map((result) => (
<li key={result.uid} className="py-1"> <li key={result.uid} className="py-1">
{result.status === 'pass' ? ( {result.status === 'pass' ? (
<span className="test-success"> <span className="test-success">&#x2714;&nbsp; {result.description}</span>
&#x2714;&nbsp; {result.description}
</span>
) : ( ) : (
<> <>
<span className="test-failure"> <span className="test-failure">&#x2718;&nbsp; {result.description}</span>
&#x2718;&nbsp; {result.description}
</span>
<br /> <br />
<span className="error-message pl-8"> <span className="error-message pl-8">{result.error}</span>
{result.error}
</span>
</> </>
)} )}
</li> </li>
@ -46,7 +36,8 @@ const TestResults = ({ results, assertionResults }) => {
</ul> </ul>
<div className="py-2 font-medium test-summary"> <div className="py-2 font-medium test-summary">
Assertions ({assertionResults.length}/{assertionResults.length}), Passed: {passedAssertions.length}, Failed: {failedAssertions.length} Assertions ({assertionResults.length}/{assertionResults.length}), Passed: {passedAssertions.length}, Failed:{' '}
{failedAssertions.length}
</div> </div>
<ul className=""> <ul className="">
{assertionResults.map((result) => ( {assertionResults.map((result) => (
@ -61,9 +52,7 @@ const TestResults = ({ results, assertionResults }) => {
&#x2718;&nbsp; {result.lhsExpr}: {result.rhsExpr} &#x2718;&nbsp; {result.lhsExpr}: {result.rhsExpr}
</span> </span>
<br /> <br />
<span className="error-message pl-8"> <span className="error-message pl-8">{result.error}</span>
{result.error}
</span>
</> </>
)} )}
</li> </li>

View File

@ -3,30 +3,26 @@ import React from 'react';
const TestResultsLabel = ({ results, assertionResults }) => { const TestResultsLabel = ({ results, assertionResults }) => {
results = results || []; results = results || [];
assertionResults = assertionResults || []; assertionResults = assertionResults || [];
if(!results.length && !assertionResults.length) { if (!results.length && !assertionResults.length) {
return 'Tests'; return 'Tests';
} }
const numberOfTests = results.length; const numberOfTests = results.length;
const numberOfFailedTests = results.filter(result => result.status === 'fail').length; const numberOfFailedTests = results.filter((result) => result.status === 'fail').length;
const numberOfAssertions = assertionResults.length; const numberOfAssertions = assertionResults.length;
const numberOfFailedAssertions = assertionResults.filter(result => result.status === 'fail').length; const numberOfFailedAssertions = assertionResults.filter((result) => result.status === 'fail').length;
const totalNumberOfTests = numberOfTests + numberOfAssertions; const totalNumberOfTests = numberOfTests + numberOfAssertions;
const totalNumberOfFailedTests = numberOfFailedTests + numberOfFailedAssertions; const totalNumberOfFailedTests = numberOfFailedTests + numberOfFailedAssertions;
return ( return (
<div className='flex items-center'> <div className="flex items-center">
<div>Tests</div> <div>Tests</div>
{totalNumberOfFailedTests ? ( {totalNumberOfFailedTests ? (
<sup className='sups some-tests-failed ml-1 font-medium'> <sup className="sups some-tests-failed ml-1 font-medium">{totalNumberOfFailedTests}</sup>
{totalNumberOfFailedTests}
</sup>
) : ( ) : (
<sup className='sups all-tests-passed ml-1 font-medium'> <sup className="sups all-tests-passed ml-1 font-medium">{totalNumberOfTests}</sup>
{totalNumberOfTests}
</sup>
)} )}
</div> </div>
); );

View File

@ -3,7 +3,7 @@ import forOwn from 'lodash/forOwn';
import { safeStringifyJSON } from 'utils/common'; import { safeStringifyJSON } from 'utils/common';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const Timeline = ({ request, response}) => { const Timeline = ({ request, response }) => {
const requestHeaders = []; const requestHeaders = [];
const responseHeaders = response.headers || []; const responseHeaders = response.headers || [];
@ -22,32 +22,32 @@ const Timeline = ({ request, response}) => {
return ( return (
<StyledWrapper className="px-3 pb-4 w-full"> <StyledWrapper className="px-3 pb-4 w-full">
<div> <div>
<pre className='line request font-bold'> <pre className="line request font-bold">
<span className="arrow">{'>'}</span> {request.method} {request.url} <span className="arrow">{'>'}</span> {request.method} {request.url}
</pre> </pre>
{requestHeaders.map((h) => { {requestHeaders.map((h) => {
return ( return (
<pre className='line request' key={h.name}> <pre className="line request" key={h.name}>
<span className="arrow">{'>'}</span> {h.name}: {h.value} <span className="arrow">{'>'}</span> {h.name}: {h.value}
</pre> </pre>
); );
})} })}
{requestData ? ( {requestData ? (
<pre className='line request'> <pre className="line request">
<span className="arrow">{'>'}</span> data {requestData} <span className="arrow">{'>'}</span> data {requestData}
</pre> </pre>
) : null} ) : null}
</div> </div>
<div className='mt-4'> <div className="mt-4">
<pre className='line response font-bold'> <pre className="line response font-bold">
<span className="arrow">{'<'}</span> {response.status} {response.statusText} <span className="arrow">{'<'}</span> {response.status} {response.statusText}
</pre> </pre>
{responseHeaders.map((h) => { {responseHeaders.map((h) => {
return ( return (
<pre className='line response' key={h[0]}> <pre className="line response" key={h[0]}>
<span className="arrow">{'<'}</span> {h[0]}: {h[1]} <span className="arrow">{'<'}</span> {h[0]}: {h[1]}
</pre> </pre>
); );

View File

@ -41,7 +41,9 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
item={item} item={item}
collection={collection} collection={collection}
width={rightPaneWidth} width={rightPaneWidth}
value={response.data ? (isJson(response.headers) ? safeStringifyJSON(response.data, true) : response.data) : ''} value={
response.data ? (isJson(response.headers) ? safeStringifyJSON(response.data, true) : response.data) : ''
}
mode={getContentType(response.headers)} mode={getContentType(response.headers)}
/> />
); );

View File

@ -15,11 +15,7 @@ import StyledWrapper from './StyledWrapper';
const ResponsePane = ({ rightPaneWidth, item, collection }) => { const ResponsePane = ({ rightPaneWidth, item, collection }) => {
const [selectedTab, setSelectedTab] = useState('response'); const [selectedTab, setSelectedTab] = useState('response');
const { const { requestSent, responseReceived, testResults } = item;
requestSent,
responseReceived,
testResults
} = item;
const headers = get(item, 'responseReceived.headers', {}); const headers = get(item, 'responseReceived.headers', {});
const status = get(item, 'responseReceived.status', 0); const status = get(item, 'responseReceived.status', 0);
@ -31,13 +27,15 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
const getTabPanel = (tab) => { const getTabPanel = (tab) => {
switch (tab) { switch (tab) {
case 'response': { case 'response': {
return <QueryResult return (
<QueryResult
item={item} item={item}
collection={collection} collection={collection}
width={rightPaneWidth} width={rightPaneWidth}
disableRunEventListener={true} disableRunEventListener={true}
value={(responseReceived && responseReceived.data) ? safeStringifyJSON(responseReceived.data, true) : ''} value={responseReceived && responseReceived.data ? safeStringifyJSON(responseReceived.data, true) : ''}
/>; />
);
} }
case 'headers': { case 'headers': {
return <ResponseHeaders headers={headers} />; return <ResponseHeaders headers={headers} />;

View File

@ -18,14 +18,14 @@ const getRelativePath = (fullPath, pathname) => {
let relativePath = path.relative(fullPath, pathname); let relativePath = path.relative(fullPath, pathname);
const { dir, name } = path.parse(relativePath); const { dir, name } = path.parse(relativePath);
return path.join(dir, name); return path.join(dir, name);
} };
export default function RunnerResults({collection}) { export default function RunnerResults({ collection }) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [selectedItem, setSelectedItem] = useState(null); const [selectedItem, setSelectedItem] = useState(null);
useEffect(() => { useEffect(() => {
if(!collection.runnerResult) { if (!collection.runnerResult) {
setSelectedItem(null); setSelectedItem(null);
} }
}, [collection, setSelectedItem]); }, [collection, setSelectedItem]);
@ -42,8 +42,8 @@ export default function RunnerResults({collection}) {
item.pathname = info.pathname; item.pathname = info.pathname;
item.relativePath = getRelativePath(collection.pathname, info.pathname); item.relativePath = getRelativePath(collection.pathname, info.pathname);
if(item.status !== "error") { if (item.status !== 'error') {
if(item.testResults) { if (item.testResults) {
const failed = item.testResults.filter((result) => result.status === 'fail'); const failed = item.testResults.filter((result) => result.status === 'fail');
item.testStatus = failed.length ? 'fail' : 'pass'; item.testStatus = failed.length ? 'fail' : 'pass';
@ -51,7 +51,7 @@ export default function RunnerResults({collection}) {
item.testStatus = 'pass'; item.testStatus = 'pass';
} }
if(item.assertionResults) { if (item.assertionResults) {
const failed = item.assertionResults.filter((result) => result.status === 'fail'); const failed = item.assertionResults.filter((result) => result.status === 'fail');
item.assertionStatus = failed.length ? 'fail' : 'pass'; item.assertionStatus = failed.length ? 'fail' : 'pass';
@ -70,29 +70,31 @@ export default function RunnerResults({collection}) {
}; };
const closeRunner = () => { const closeRunner = () => {
dispatch(closeCollectionRunner({ dispatch(
collectionUid: collection.uid, closeCollectionRunner({
})); collectionUid: collection.uid
})
);
}; };
const totalRequestsInCollection = getTotalRequestCountInCollection(collectionCopy); const totalRequestsInCollection = getTotalRequestCountInCollection(collectionCopy);
const passedRequests = items.filter((item) => { const passedRequests = items.filter((item) => {
return item.status !== "error" && item.testStatus === 'pass' && item.assertionStatus === 'pass'; return item.status !== 'error' && item.testStatus === 'pass' && item.assertionStatus === 'pass';
}); });
const failedRequests = items.filter((item) => { const failedRequests = items.filter((item) => {
return item.status !== "error" && item.testStatus === 'fail' || item.assertionStatus === 'fail'; return (item.status !== 'error' && item.testStatus === 'fail') || item.assertionStatus === 'fail';
}); });
if(!items || !items.length) { if (!items || !items.length) {
return ( return (
<StyledWrapper className='px-4'> <StyledWrapper className="px-4">
<div className='font-medium mt-6 title flex items-center'> <div className="font-medium mt-6 title flex items-center">
Runner Runner
<IconRun size={20} strokeWidth={1.5} className='ml-2'/> <IconRun size={20} strokeWidth={1.5} className="ml-2" />
</div> </div>
<div className='mt-6'> <div className="mt-6">
You have <span className='font-medium'>{totalRequestsInCollection}</span> requests in this collection. You have <span className="font-medium">{totalRequestsInCollection}</span> requests in this collection.
</div> </div>
<button type="submit" className="submit btn btn-sm btn-secondary mt-6" onClick={runCollection}> <button type="submit" className="submit btn btn-sm btn-secondary mt-6" onClick={runCollection}>
@ -107,13 +109,13 @@ export default function RunnerResults({collection}) {
} }
return ( return (
<StyledWrapper className='px-4'> <StyledWrapper className="px-4">
<div className='font-medium mt-6 mb-4 title flex items-center'> <div className="font-medium mt-6 mb-4 title flex items-center">
Runner Runner
<IconRun size={20} strokeWidth={1.5} className='ml-2'/> <IconRun size={20} strokeWidth={1.5} className="ml-2" />
</div> </div>
<div className='flex'> <div className="flex">
<div className='flex flex-col flex-1'> <div className="flex flex-col flex-1">
<div className="py-2 font-medium test-summary"> <div className="py-2 font-medium test-summary">
Total Requests: {items.length}, Passed: {passedRequests.length}, Failed: {failedRequests.length} Total Requests: {items.length}, Passed: {passedRequests.length}, Failed: {failedRequests.length}
</div> </div>
@ -123,73 +125,69 @@ export default function RunnerResults({collection}) {
<div className="item-path mt-2"> <div className="item-path mt-2">
<div className="flex items-center"> <div className="flex items-center">
<span> <span>
{item.status !== "error" && item.testStatus === 'pass' ? ( {item.status !== 'error' && item.testStatus === 'pass' ? (
<IconCircleCheck className="test-success" size={20} strokeWidth={1.5}/> <IconCircleCheck className="test-success" size={20} strokeWidth={1.5} />
) : ( ) : (
<IconCircleX className="test-failure" size={20} strokeWidth={1.5}/> <IconCircleX className="test-failure" size={20} strokeWidth={1.5} />
)} )}
</span> </span>
<span className={`mr-1 ml-2 ${(item.status == "error" || item.testStatus == 'fail') ? 'danger' : ''}`}>{item.relativePath}</span> <span
{(item.status !== "error" && item.status !== "completed") ? ( className={`mr-1 ml-2 ${item.status == 'error' || item.testStatus == 'fail' ? 'danger' : ''}`}
<IconRefresh className="animate-spin ml-1" size={18} strokeWidth={1.5}/> >
) : ( {item.relativePath}
<span className='text-xs link cursor-pointer' onClick={() => setSelectedItem(item)}>
(<span className='mr-1'>
{get(item.responseReceived, 'status')}
</span> </span>
<span> {item.status !== 'error' && item.status !== 'completed' ? (
{get(item.responseReceived, 'statusText')} <IconRefresh className="animate-spin ml-1" size={18} strokeWidth={1.5} />
</span>) ) : (
<span className="text-xs link cursor-pointer" onClick={() => setSelectedItem(item)}>
(<span className="mr-1">{get(item.responseReceived, 'status')}</span>
<span>{get(item.responseReceived, 'statusText')}</span>)
</span> </span>
)} )}
</div> </div>
{item.status == "error" ? ( {item.status == 'error' ? <div className="error-message pl-8 pt-2 text-xs">{item.error}</div> : null}
<div className="error-message pl-8 pt-2 text-xs">
{item.error}
</div>
) : null }
<ul className="pl-8"> <ul className="pl-8">
{item.testResults ? item.testResults.map((result) => ( {item.testResults
? item.testResults.map((result) => (
<li key={result.uid}> <li key={result.uid}>
{result.status === 'pass' ? ( {result.status === 'pass' ? (
<span className="test-success flex items-center"> <span className="test-success flex items-center">
<IconCheck size={18} strokeWidth={2} className="mr-2"/> <IconCheck size={18} strokeWidth={2} className="mr-2" />
{result.description} {result.description}
</span> </span>
) : ( ) : (
<> <>
<span className="test-failure flex items-center"> <span className="test-failure flex items-center">
<IconX size={18} strokeWidth={2} className="mr-2"/> <IconX size={18} strokeWidth={2} className="mr-2" />
{result.description} {result.description}
</span> </span>
<span className="error-message pl-8 text-xs"> <span className="error-message pl-8 text-xs">{result.error}</span>
{result.error}
</span>
</> </>
)} )}
</li> </li>
)): null} ))
{item.assertionResults ? item.assertionResults.map((result) => ( : null}
{item.assertionResults
? item.assertionResults.map((result) => (
<li key={result.uid}> <li key={result.uid}>
{result.status === 'pass' ? ( {result.status === 'pass' ? (
<span className="test-success flex items-center"> <span className="test-success flex items-center">
<IconCheck size={18} strokeWidth={2} className="mr-2"/> <IconCheck size={18} strokeWidth={2} className="mr-2" />
{result.lhsExpr}: {result.rhsExpr} {result.lhsExpr}: {result.rhsExpr}
</span> </span>
) : ( ) : (
<> <>
<span className="test-failure flex items-center"> <span className="test-failure flex items-center">
<IconX size={18} strokeWidth={2} className="mr-2"/> <IconX size={18} strokeWidth={2} className="mr-2" />
{result.lhsExpr}: {result.rhsExpr} {result.lhsExpr}: {result.rhsExpr}
</span> </span>
<span className="error-message pl-8 text-xs"> <span className="error-message pl-8 text-xs">{result.error}</span>
{result.error}
</span>
</> </>
)} )}
</li> </li>
)): null} ))
: null}
</ul> </ul>
</div> </div>
</div> </div>
@ -210,25 +208,25 @@ export default function RunnerResults({collection}) {
</div> </div>
) : null} ) : null}
</div> </div>
<div className='flex flex-1' style={{width: '50%'}}> <div className="flex flex-1" style={{ width: '50%' }}>
{selectedItem ? ( {selectedItem ? (
<div className='flex flex-col w-full overflow-auto'> <div className="flex flex-col w-full overflow-auto">
<div className="flex items-center px-3 mb-4 font-medium"> <div className="flex items-center px-3 mb-4 font-medium">
<span className='mr-2'>{selectedItem.relativePath}</span> <span className="mr-2">{selectedItem.relativePath}</span>
<span> <span>
{selectedItem.testStatus === 'pass' ? ( {selectedItem.testStatus === 'pass' ? (
<IconCircleCheck className="test-success" size={20} strokeWidth={1.5}/> <IconCircleCheck className="test-success" size={20} strokeWidth={1.5} />
) : ( ) : (
<IconCircleX className="test-failure" size={20} strokeWidth={1.5}/> <IconCircleX className="test-failure" size={20} strokeWidth={1.5} />
)} )}
</span> </span>
</div> </div>
{/* <div className='px-3 mb-4 font-medium'>{selectedItem.relativePath}</div> */} {/* <div className='px-3 mb-4 font-medium'>{selectedItem.relativePath}</div> */}
<ResponsePane item={selectedItem} collection={collection}/> <ResponsePane item={selectedItem} collection={collection} />
</div> </div>
) : null} ) : null}
</div> </div>
</div> </div>
</StyledWrapper> </StyledWrapper>
); );
}; }

View File

@ -17,7 +17,10 @@ const CloneCollectionItem = ({ collection, item, onClose }) => {
name: item.name name: item.name
}, },
validationSchema: Yup.object({ validationSchema: Yup.object({
name: Yup.string().min(1, 'must be atleast 1 characters').max(50, 'must be 50 characters or less').required('name is required') name: Yup.string()
.min(1, 'must be atleast 1 characters')
.max(50, 'must be 50 characters or less')
.required('name is required')
}), }),
onSubmit: (values) => { onSubmit: (values) => {
dispatch(cloneItem(values.name, item.uid, collection.uid)) dispatch(cloneItem(values.name, item.uid, collection.uid))
@ -25,7 +28,7 @@ const CloneCollectionItem = ({ collection, item, onClose }) => {
onClose(); onClose();
}) })
.catch((err) => { .catch((err) => {
toast.error(err ? err.message : 'An error occured while cloning the request') toast.error(err ? err.message : 'An error occured while cloning the request');
}); });
} }
}); });
@ -39,7 +42,13 @@ const CloneCollectionItem = ({ collection, item, onClose }) => {
const onSubmit = () => formik.handleSubmit(); const onSubmit = () => formik.handleSubmit();
return ( return (
<Modal size="sm" title={`Clone ${isFolder ? 'Folder' : 'Request'}`} confirmText="Clone" handleConfirm={onSubmit} handleCancel={onClose}> <Modal
size="sm"
title={`Clone ${isFolder ? 'Folder' : 'Request'}`}
confirmText="Clone"
handleConfirm={onSubmit}
handleCancel={onClose}
>
<form className="bruno-form" onSubmit={formik.handleSubmit}> <form className="bruno-form" onSubmit={formik.handleSubmit}>
<div> <div>
<label htmlFor="name" className="block font-semibold"> <label htmlFor="name" className="block font-semibold">

View File

@ -31,7 +31,13 @@ const DeleteCollectionItem = ({ onClose, item, collection }) => {
return ( return (
<StyledWrapper> <StyledWrapper>
<Modal size="sm" title={`Delete ${isFolder ? 'Folder' : 'Request'}`} confirmText="Delete" handleConfirm={onConfirm} handleCancel={onClose}> <Modal
size="sm"
title={`Delete ${isFolder ? 'Folder' : 'Request'}`}
confirmText="Delete"
handleConfirm={onConfirm}
handleCancel={onClose}
>
Are you sure you want to delete <span className="font-semibold">{item.name}</span> ? Are you sure you want to delete <span className="font-semibold">{item.name}</span> ?
</Modal> </Modal>
</StyledWrapper> </StyledWrapper>

View File

@ -16,7 +16,10 @@ const RenameCollectionItem = ({ collection, item, onClose }) => {
name: item.name name: item.name
}, },
validationSchema: Yup.object({ validationSchema: Yup.object({
name: Yup.string().min(1, 'must be atleast 1 characters').max(50, 'must be 50 characters or less').required('name is required') name: Yup.string()
.min(1, 'must be atleast 1 characters')
.max(50, 'must be 50 characters or less')
.required('name is required')
}), }),
onSubmit: (values) => { onSubmit: (values) => {
dispatch(renameItem(values.name, item.uid, collection.uid)); dispatch(renameItem(values.name, item.uid, collection.uid));
@ -33,7 +36,13 @@ const RenameCollectionItem = ({ collection, item, onClose }) => {
const onSubmit = () => formik.handleSubmit(); const onSubmit = () => formik.handleSubmit();
return ( return (
<Modal size="sm" title={`Rename ${isFolder ? 'Folder' : 'Request'}`} confirmText="Rename" handleConfirm={onSubmit} handleCancel={onClose}> <Modal
size="sm"
title={`Rename ${isFolder ? 'Folder' : 'Request'}`}
confirmText="Rename"
handleConfirm={onSubmit}
handleCancel={onClose}
>
<form className="bruno-form" onSubmit={formik.handleSubmit}> <form className="bruno-form" onSubmit={formik.handleSubmit}>
<div> <div>
<label htmlFor="name" className="block font-semibold"> <label htmlFor="name" className="block font-semibold">

View File

@ -11,9 +11,11 @@ const RunCollectionItem = ({ collection, item, onClose }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const onSubmit = (recursive) => { const onSubmit = (recursive) => {
dispatch(showRunnerView({ dispatch(
collectionUid: collection.uid, showRunnerView({
})); collectionUid: collection.uid
})
);
dispatch(runCollectionFolder(collection.uid, item ? item.uid : null, recursive)); dispatch(runCollectionFolder(collection.uid, item ? item.uid : null, recursive));
onClose(); onClose();
}; };
@ -25,25 +27,21 @@ const RunCollectionItem = ({ collection, item, onClose }) => {
return ( return (
<StyledWrapper> <StyledWrapper>
<Modal size="md" title='Collection Runner' hideFooter={true} handleCancel={onClose}> <Modal size="md" title="Collection Runner" hideFooter={true} handleCancel={onClose}>
<div className='mb-1'> <div className="mb-1">
<span className='font-medium'>Run</span> <span className="font-medium">Run</span>
<span className='ml-1 text-xs'>({runLength} requests)</span> <span className="ml-1 text-xs">({runLength} requests)</span>
</div>
<div className='mb-8'>
This will only run the requests in this folder.
</div> </div>
<div className="mb-8">This will only run the requests in this folder.</div>
<div className='mb-1'> <div className="mb-1">
<span className='font-medium'>Recursive Run</span> <span className="font-medium">Recursive Run</span>
<span className='ml-1 text-xs'>({recursiveRunLength} requests)</span> <span className="ml-1 text-xs">({recursiveRunLength} requests)</span>
</div>
<div className='mb-8'>
This will run all the requests in this folder and all its subfolders.
</div> </div>
<div className="mb-8">This will run all the requests in this folder and all its subfolders.</div>
<div className="flex justify-end bruno-modal-footer"> <div className="flex justify-end bruno-modal-footer">
<span className='mr-3'> <span className="mr-3">
<button type="button" onClick={onClose} className="btn btn-md btn-close"> <button type="button" onClick={onClose} className="btn btn-md btn-close">
Cancel Cancel
</button> </button>

View File

@ -86,9 +86,11 @@ const CollectionItem = ({ item, collection, searchText }) => {
}); });
const handleClick = (event) => { const handleClick = (event) => {
dispatch(hideRunnerView({ dispatch(
hideRunnerView({
collectionUid: collection.uid collectionUid: collection.uid
})); })
);
if (isItemARequest(item)) { if (isItemARequest(item)) {
if (itemIsOpenedInTabs(item, tabs)) { if (itemIsOpenedInTabs(item, tabs)) {
dispatch( dispatch(
@ -151,12 +153,24 @@ const CollectionItem = ({ item, collection, searchText }) => {
return ( return (
<StyledWrapper className={className}> <StyledWrapper className={className}>
{renameItemModalOpen && <RenameCollectionItem item={item} collection={collection} onClose={() => setRenameItemModalOpen(false)} />} {renameItemModalOpen && (
{cloneItemModalOpen && <CloneCollectionItem item={item} collection={collection} onClose={() => setCloneItemModalOpen(false)} />} <RenameCollectionItem item={item} collection={collection} onClose={() => setRenameItemModalOpen(false)} />
{deleteItemModalOpen && <DeleteCollectionItem item={item} collection={collection} onClose={() => setDeleteItemModalOpen(false)} />} )}
{newRequestModalOpen && <NewRequest item={item} collection={collection} onClose={() => setNewRequestModalOpen(false)} />} {cloneItemModalOpen && (
{newFolderModalOpen && <NewFolder item={item} collection={collection} onClose={() => setNewFolderModalOpen(false)} />} <CloneCollectionItem item={item} collection={collection} onClose={() => setCloneItemModalOpen(false)} />
{runCollectionModalOpen && <RunCollectionItem collection={collection} item={item} onClose={() => setRunCollectionModalOpen(false)} />} )}
{deleteItemModalOpen && (
<DeleteCollectionItem item={item} collection={collection} onClose={() => setDeleteItemModalOpen(false)} />
)}
{newRequestModalOpen && (
<NewRequest item={item} collection={collection} onClose={() => setNewRequestModalOpen(false)} />
)}
{newFolderModalOpen && (
<NewFolder item={item} collection={collection} onClose={() => setNewFolderModalOpen(false)} />
)}
{runCollectionModalOpen && (
<RunCollectionItem collection={collection} item={item} onClose={() => setRunCollectionModalOpen(false)} />
)}
<div className={itemRowClassName} ref={(node) => drag(drop(node))}> <div className={itemRowClassName} ref={(node) => drag(drop(node))}>
<div className="flex items-center h-full w-full"> <div className="flex items-center h-full w-full">
{indents && indents.length {indents && indents.length
@ -185,7 +199,14 @@ const CollectionItem = ({ item, collection, searchText }) => {
}} }}
> >
<div style={{ width: 16, minWidth: 16 }}> <div style={{ width: 16, minWidth: 16 }}>
{isFolder ? <IconChevronRight size={16} strokeWidth={2} className={iconClassName} style={{ color: 'rgb(160 160 160)' }} /> : null} {isFolder ? (
<IconChevronRight
size={16}
strokeWidth={2}
className={iconClassName}
style={{ color: 'rgb(160 160 160)' }}
/>
) : null}
</div> </div>
<div className="ml-1 flex items-center overflow-hidden"> <div className="ml-1 flex items-center overflow-hidden">

View File

@ -15,7 +15,10 @@ const RenameCollection = ({ collection, onClose }) => {
name: collection.name name: collection.name
}, },
validationSchema: Yup.object({ validationSchema: Yup.object({
name: Yup.string().min(1, 'must be atleast 1 characters').max(50, 'must be 50 characters or less').required('name is required') name: Yup.string()
.min(1, 'must be atleast 1 characters')
.max(50, 'must be 50 characters or less')
.required('name is required')
}), }),
onSubmit: (values) => { onSubmit: (values) => {
dispatch(renameCollection(values.name, collection.uid)); dispatch(renameCollection(values.name, collection.uid));

View File

@ -97,13 +97,26 @@ const Collection = ({ collection, searchText }) => {
<StyledWrapper className="flex flex-col"> <StyledWrapper className="flex flex-col">
{showNewRequestModal && <NewRequest collection={collection} onClose={() => setShowNewRequestModal(false)} />} {showNewRequestModal && <NewRequest collection={collection} onClose={() => setShowNewRequestModal(false)} />}
{showNewFolderModal && <NewFolder collection={collection} onClose={() => setShowNewFolderModal(false)} />} {showNewFolderModal && <NewFolder collection={collection} onClose={() => setShowNewFolderModal(false)} />}
{showRenameCollectionModal && <RenameCollection collection={collection} onClose={() => setShowRenameCollectionModal(false)} />} {showRenameCollectionModal && (
{showRemoveCollectionModal && <RemoveCollection collection={collection} onClose={() => setShowRemoveCollectionModal(false)} />} <RenameCollection collection={collection} onClose={() => setShowRenameCollectionModal(false)} />
{showRunCollectionModal && <RunCollectionItem collection={collection} onClose={() => setShowRunCollectionModal(false)} />} )}
{showRemoveCollectionModal && (
<RemoveCollection collection={collection} onClose={() => setShowRemoveCollectionModal(false)} />
)}
{showRunCollectionModal && (
<RunCollectionItem collection={collection} onClose={() => setShowRunCollectionModal(false)} />
)}
<div className="flex py-1 collection-name items-center" ref={drop}> <div className="flex py-1 collection-name items-center" ref={drop}>
<div className="flex flex-grow items-center overflow-hidden" onClick={handleClick}> <div className="flex flex-grow items-center overflow-hidden" onClick={handleClick}>
<IconChevronRight size={16} strokeWidth={2} className={iconClassName} style={{ width: 16, minWidth:16, color: 'rgb(160 160 160)' }} /> <IconChevronRight
<div className="ml-1" id="sidebar-collection-name">{collection.name}</div> size={16}
strokeWidth={2}
className={iconClassName}
style={{ width: 16, minWidth: 16, color: 'rgb(160 160 160)' }}
/>
<div className="ml-1" id="sidebar-collection-name">
{collection.name}
</div>
</div> </div>
<div className="collection-actions"> <div className="collection-actions">
<Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement="bottom-start"> <Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement="bottom-start">

View File

@ -18,10 +18,16 @@ const CreateOrOpenCollection = () => {
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false); const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
const handleOpenCollection = () => { const handleOpenCollection = () => {
dispatch(openCollection()).catch((err) => console.log(err) && toast.error('An error occured while opening the collection')); dispatch(openCollection()).catch(
(err) => console.log(err) && toast.error('An error occured while opening the collection')
);
}; };
const CreateLink = () => ( const CreateLink = () => (
<LinkStyle className="underline text-link cursor-pointer" theme={theme} onClick={() => setCreateCollectionModalOpen(true)}> <LinkStyle
className="underline text-link cursor-pointer"
theme={theme}
onClick={() => setCreateCollectionModalOpen(true)}
>
Create Create
</LinkStyle> </LinkStyle>
); );

View File

@ -10,7 +10,7 @@ const StyledWrapper = styled.div`
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background-color: ${(props) => props.theme.plainGrid.hoverBg};; background-color: ${(props) => props.theme.plainGrid.hoverBg};
} }
} }
`; `;

View File

@ -20,8 +20,14 @@ const CreateCollection = ({ onClose }) => {
collectionLocation: '' collectionLocation: ''
}, },
validationSchema: Yup.object({ validationSchema: Yup.object({
collectionName: Yup.string().min(1, 'must be atleast 1 characters').max(50, 'must be 50 characters or less').required('collection name is required'), collectionName: Yup.string()
collectionFolderName: Yup.string().min(1, 'must be atleast 1 characters').max(50, 'must be 50 characters or less').required('folder name is required'), .min(1, 'must be atleast 1 characters')
.max(50, 'must be 50 characters or less')
.required('collection name is required'),
collectionFolderName: Yup.string()
.min(1, 'must be atleast 1 characters')
.max(50, 'must be 50 characters or less')
.required('folder name is required'),
collectionLocation: Yup.string().required('location is required') collectionLocation: Yup.string().required('location is required')
}), }),
onSubmit: (values) => { onSubmit: (values) => {
@ -58,8 +64,8 @@ const CreateCollection = ({ onClose }) => {
<form className="bruno-form" onSubmit={formik.handleSubmit}> <form className="bruno-form" onSubmit={formik.handleSubmit}>
<div> <div>
<label htmlFor="collectionName" className="flex items-center"> <label htmlFor="collectionName" className="flex items-center">
<span className='font-semibold'>Name</span> <span className="font-semibold">Name</span>
<Tooltip text="Name of the collection" tooltipId="collection-name"/> <Tooltip text="Name of the collection" tooltipId="collection-name" />
</label> </label>
<input <input
id="collection-name" id="collection-name"
@ -74,11 +80,13 @@ const CreateCollection = ({ onClose }) => {
spellCheck="false" spellCheck="false"
value={formik.values.collectionName || ''} value={formik.values.collectionName || ''}
/> />
{formik.touched.collectionName && formik.errors.collectionName ? <div className="text-red-500">{formik.errors.collectionName}</div> : null} {formik.touched.collectionName && formik.errors.collectionName ? (
<div className="text-red-500">{formik.errors.collectionName}</div>
) : null}
<label htmlFor="collectionFolderName" className="flex items-center mt-3"> <label htmlFor="collectionFolderName" className="flex items-center mt-3">
<span className='font-semibold'>Folder Name</span> <span className="font-semibold">Folder Name</span>
<Tooltip text="Name of the folder where your collection is stored" tooltipId="collection-folder-name"/> <Tooltip text="Name of the folder where your collection is stored" tooltipId="collection-folder-name" />
</label> </label>
<input <input
id="collection-folder-name" id="collection-folder-name"
@ -92,7 +100,9 @@ const CreateCollection = ({ onClose }) => {
spellCheck="false" spellCheck="false"
value={formik.values.collectionFolderName || ''} value={formik.values.collectionFolderName || ''}
/> />
{formik.touched.collectionFolderName && formik.errors.collectionFolderName ? <div className="text-red-500">{formik.errors.collectionFolderName}</div> : null} {formik.touched.collectionFolderName && formik.errors.collectionFolderName ? (
<div className="text-red-500">{formik.errors.collectionFolderName}</div>
) : null}
<> <>
<label htmlFor="collectionLocation" className="block font-semibold mt-3"> <label htmlFor="collectionLocation" className="block font-semibold mt-3">

View File

@ -33,22 +33,13 @@ const ImportCollection = ({ onClose, handleSubmit }) => {
return ( return (
<Modal size="sm" title="Import Collection" hideFooter={true} handleConfirm={onClose} handleCancel={onClose}> <Modal size="sm" title="Import Collection" hideFooter={true} handleConfirm={onClose} handleCancel={onClose}>
<div> <div>
<div <div className="text-link hover:underline cursor-pointer" onClick={handleImportBrunoCollection}>
className='text-link hover:underline cursor-pointer'
onClick={handleImportBrunoCollection}
>
Bruno Collection Bruno Collection
</div> </div>
<div <div className="text-link hover:underline cursor-pointer mt-2" onClick={handleImportPostmanCollection}>
className='text-link hover:underline cursor-pointer mt-2'
onClick={handleImportPostmanCollection}
>
Postman Collection Postman Collection
</div> </div>
<div <div className="text-link hover:underline cursor-pointer mt-2" onClick={handleImportInsomniaCollection}>
className='text-link hover:underline cursor-pointer mt-2'
onClick={handleImportInsomniaCollection}
>
Insomnia Collection Insomnia Collection
</div> </div>
</div> </div>

View File

@ -15,7 +15,10 @@ const ImportCollectionLocation = ({ onClose, handleSubmit, collectionName }) =>
collectionLocation: '' collectionLocation: ''
}, },
validationSchema: Yup.object({ validationSchema: Yup.object({
collectionLocation: Yup.string().min(1, 'must be atleast 1 characters').max(500, 'must be 500 characters or less').required('name is required') collectionLocation: Yup.string()
.min(1, 'must be atleast 1 characters')
.max(500, 'must be 500 characters or less')
.required('name is required')
}), }),
onSubmit: (values) => { onSubmit: (values) => {
console.log('here'); console.log('here');
@ -49,7 +52,7 @@ const ImportCollectionLocation = ({ onClose, handleSubmit, collectionName }) =>
<label htmlFor="collectionName" className="block font-semibold"> <label htmlFor="collectionName" className="block font-semibold">
Name Name
</label> </label>
<div className='mt-2'>{collectionName}</div> <div className="mt-2">{collectionName}</div>
<> <>
<label htmlFor="collectionLocation" className="block font-semibold mt-3"> <label htmlFor="collectionLocation" className="block font-semibold mt-3">

View File

@ -21,11 +21,11 @@ const NewFolder = ({ collection, item, onClose }) => {
.test({ .test({
name: 'folderName', name: 'folderName',
message: 'The folder name "environments" at the root of the collection is reserved in bruno', message: 'The folder name "environments" at the root of the collection is reserved in bruno',
test:(value) => { test: (value) => {
if(item && item.uid) { if (item && item.uid) {
return true; return true;
} }
return value && !(value.trim().toLowerCase().includes('environments')) return value && !value.trim().toLowerCase().includes('environments');
} }
}) })
}), }),
@ -64,7 +64,9 @@ const NewFolder = ({ collection, item, onClose }) => {
onChange={formik.handleChange} onChange={formik.handleChange}
value={formik.values.folderName || ''} value={formik.values.folderName || ''}
/> />
{formik.touched.folderName && formik.errors.folderName ? <div className="text-red-500">{formik.errors.folderName}</div> : null} {formik.touched.folderName && formik.errors.folderName ? (
<div className="text-red-500">{formik.errors.folderName}</div>
) : null}
</div> </div>
</form> </form>
</Modal> </Modal>

View File

@ -30,7 +30,7 @@ const NewRequest = ({ collection, item, isEphermal, onClose }) => {
.test({ .test({
name: 'requestName', name: 'requestName',
message: 'The request name "index" is reserved in bruno', message: 'The request name "index" is reserved in bruno',
test: value => value && !(value.trim().toLowerCase().includes('index')), test: (value) => value && !value.trim().toLowerCase().includes('index')
}) })
}), }),
onSubmit: (values) => { onSubmit: (values) => {
@ -51,7 +51,7 @@ const NewRequest = ({ collection, item, isEphermal, onClose }) => {
addTab({ addTab({
uid: uid, uid: uid,
collectionUid: collection.uid, collectionUid: collection.uid,
requestPaneTab: getDefaultRequestPaneTab({type: values.requestType}) requestPaneTab: getDefaultRequestPaneTab({ type: values.requestType })
}) })
); );
onClose(); onClose();
@ -140,7 +140,9 @@ const NewRequest = ({ collection, item, isEphermal, onClose }) => {
onChange={formik.handleChange} onChange={formik.handleChange}
value={formik.values.requestName || ''} value={formik.values.requestName || ''}
/> />
{formik.touched.requestName && formik.errors.requestName ? <div className="text-red-500">{formik.errors.requestName}</div> : null} {formik.touched.requestName && formik.errors.requestName ? (
<div className="text-red-500">{formik.errors.requestName}</div>
) : null}
</div> </div>
<div className="mt-4"> <div className="mt-4">
@ -150,7 +152,10 @@ const NewRequest = ({ collection, item, isEphermal, onClose }) => {
<div className="flex items-center mt-2 "> <div className="flex items-center mt-2 ">
<div className="flex items-center h-full method-selector-container"> <div className="flex items-center h-full method-selector-container">
<HttpMethodSelector method={formik.values.requestMethod} onMethodSelect={(val) => formik.setFieldValue('requestMethod', val)} /> <HttpMethodSelector
method={formik.values.requestMethod}
onMethodSelect={(val) => formik.setFieldValue('requestMethod', val)}
/>
</div> </div>
<div className="flex items-center flex-grow input-container h-full"> <div className="flex items-center flex-grow input-container h-full">
<input <input
@ -167,7 +172,9 @@ const NewRequest = ({ collection, item, isEphermal, onClose }) => {
/> />
</div> </div>
</div> </div>
{formik.touched.requestUrl && formik.errors.requestUrl ? <div className="text-red-500">{formik.errors.requestUrl}</div> : null} {formik.touched.requestUrl && formik.errors.requestUrl ? (
<div className="text-red-500">{formik.errors.requestUrl}</div>
) : null}
</div> </div>
</form> </form>
</Modal> </Modal>

View File

@ -45,20 +45,24 @@ const TitleBar = () => {
const handleTitleClick = () => dispatch(showHomePage()); const handleTitleClick = () => dispatch(showHomePage());
const handleOpenCollection = () => { const handleOpenCollection = () => {
dispatch(openCollection()).catch((err) => console.log(err) && toast.error('An error occured while opening the collection')); dispatch(openCollection()).catch(
(err) => console.log(err) && toast.error('An error occured while opening the collection')
);
}; };
return ( return (
<StyledWrapper className="px-2 py-2"> <StyledWrapper className="px-2 py-2">
{createCollectionModalOpen ? <CreateCollection onClose={() => setCreateCollectionModalOpen(false)} /> : null} {createCollectionModalOpen ? <CreateCollection onClose={() => setCreateCollectionModalOpen(false)} /> : null}
{importCollectionModalOpen ? <ImportCollection onClose={() => setImportCollectionModalOpen(false)} handleSubmit={handleImportCollection} /> : null} {importCollectionModalOpen ? (
<ImportCollection onClose={() => setImportCollectionModalOpen(false)} handleSubmit={handleImportCollection} />
) : null}
{importCollectionLocationModalOpen ? ( {importCollectionLocationModalOpen ? (
<ImportCollectionLocation <ImportCollectionLocation
collectionName={importedCollection.name} collectionName={importedCollection.name}
onClose={() => setImportCollectionLocationModalOpen(false)} onClose={() => setImportCollectionLocationModalOpen(false)}
handleSubmit={handleImportCollectionLocation} handleSubmit={handleImportCollectionLocation}
/> />
): null} ) : null}
<div className="flex items-center"> <div className="flex items-center">
<div className="flex items-center cursor-pointer" onClick={handleTitleClick}> <div className="flex items-center cursor-pointer" onClick={handleTitleClick}>

View File

@ -88,7 +88,12 @@ const Sidebar = () => {
<div className="footer flex px-1 py-2 absolute bottom-0 left-0 right-0 items-center cursor-pointer select-none"> <div className="footer flex px-1 py-2 absolute bottom-0 left-0 right-0 items-center cursor-pointer select-none">
<div className="flex items-center ml-1 text-xs "> <div className="flex items-center ml-1 text-xs ">
<IconSettings size={18} strokeWidth={1.5} className="mr-2 hover:text-gray-700" onClick={() => setPreferencesOpen(true)} /> <IconSettings
size={18}
strokeWidth={1.5}
className="mr-2 hover:text-gray-700"
onClick={() => setPreferencesOpen(true)}
/>
</div> </div>
<div className="pl-1" style={{ position: 'relative', top: '3px' }}> <div className="pl-1" style={{ position: 'relative', top: '3px' }}>
{storedTheme === 'dark' ? ( {storedTheme === 'dark' ? (

View File

@ -49,7 +49,6 @@ const StyledWrapper = styled.div`
padding-right: 0; padding-right: 0;
} }
} }
`; `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -27,32 +27,32 @@ class SingleLineEditor extends Component {
lineWrapping: false, lineWrapping: false,
lineNumbers: false, lineNumbers: false,
theme: this.props.theme === 'dark' ? 'monokai' : 'default', theme: this.props.theme === 'dark' ? 'monokai' : 'default',
mode: "brunovariables", mode: 'brunovariables',
brunoVarInfo: { brunoVarInfo: {
variables: getAllVariables(this.props.collection), variables: getAllVariables(this.props.collection)
}, },
extraKeys: { extraKeys: {
"Enter": () => { Enter: () => {
if (this.props.onRun) { if (this.props.onRun) {
this.props.onRun(); this.props.onRun();
} }
}, },
"Ctrl-Enter": () => { 'Ctrl-Enter': () => {
if (this.props.onRun) { if (this.props.onRun) {
this.props.onRun(); this.props.onRun();
} }
}, },
"Cmd-Enter": () => { 'Cmd-Enter': () => {
if (this.props.onRun) { if (this.props.onRun) {
this.props.onRun(); this.props.onRun();
} }
}, },
"Alt-Enter": () => { 'Alt-Enter': () => {
if (this.props.onRun) { if (this.props.onRun) {
this.props.onRun(); this.props.onRun();
} }
}, },
"Shift-Enter": () => { 'Shift-Enter': () => {
if (this.props.onRun) { if (this.props.onRun) {
this.props.onRun(); this.props.onRun();
} }
@ -69,8 +69,8 @@ class SingleLineEditor extends Component {
}, },
'Cmd-F': () => {}, 'Cmd-F': () => {},
'Ctrl-F': () => {}, 'Ctrl-F': () => {},
'Tab': () => {} Tab: () => {}
}, }
}); });
this.editor.setValue(this.props.value || ''); this.editor.setValue(this.props.value || '');
this.editor.on('change', this._onEdit); this.editor.on('change', this._onEdit);
@ -115,14 +115,12 @@ class SingleLineEditor extends Component {
let variables = getAllVariables(this.props.collection); let variables = getAllVariables(this.props.collection);
this.variables = variables; this.variables = variables;
defineCodeMirrorBrunoVariablesMode(variables, "text/plain"); defineCodeMirrorBrunoVariablesMode(variables, 'text/plain');
this.editor.setOption('mode', 'brunovariables'); this.editor.setOption('mode', 'brunovariables');
} };
render() { render() {
return ( return <StyledWrapper ref={this.editorRef} className="single-line-editor"></StyledWrapper>;
<StyledWrapper ref={this.editorRef} className="single-line-editor"></StyledWrapper>
);
} }
} }
export default SingleLineEditor; export default SingleLineEditor;

View File

@ -4,9 +4,19 @@ import { Tooltip as ReactTooltip } from 'react-tooltip';
const Tooltip = ({ text, tooltipId }) => { const Tooltip = ({ text, tooltipId }) => {
return ( return (
<> <>
<svg tabIndex="-1" id={tooltipId} xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" className="inline-block ml-2 cursor-pointer" viewBox="0 0 16 16" style={{marginTop: 1}}> <svg
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/> tabIndex="-1"
<path d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z"/> id={tooltipId}
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
fill="currentColor"
className="inline-block ml-2 cursor-pointer"
viewBox="0 0 16 16"
style={{ marginTop: 1 }}
>
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
<path d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z" />
</svg> </svg>
<ReactTooltip anchorId={tooltipId} content={text} /> <ReactTooltip anchorId={tooltipId} content={text} />
</> </>

View File

@ -1,16 +1,12 @@
import React, {useRef} from 'react'; import React, { useRef } from 'react';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
import useOnClickOutside from 'hooks/useOnClickOutside'; import useOnClickOutside from 'hooks/useOnClickOutside';
const PopOver = ({ const PopOver = ({ children, iconRef, handleClose }) => {
children,
iconRef,
handleClose
}) => {
const popOverRef = useRef(null); const popOverRef = useRef(null);
useOnClickOutside(popOverRef, (e) => { useOnClickOutside(popOverRef, (e) => {
if(iconRef && iconRef.current) { if (iconRef && iconRef.current) {
if (e.target == iconRef.current || iconRef.current.contains(e.target)) { if (e.target == iconRef.current || iconRef.current.contains(e.target)) {
return; return;
} }

View File

@ -10,6 +10,6 @@ const StyledWrapper = styled.div`
width: 1rem; width: 1rem;
font-size: 10px; font-size: 10px;
} }
` `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -5,8 +5,8 @@ const StyledWrapper = styled.div`
color: ${(props) => props.theme.variables.name.color}; color: ${(props) => props.theme.variables.name.color};
} }
.variable-name{ .variable-name {
min-width:180px; min-width: 180px;
} }
.variable-value { .variable-value {
@ -14,6 +14,6 @@ const StyledWrapper = styled.div`
inline-size: 600px; inline-size: 600px;
overflow-wrap: break-word; overflow-wrap: break-word;
} }
` `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -18,25 +18,33 @@ const VariablesTable = ({ variables, collectionVariables }) => {
return ( return (
<StyledWrapper> <StyledWrapper>
<div className="flex flex-col w-full"> <div className="flex flex-col w-full">
<div className='mb-2 font-medium'>Environment Variables</div> <div className="mb-2 font-medium">Environment Variables</div>
{(variables && variables.length) ? variables.map((variable) => { {variables && variables.length ? (
variables.map((variable) => {
return ( return (
<div key={variable.uid} className="flex"> <div key={variable.uid} className="flex">
<div className='variable-name text-yellow-600 text-right pr-2'>{variable.name}</div> <div className="variable-name text-yellow-600 text-right pr-2">{variable.name}</div>
<div className='variable-value pl-2 whitespace-normal text-left flex-grow'>{variable.value}</div> <div className="variable-value pl-2 whitespace-normal text-left flex-grow">{variable.value}</div>
</div> </div>
); );
}) : <small>No env variables found</small>} })
) : (
<small>No env variables found</small>
)}
<div className='mt-2 font-medium'>Collection Variables</div> <div className="mt-2 font-medium">Collection Variables</div>
{(collectionVars && collectionVars.length) ? collectionVars.map((variable) => { {collectionVars && collectionVars.length ? (
collectionVars.map((variable) => {
return ( return (
<div key={variable.uid} className="flex"> <div key={variable.uid} className="flex">
<div className='variable-name text-yellow-600 text-right pr-2'>{variable.name}</div> <div className="variable-name text-yellow-600 text-right pr-2">{variable.name}</div>
<div className='variable-value pl-2 whitespace-normal text-left flex-grow'>{variable.value}</div> <div className="variable-value pl-2 whitespace-normal text-left flex-grow">{variable.value}</div>
</div> </div>
); );
}) : <small>No collection variables found</small>} })
) : (
<small>No collection variables found</small>
)}
</div> </div>
</StyledWrapper> </StyledWrapper>
); );

View File

@ -7,46 +7,42 @@ import StyledWrapper from './StyledWrapper';
import PopOver from './Popover'; import PopOver from './Popover';
import { IconEye } from '@tabler/icons'; import { IconEye } from '@tabler/icons';
const VariablesView = ({collection}) => { const VariablesView = ({ collection }) => {
const iconRef = useRef(null); const iconRef = useRef(null);
const [popOverOpen, setPopOverOpen] = useState(false); const [popOverOpen, setPopOverOpen] = useState(false);
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid); const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
const variables = get(environment, 'variables', []); const variables = get(environment, 'variables', []);
const enabledVariables = filter(variables, (variable) => variable.enabled); const enabledVariables = filter(variables, (variable) => variable.enabled);
const showVariablesTable = enabledVariables.length > 0 || (collection.collectionVariables && Object.keys(collection.collectionVariables).length > 0); const showVariablesTable =
enabledVariables.length > 0 ||
(collection.collectionVariables && Object.keys(collection.collectionVariables).length > 0);
return ( return (
<StyledWrapper <StyledWrapper className="mr-2 server-syncstatus-icon" ref={iconRef}>
className="mr-2 server-syncstatus-icon" <div
ref={iconRef} className="flex p-1 items-center"
>
<div className="flex p-1 items-center"
onClick={() => setPopOverOpen(true)} onClick={() => setPopOverOpen(true)}
onMouseEnter={() => setPopOverOpen(true)} onMouseEnter={() => setPopOverOpen(true)}
onMouseLeave={() => setPopOverOpen(false)} onMouseLeave={() => setPopOverOpen(false)}
> >
<div className='cursor-pointer view-environment'> <div className="cursor-pointer view-environment">
<IconEye size={18} strokeWidth={1.5} /> <IconEye size={18} strokeWidth={1.5} />
</div> </div>
{popOverOpen && ( {popOverOpen && (
<PopOver <PopOver iconRef={iconRef} handleClose={() => setPopOverOpen(false)}>
iconRef={iconRef}
handleClose={() => setPopOverOpen(false)}
>
<div className="px-2 py-1"> <div className="px-2 py-1">
{showVariablesTable ? ( {showVariablesTable ? (
<VariablesTable <VariablesTable variables={enabledVariables} collectionVariables={collection.collectionVariables} />
variables={enabledVariables} ) : (
collectionVariables={collection.collectionVariables} 'No variables found'
/> )}
) : 'No variables found'}
</div> </div>
</PopOver> </PopOver>
)} )}
</div> </div>
</StyledWrapper> </StyledWrapper>
) );
}; };
export default VariablesView; export default VariablesView;

View File

@ -18,7 +18,9 @@ const Welcome = () => {
const [importCollectionLocationModalOpen, setImportCollectionLocationModalOpen] = useState(false); const [importCollectionLocationModalOpen, setImportCollectionLocationModalOpen] = useState(false);
const handleOpenCollection = () => { const handleOpenCollection = () => {
dispatch(openCollection()).catch((err) => console.log(err) && toast.error('An error occured while opening the collection')); dispatch(openCollection()).catch(
(err) => console.log(err) && toast.error('An error occured while opening the collection')
);
}; };
const handleImportCollection = (collection) => { const handleImportCollection = (collection) => {
@ -37,14 +39,16 @@ const Welcome = () => {
return ( return (
<StyledWrapper className="pb-4 px-6 mt-6"> <StyledWrapper className="pb-4 px-6 mt-6">
{createCollectionModalOpen ? <CreateCollection onClose={() => setCreateCollectionModalOpen(false)} /> : null} {createCollectionModalOpen ? <CreateCollection onClose={() => setCreateCollectionModalOpen(false)} /> : null}
{importCollectionModalOpen ? <ImportCollection onClose={() => setImportCollectionModalOpen(false)} handleSubmit={handleImportCollection} /> : null} {importCollectionModalOpen ? (
<ImportCollection onClose={() => setImportCollectionModalOpen(false)} handleSubmit={handleImportCollection} />
) : null}
{importCollectionLocationModalOpen ? ( {importCollectionLocationModalOpen ? (
<ImportCollectionLocation <ImportCollectionLocation
collectionName={importedCollection.name} collectionName={importedCollection.name}
onClose={() => setImportCollectionLocationModalOpen(false)} onClose={() => setImportCollectionLocationModalOpen(false)}
handleSubmit={handleImportCollectionLocation} handleSubmit={handleImportCollectionLocation}
/> />
): null} ) : null}
<div className=""> <div className="">
<Bruno width={50} /> <Bruno width={50} />
@ -62,13 +66,13 @@ const Welcome = () => {
</div> </div>
<div className="flex items-center ml-6" onClick={handleOpenCollection}> <div className="flex items-center ml-6" onClick={handleOpenCollection}>
<IconFolders size={18} strokeWidth={2} /> <IconFolders size={18} strokeWidth={2} />
<span className="label ml-2"> <span className="label ml-2">Open Collection</span>
Open Collection
</span>
</div> </div>
<div className="flex items-center ml-6" onClick={() => setImportCollectionModalOpen(true)}> <div className="flex items-center ml-6" onClick={() => setImportCollectionModalOpen(true)}>
<IconUpload size={18} strokeWidth={2} /> <IconUpload size={18} strokeWidth={2} />
<span className="label ml-2" id="import-collection">Import Collection</span> <span className="label ml-2" id="import-collection">
Import Collection
</span>
</div> </div>
</div> </div>

View File

@ -4,7 +4,7 @@ import { useEffect } from 'react';
const useOnClickOutside = (ref, handler) => { const useOnClickOutside = (ref, handler) => {
useEffect( useEffect(
() => { () => {
const listener = event => { const listener = (event) => {
// Do nothing if clicking ref's element or descendent elements // Do nothing if clicking ref's element or descendent elements
if (!ref.current || ref.current.contains(event.target)) { if (!ref.current || ref.current.contains(event.target)) {
return; return;

View File

@ -5,7 +5,7 @@ function usePrevious(value) {
useEffect(() => { useEffect(() => {
ref.current = value; //assign the value of ref to the argument ref.current = value; //assign the value of ref to the argument
},[value]); //this code will run when the value of 'value' changes }, [value]); //this code will run when the value of 'value' changes
return ref.current; //in the end, return the current ref value. return ref.current; //in the end, return the current ref value.
} }

View File

@ -36,7 +36,7 @@ function MyApp({ Component, pageProps }) {
setDomLoaded(true); setDomLoaded(true);
}, []); }, []);
if(!domLoaded) { if (!domLoaded) {
return null; return null;
} }

View File

@ -31,7 +31,10 @@ export default class MyDocument extends Document {
return ( return (
<Html> <Html>
<Head> <Head>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" /> <link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
</Head> </Head>
<body id="bruno-app-body"> <body id="bruno-app-body">
<Main /> <Main />

View File

@ -29,9 +29,7 @@ export const AppProvider = (props) => {
return ( return (
<AppContext.Provider {...props} value="appProvider"> <AppContext.Provider {...props} value="appProvider">
<StyledWrapper> <StyledWrapper>{props.children}</StyledWrapper>
{props.children}
</StyledWrapper>
</AppContext.Provider> </AppContext.Provider>
); );
}; };

View File

@ -31,7 +31,7 @@ const useCollectionTreeSync = () => {
}; };
const _collectionTreeUpdated = (type, val) => { const _collectionTreeUpdated = (type, val) => {
if(window.__IS_DEV__) { if (window.__IS_DEV__) {
console.log(type); console.log(type);
console.log(val); console.log(val);
} }
@ -85,10 +85,10 @@ const useCollectionTreeSync = () => {
}; };
const _displayError = (error) => { const _displayError = (error) => {
if(typeof error === "string") { if (typeof error === 'string') {
return toast.error(error || 'Something went wrong!'); return toast.error(error || 'Something went wrong!');
} }
if(typeof message === "object") { if (typeof message === 'object') {
return toast.error(error.message || 'Something went wrong!'); return toast.error(error.message || 'Something went wrong!');
} }
}; };

View File

@ -23,7 +23,7 @@ const getPlatform = () => {
}; };
const getPosthogClient = () => { const getPosthogClient = () => {
if(posthogClient) { if (posthogClient) {
return posthogClient; return posthogClient;
} }
@ -34,7 +34,7 @@ const getPosthogClient = () => {
const getAnonymousTrackingId = () => { const getAnonymousTrackingId = () => {
let id = localStorage.getItem('bruno.anonymousTrackingId'); let id = localStorage.getItem('bruno.anonymousTrackingId');
if(!id || !id.length || id.length !== 21) { if (!id || !id.length || id.length !== 21) {
id = uuid(); id = uuid();
localStorage.setItem('bruno.anonymousTrackingId', id); localStorage.setItem('bruno.anonymousTrackingId', id);
} }
@ -43,11 +43,11 @@ const getAnonymousTrackingId = () => {
}; };
const trackStart = () => { const trackStart = () => {
if(isPlaywrightTestRunning()) { if (isPlaywrightTestRunning()) {
return; return;
} }
if(isDevEnv()) { if (isDevEnv()) {
return; return;
} }
@ -67,7 +67,7 @@ const trackStart = () => {
const useTelemetry = () => { const useTelemetry = () => {
useEffect(() => { useEffect(() => {
trackStart(); trackStart();
setInterval(trackStart , 24 * 60 * 60 * 1000); setInterval(trackStart, 24 * 60 * 60 * 1000);
}, []); }, []);
}; };

View File

@ -165,9 +165,15 @@ export const HotkeysProvider = (props) => {
return ( return (
<HotkeysContext.Provider {...props} value="hotkey"> <HotkeysContext.Provider {...props} value="hotkey">
{showBrunoSupportModal && <BrunoSupport onClose={() => setShowBrunoSupportModal(false)} />} {showBrunoSupportModal && <BrunoSupport onClose={() => setShowBrunoSupportModal(false)} />}
{showSaveRequestModal && <SaveRequest items={getCurrentCollectionItems()} onClose={() => setShowSaveRequestModal(false)} />} {showSaveRequestModal && (
{showEnvSettingsModal && <EnvironmentSettings collection={getCurrentCollection()} onClose={() => setShowEnvSettingsModal(false)} />} <SaveRequest items={getCurrentCollectionItems()} onClose={() => setShowSaveRequestModal(false)} />
{showNewRequestModal && <NewRequest collection={getCurrentCollection()} onClose={() => setShowNewRequestModal(false)} />} )}
{showEnvSettingsModal && (
<EnvironmentSettings collection={getCurrentCollection()} onClose={() => setShowEnvSettingsModal(false)} />
)}
{showNewRequestModal && (
<NewRequest collection={getCurrentCollection()} onClose={() => setShowNewRequestModal(false)} />
)}
<div>{props.children}</div> <div>{props.children}</div>
</HotkeysContext.Provider> </HotkeysContext.Provider>
); );

View File

@ -30,21 +30,20 @@ export const PreferencesProvider = (props) => {
const { ipcRenderer } = window; const { ipcRenderer } = window;
useEffect(() => { useEffect(() => {
ipcRenderer ipcRenderer.invoke('renderer:set-preferences', preferences).catch((err) => {
.invoke('renderer:set-preferences', preferences)
.catch(err => {
toast.error(err.message || 'Preferences sync error'); toast.error(err.message || 'Preferences sync error');
}); });
}, [preferences, toast]); }, [preferences, toast]);
const validatedSetPreferences = (newPreferences) => { const validatedSetPreferences = (newPreferences) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
preferencesSchema.validate(newPreferences, { abortEarly: true }) preferencesSchema
.then(validatedPreferences => { .validate(newPreferences, { abortEarly: true })
.then((validatedPreferences) => {
setPreferences(validatedPreferences); setPreferences(validatedPreferences);
resolve(validatedPreferences); resolve(validatedPreferences);
}) })
.catch(error => { .catch((error) => {
let errMsg = error.message || 'Preferences validation error'; let errMsg = error.message || 'Preferences validation error';
toast.error(errMsg); toast.error(errMsg);
reject(error); reject(error);
@ -60,9 +59,7 @@ export const PreferencesProvider = (props) => {
return ( return (
<PreferencesContext.Provider value={value}> <PreferencesContext.Provider value={value}>
<> <>{props.children}</>
{props.children}
</>
</PreferencesContext.Provider> </PreferencesContext.Provider>
); );
}; };

View File

@ -33,6 +33,13 @@ export const appSlice = createSlice({
} }
}); });
export const { idbConnectionReady, refreshScreenWidth, updateLeftSidebarWidth, updateIsDragging, showHomePage, hideHomePage } = appSlice.actions; export const {
idbConnectionReady,
refreshScreenWidth,
updateLeftSidebarWidth,
updateIsDragging,
showHomePage,
hideHomePage
} = appSlice.actions;
export default appSlice.reducer; export default appSlice.reducer;

View File

@ -56,10 +56,7 @@ export const renameCollection = (newName, collectionUid) => (dispatch, getState)
return reject(new Error('Collection not found')); return reject(new Error('Collection not found'));
} }
ipcRenderer ipcRenderer.invoke('renderer:rename-collection', newName, collection.pathname).then(resolve).catch(reject);
.invoke('renderer:rename-collection', newName, collection.pathname)
.then(resolve)
.catch(reject);
}); });
}; };
@ -123,7 +120,7 @@ export const sendRequest = (item, collectionUid) => (dispatch, getState) => {
}) })
); );
if(err && err.message === "Error invoking remote method 'send-http-request': Error: Request cancelled") { if (err && err.message === "Error invoking remote method 'send-http-request': Error: Request cancelled") {
console.log('>> request cancelled'); console.log('>> request cancelled');
return; return;
} }
@ -166,12 +163,21 @@ export const runCollectionFolder = (collectionUid, folderUid, recursive) => (dis
const environment = findEnvironmentInCollection(collectionCopy, collection.activeEnvironmentUid); const environment = findEnvironmentInCollection(collectionCopy, collection.activeEnvironmentUid);
dispatch(resetRunResults({ dispatch(
resetRunResults({
collectionUid: collection.uid collectionUid: collection.uid
})); })
);
ipcRenderer ipcRenderer
.invoke('renderer:run-collection-folder', folder, collectionCopy, environment, collectionCopy.collectionVariables, recursive) .invoke(
'renderer:run-collection-folder',
folder,
collectionCopy,
environment,
collectionCopy.collectionVariables,
recursive
)
.then(resolve) .then(resolve)
.catch((err) => { .catch((err) => {
toast.error(get(err, 'error.message') || 'Something went wrong!'); toast.error(get(err, 'error.message') || 'Something went wrong!');
@ -190,7 +196,10 @@ export const newFolder = (folderName, collectionUid, itemUid) => (dispatch, getS
} }
if (!itemUid) { if (!itemUid) {
const folderWithSameNameExists = find(collection.items, (i) => i.type === 'folder' && trim(i.name) === trim(folderName)); const folderWithSameNameExists = find(
collection.items,
(i) => i.type === 'folder' && trim(i.name) === trim(folderName)
);
if (!folderWithSameNameExists) { if (!folderWithSameNameExists) {
const fullName = `${collection.pathname}${PATH_SEPARATOR}${folderName}`; const fullName = `${collection.pathname}${PATH_SEPARATOR}${folderName}`;
const { ipcRenderer } = window; const { ipcRenderer } = window;
@ -205,7 +214,10 @@ export const newFolder = (folderName, collectionUid, itemUid) => (dispatch, getS
} else { } else {
const currentItem = findItemInCollection(collection, itemUid); const currentItem = findItemInCollection(collection, itemUid);
if (currentItem) { if (currentItem) {
const folderWithSameNameExists = find(currentItem.items, (i) => i.type === 'folder' && trim(i.name) === trim(folderName)); const folderWithSameNameExists = find(
currentItem.items,
(i) => i.type === 'folder' && trim(i.name) === trim(folderName)
);
if (!folderWithSameNameExists) { if (!folderWithSameNameExists) {
const fullName = `${currentItem.pathname}${PATH_SEPARATOR}${folderName}`; const fullName = `${currentItem.pathname}${PATH_SEPARATOR}${folderName}`;
const { ipcRenderer } = window; const { ipcRenderer } = window;
@ -250,10 +262,7 @@ export const renameItem = (newName, itemUid, collectionUid) => (dispatch, getSta
} }
const { ipcRenderer } = window; const { ipcRenderer } = window;
ipcRenderer ipcRenderer.invoke('renderer:rename-item', item.pathname, newPathname, newName).then(resolve).catch(reject);
.invoke('renderer:rename-item', item.pathname, newPathname, newName)
.then(resolve)
.catch(reject);
}); });
}; };
@ -280,12 +289,15 @@ export const cloneItem = (newName, itemUid, collectionUid) => (dispatch, getStat
const itemToSave = refreshUidsInItem(transformRequestToSaveToFilesystem(item)); const itemToSave = refreshUidsInItem(transformRequestToSaveToFilesystem(item));
itemToSave.name = trim(newName); itemToSave.name = trim(newName);
if (!parentItem) { if (!parentItem) {
const reqWithSameNameExists = find(collection.items, (i) => i.type !== 'folder' && trim(i.filename) === trim(filename)); const reqWithSameNameExists = find(
collection.items,
(i) => i.type !== 'folder' && trim(i.filename) === trim(filename)
);
if (!reqWithSameNameExists) { if (!reqWithSameNameExists) {
const fullName = `${collection.pathname}${PATH_SEPARATOR}${filename}`; const fullName = `${collection.pathname}${PATH_SEPARATOR}${filename}`;
const { ipcRenderer } = window; const { ipcRenderer } = window;
const requestItems = filter(collection.items, (i) => i.type !== 'folder'); const requestItems = filter(collection.items, (i) => i.type !== 'folder');
itemToSave.seq = requestItems ? (requestItems.length + 1) : 1; itemToSave.seq = requestItems ? requestItems.length + 1 : 1;
itemSchema itemSchema
.validate(itemToSave) .validate(itemToSave)
@ -296,13 +308,16 @@ export const cloneItem = (newName, itemUid, collectionUid) => (dispatch, getStat
return reject(new Error('Duplicate request names are not allowed under the same folder')); return reject(new Error('Duplicate request names are not allowed under the same folder'));
} }
} else { } else {
const reqWithSameNameExists = find(parentItem.items, (i) => i.type !== 'folder' && trim(i.filename) === trim(filename)); const reqWithSameNameExists = find(
parentItem.items,
(i) => i.type !== 'folder' && trim(i.filename) === trim(filename)
);
if (!reqWithSameNameExists) { if (!reqWithSameNameExists) {
const dirname = getDirectoryName(item.pathname); const dirname = getDirectoryName(item.pathname);
const fullName = path.join(dirname, filename); const fullName = path.join(dirname, filename);
const { ipcRenderer } = window; const { ipcRenderer } = window;
const requestItems = filter(parentItem.items, (i) => i.type !== 'folder'); const requestItems = filter(parentItem.items, (i) => i.type !== 'folder');
itemToSave.seq = requestItems ? (requestItems.length + 1) : 1; itemToSave.seq = requestItems ? requestItems.length + 1 : 1;
itemSchema itemSchema
.validate(itemToSave) .validate(itemToSave)
@ -490,7 +505,7 @@ export const moveItemToRootOfCollection = (collectionUid, draggedItemUid) => (di
const draggedItemPathname = draggedItem.pathname; const draggedItemPathname = draggedItem.pathname;
moveCollectionItemToRootOfCollection(collectionCopy, draggedItem); moveCollectionItemToRootOfCollection(collectionCopy, draggedItem);
if(isItemAFolder(draggedItem)) { if (isItemAFolder(draggedItem)) {
return ipcRenderer return ipcRenderer
.invoke('renderer:move-folder-item', draggedItemPathname, collectionCopy.pathname) .invoke('renderer:move-folder-item', draggedItemPathname, collectionCopy.pathname)
.then(resolve) .then(resolve)
@ -542,7 +557,10 @@ export const newHttpRequest = (params) => (dispatch, getState) => {
// itemUid is null when we are creating a new request at the root level // itemUid is null when we are creating a new request at the root level
const filename = resolveRequestFilename(requestName); const filename = resolveRequestFilename(requestName);
if (!itemUid) { if (!itemUid) {
const reqWithSameNameExists = find(collection.items, (i) => i.type !== 'folder' && trim(i.filename) === trim(filename)); const reqWithSameNameExists = find(
collection.items,
(i) => i.type !== 'folder' && trim(i.filename) === trim(filename)
);
const requestItems = filter(collection.items, (i) => i.type !== 'folder'); const requestItems = filter(collection.items, (i) => i.type !== 'folder');
item.seq = requestItems.length + 1; item.seq = requestItems.length + 1;
@ -557,7 +575,10 @@ export const newHttpRequest = (params) => (dispatch, getState) => {
} else { } else {
const currentItem = findItemInCollection(collection, itemUid); const currentItem = findItemInCollection(collection, itemUid);
if (currentItem) { if (currentItem) {
const reqWithSameNameExists = find(currentItem.items, (i) => i.type !== 'folder' && trim(i.filename) === trim(filename)); const reqWithSameNameExists = find(
currentItem.items,
(i) => i.type !== 'folder' && trim(i.filename) === trim(filename)
);
const requestItems = filter(currentItem.items, (i) => i.type !== 'folder'); const requestItems = filter(currentItem.items, (i) => i.type !== 'folder');
item.seq = requestItems.length + 1; item.seq = requestItems.length + 1;
if (!reqWithSameNameExists) { if (!reqWithSameNameExists) {
@ -583,13 +604,17 @@ export const addEnvironment = (name, collectionUid) => (dispatch, getState) => {
ipcRenderer ipcRenderer
.invoke('renderer:create-environment', collection.pathname, name) .invoke('renderer:create-environment', collection.pathname, name)
.then(dispatch(updateLastAction({ .then(
dispatch(
updateLastAction({
collectionUid, collectionUid,
lastAction: { lastAction: {
type: 'ADD_ENVIRONMENT', type: 'ADD_ENVIRONMENT',
payload: name payload: name
} }
}))) })
)
)
.then(resolve) .then(resolve)
.catch(reject); .catch(reject);
}); });
@ -793,9 +818,6 @@ export const importCollection = (collection, collectionLocation) => (dispatch, g
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const { ipcRenderer } = window; const { ipcRenderer } = window;
ipcRenderer ipcRenderer.invoke('renderer:import-collection', collection, collectionLocation).then(resolve).catch(reject);
.invoke('renderer:import-collection', collection, collectionLocation)
.then(resolve)
.catch(reject);
}); });
}; };

View File

@ -482,7 +482,10 @@ export const collectionsSlice = createSlice({
if (!item.draft) { if (!item.draft) {
item.draft = cloneDeep(item); item.draft = cloneDeep(item);
} }
item.draft.request.body.formUrlEncoded = filter(item.draft.request.body.formUrlEncoded, (p) => p.uid !== action.payload.paramUid); item.draft.request.body.formUrlEncoded = filter(
item.draft.request.body.formUrlEncoded,
(p) => p.uid !== action.payload.paramUid
);
} }
} }
}, },
@ -537,7 +540,10 @@ export const collectionsSlice = createSlice({
if (!item.draft) { if (!item.draft) {
item.draft = cloneDeep(item); item.draft = cloneDeep(item);
} }
item.draft.request.body.multipartForm = filter(item.draft.request.body.multipartForm, (p) => p.uid !== action.payload.paramUid); item.draft.request.body.multipartForm = filter(
item.draft.request.body.multipartForm,
(p) => p.uid !== action.payload.paramUid
);
} }
} }
}, },
@ -729,7 +735,9 @@ export const collectionsSlice = createSlice({
if (!item.draft) { if (!item.draft) {
item.draft = cloneDeep(item); item.draft = cloneDeep(item);
} }
item.draft.request.assertions = item.draft.request.assertions.filter((a) => a.uid !== action.payload.assertUid); item.draft.request.assertions = item.draft.request.assertions.filter(
(a) => a.uid !== action.payload.assertUid
);
} }
} }
}, },
@ -744,7 +752,7 @@ export const collectionsSlice = createSlice({
if (!item.draft) { if (!item.draft) {
item.draft = cloneDeep(item); item.draft = cloneDeep(item);
} }
if(type === 'request') { if (type === 'request') {
item.draft.request.vars = item.draft.request.vars || {}; item.draft.request.vars = item.draft.request.vars || {};
item.draft.request.vars.req = item.draft.request.vars.req || []; item.draft.request.vars.req = item.draft.request.vars.req || [];
item.draft.request.vars.req.push({ item.draft.request.vars.req.push({
@ -754,7 +762,7 @@ export const collectionsSlice = createSlice({
local: false, local: false,
enabled: true enabled: true
}); });
} else if(type === 'response') { } else if (type === 'response') {
item.draft.request.vars = item.draft.request.vars || {}; item.draft.request.vars = item.draft.request.vars || {};
item.draft.request.vars.res = item.draft.request.vars.res || []; item.draft.request.vars.res = item.draft.request.vars.res || [];
item.draft.request.vars.res.push({ item.draft.request.vars.res.push({
@ -779,7 +787,7 @@ export const collectionsSlice = createSlice({
if (!item.draft) { if (!item.draft) {
item.draft = cloneDeep(item); item.draft = cloneDeep(item);
} }
if(type === 'request') { if (type === 'request') {
item.draft.request.vars = item.draft.request.vars || {}; item.draft.request.vars = item.draft.request.vars || {};
item.draft.request.vars.req = item.draft.request.vars.req || []; item.draft.request.vars.req = item.draft.request.vars.req || [];
@ -790,7 +798,7 @@ export const collectionsSlice = createSlice({
reqVar.description = action.payload.var.description; reqVar.description = action.payload.var.description;
reqVar.enabled = action.payload.var.enabled; reqVar.enabled = action.payload.var.enabled;
} }
} else if(type === 'response') { } else if (type === 'response') {
item.draft.request.vars = item.draft.request.vars || {}; item.draft.request.vars = item.draft.request.vars || {};
item.draft.request.vars.res = item.draft.request.vars.res || []; item.draft.request.vars.res = item.draft.request.vars.res || [];
const resVar = find(item.draft.request.vars.res, (v) => v.uid === action.payload.var.uid); const resVar = find(item.draft.request.vars.res, (v) => v.uid === action.payload.var.uid);
@ -815,11 +823,11 @@ export const collectionsSlice = createSlice({
if (!item.draft) { if (!item.draft) {
item.draft = cloneDeep(item); item.draft = cloneDeep(item);
} }
if(type === 'request') { if (type === 'request') {
item.draft.request.vars = item.draft.request.vars || {}; item.draft.request.vars = item.draft.request.vars || {};
item.draft.request.vars.req = item.draft.request.vars.req || []; item.draft.request.vars.req = item.draft.request.vars.req || [];
item.draft.request.vars.req = item.draft.request.vars.req.filter((v) => v.uid !== action.payload.varUid); item.draft.request.vars.req = item.draft.request.vars.req.filter((v) => v.uid !== action.payload.varUid);
} else if(type === 'response') { } else if (type === 'response') {
item.draft.request.vars = item.draft.request.vars || {}; item.draft.request.vars = item.draft.request.vars || {};
item.draft.request.vars.res = item.draft.request.vars.res || []; item.draft.request.vars.res = item.draft.request.vars.res || [];
item.draft.request.vars.res = item.draft.request.vars.res.filter((v) => v.uid !== action.payload.varUid); item.draft.request.vars.res = item.draft.request.vars.res.filter((v) => v.uid !== action.payload.varUid);
@ -922,7 +930,7 @@ export const collectionsSlice = createSlice({
// whenever a user attempts to sort a req within the same folder // whenever a user attempts to sort a req within the same folder
// the seq is updated, but everything else remains the same // the seq is updated, but everything else remains the same
// we don't want to lose the draft in this case // we don't want to lose the draft in this case
if(areItemsTheSameExceptSeqUpdate(item, file.data)) { if (areItemsTheSameExceptSeqUpdate(item, file.data)) {
item.seq = file.data.seq; item.seq = file.data.seq;
} else { } else {
item.name = file.data.name; item.name = file.data.name;
@ -975,9 +983,9 @@ export const collectionsSlice = createSlice({
collection.environments.push(environment); collection.environments.push(environment);
const lastAction = collection.lastAction; const lastAction = collection.lastAction;
if(lastAction && lastAction.type === 'ADD_ENVIRONMENT') { if (lastAction && lastAction.type === 'ADD_ENVIRONMENT') {
collection.lastAction = null; collection.lastAction = null;
if(lastAction.payload === environment.name) { if (lastAction.payload === environment.name) {
collection.activeEnvironmentUid = environment.uid; collection.activeEnvironmentUid = environment.uid;
} }
} }
@ -1031,7 +1039,7 @@ export const collectionsSlice = createSlice({
if (collection) { if (collection) {
const item = findItemInCollection(collection, itemUid); const item = findItemInCollection(collection, itemUid);
if (item) { if (item) {
if(type === 'request-queued') { if (type === 'request-queued') {
const { cancelTokenUid } = action.payload; const { cancelTokenUid } = action.payload;
item.requestUid = requestUid; item.requestUid = requestUid;
item.requestState = 'queued'; item.requestState = 'queued';
@ -1039,24 +1047,24 @@ export const collectionsSlice = createSlice({
item.cancelTokenUid = cancelTokenUid; item.cancelTokenUid = cancelTokenUid;
} }
if(type === 'request-sent') { if (type === 'request-sent') {
const { cancelTokenUid, requestSent } = action.payload; const { cancelTokenUid, requestSent } = action.payload;
item.requestSent = requestSent; item.requestSent = requestSent;
// sometimes the response is received before the request-sent event arrives // sometimes the response is received before the request-sent event arrives
if(item.requestUid === requestUid && item.requestState === 'queued') { if (item.requestUid === requestUid && item.requestState === 'queued') {
item.requestUid = requestUid; item.requestUid = requestUid;
item.requestState = 'sending'; item.requestState = 'sending';
item.cancelTokenUid = cancelTokenUid; item.cancelTokenUid = cancelTokenUid;
} }
} }
if(type === 'assertion-results') { if (type === 'assertion-results') {
const { results } = action.payload; const { results } = action.payload;
item.assertionResults = results; item.assertionResults = results;
} }
if(type === 'test-results') { if (type === 'test-results') {
const { results } = action.payload; const { results } = action.payload;
item.testResults = results; item.testResults = results;
} }
@ -1071,11 +1079,11 @@ export const collectionsSlice = createSlice({
const folder = findItemInCollection(collection, folderUid); const folder = findItemInCollection(collection, folderUid);
const request = findItemInCollection(collection, itemUid); const request = findItemInCollection(collection, itemUid);
collection.runnerResult = collection.runnerResult || {info: {}, items: []}; collection.runnerResult = collection.runnerResult || { info: {}, items: [] };
// todo // todo
// get startedAt and endedAt from the runner and display it in the UI // get startedAt and endedAt from the runner and display it in the UI
if(type === 'testrun-started') { if (type === 'testrun-started') {
const info = collection.runnerResult.info; const info = collection.runnerResult.info;
info.collectionUid = collectionUid; info.collectionUid = collectionUid;
info.folderUid = folderUid; info.folderUid = folderUid;
@ -1083,46 +1091,45 @@ export const collectionsSlice = createSlice({
info.status = 'started'; info.status = 'started';
} }
if(type === 'testrun-ended') { if (type === 'testrun-ended') {
const info = collection.runnerResult.info; const info = collection.runnerResult.info;
info.status = 'ended'; info.status = 'ended';
} }
if (type === 'request-queued') {
if(type === 'request-queued') {
collection.runnerResult.items.push({ collection.runnerResult.items.push({
uid: request.uid, uid: request.uid,
status: 'queued' status: 'queued'
}); });
} }
if(type === 'request-sent') { if (type === 'request-sent') {
const item = collection.runnerResult.items.find((i) => i.uid === request.uid); const item = collection.runnerResult.items.find((i) => i.uid === request.uid);
item.status = 'running'; item.status = 'running';
item.requestSent = action.payload.requestSent; item.requestSent = action.payload.requestSent;
} }
if(type === 'response-received') { if (type === 'response-received') {
const item = collection.runnerResult.items.find((i) => i.uid === request.uid); const item = collection.runnerResult.items.find((i) => i.uid === request.uid);
item.status = 'completed'; item.status = 'completed';
item.responseReceived = action.payload.responseReceived; item.responseReceived = action.payload.responseReceived;
} }
if(type === 'test-results') { if (type === 'test-results') {
const item = collection.runnerResult.items.find((i) => i.uid === request.uid); const item = collection.runnerResult.items.find((i) => i.uid === request.uid);
item.testResults = action.payload.testResults; item.testResults = action.payload.testResults;
} }
if(type === 'assertion-results') { if (type === 'assertion-results') {
const item = collection.runnerResult.items.find((i) => i.uid === request.uid); const item = collection.runnerResult.items.find((i) => i.uid === request.uid);
item.assertionResults = action.payload.assertionResults; item.assertionResults = action.payload.assertionResults;
} }
if(type === 'error') { if (type === 'error') {
const item = collection.runnerResult.items.find((i) => i.uid === request.uid); const item = collection.runnerResult.items.find((i) => i.uid === request.uid);
item.error = action.payload.error; item.error = action.payload.error;
item.responseReceived = action.payload.responseReceived; item.responseReceived = action.payload.responseReceived;
item.status = "error"; item.status = 'error';
} }
} }
}, },

View File

@ -80,6 +80,7 @@ export const tabsSlice = createSlice({
} }
}); });
export const { addTab, focusTab, updateRequestPaneTabWidth, updateRequestPaneTab, updateResponsePaneTab, closeTabs } = tabsSlice.actions; export const { addTab, focusTab, updateRequestPaneTabWidth, updateRequestPaneTab, updateResponsePaneTab, closeTabs } =
tabsSlice.actions;
export default tabsSlice.reducer; export default tabsSlice.reducer;

View File

@ -6,7 +6,7 @@ import { ThemeProvider as SCThemeProvider } from 'styled-components';
export const ThemeContext = createContext(); export const ThemeContext = createContext();
export const ThemeProvider = (props) => { export const ThemeProvider = (props) => {
const isBrowserThemeLight = window.matchMedia("(prefers-color-scheme: light)").matches; const isBrowserThemeLight = window.matchMedia('(prefers-color-scheme: light)').matches;
const [storedTheme, setStoredTheme] = useLocalStorage('bruno.theme', isBrowserThemeLight ? 'light' : 'dark'); const [storedTheme, setStoredTheme] = useLocalStorage('bruno.theme', isBrowserThemeLight ? 'light' : 'dark');
const theme = themes[storedTheme]; const theme = themes[storedTheme];

View File

@ -5,12 +5,10 @@ import { useTheme } from 'providers/Theme';
export const ToastContext = React.createContext(); export const ToastContext = React.createContext();
export const ToastProvider = (props) => { export const ToastProvider = (props) => {
const { const { storedTheme } = useTheme();
storedTheme
} = useTheme();
const toastOptions = { duration: 2000 }; const toastOptions = { duration: 2000 };
if(storedTheme === 'dark') { if (storedTheme === 'dark') {
toastOptions.style = { toastOptions.style = {
borderRadius: '10px', borderRadius: '10px',
background: '#3d3d3d', background: '#3d3d3d',
@ -21,9 +19,7 @@ export const ToastProvider = (props) => {
return ( return (
<ToastContext.Provider {...props} value="toastProvider"> <ToastContext.Provider {...props} value="toastProvider">
<Toaster toastOptions={toastOptions} /> <Toaster toastOptions={toastOptions} />
<div> <div>{props.children}</div>
{props.children}
</div>
</ToastContext.Provider> </ToastContext.Provider>
); );
}; };

View File

@ -20,7 +20,7 @@ const darkTheme = {
bg: 'rgb(48, 48, 49)', bg: 'rgb(48, 48, 49)',
name: { name: {
color: '#569cd6', color: '#569cd6'
} }
}, },

View File

@ -9,7 +9,7 @@ const lightTheme = {
green: '#047857', green: '#047857',
danger: 'rgb(185, 28, 28)', danger: 'rgb(185, 28, 28)',
muted: '#4b5563', muted: '#4b5563',
purple: '#8e44ad', purple: '#8e44ad'
}, },
bg: { bg: {
danger: '#dc3545' danger: '#dc3545'
@ -24,7 +24,7 @@ const lightTheme = {
bg: '#fff', bg: '#fff',
name: { name: {
color: '#546de5', color: '#546de5'
} }
}, },

View File

@ -14,7 +14,7 @@ if (!SERVER_RENDERED) {
const renderVarInfo = (token, options, cm, pos) => { const renderVarInfo = (token, options, cm, pos) => {
const str = token.string || ''; const str = token.string || '';
if(!str || !str.length || typeof str !== 'string') { if (!str || !str.length || typeof str !== 'string') {
return; return;
} }
// str is of format {{variableName}}, extract variableName // str is of format {{variableName}}, extract variableName
@ -32,8 +32,7 @@ if (!SERVER_RENDERED) {
return into; return into;
}; };
CodeMirror.defineOption('brunoVarInfo', false, function(cm, options, old) { CodeMirror.defineOption('brunoVarInfo', false, function (cm, options, old) {
if (old && old !== CodeMirror.Init) { if (old && old !== CodeMirror.Init) {
const oldOnMouseOver = cm.state.brunoVarInfo.onMouseOver; const oldOnMouseOver = cm.state.brunoVarInfo.onMouseOver;
CodeMirror.off(cm.getWrapperElement(), 'mouseover', oldOnMouseOver); CodeMirror.off(cm.getWrapperElement(), 'mouseover', oldOnMouseOver);
@ -50,10 +49,7 @@ if (!SERVER_RENDERED) {
function createState(options) { function createState(options) {
return { return {
options: options: options instanceof Function ? { render: options } : options === true ? {} : options
options instanceof Function
? {render: options}
: options === true ? {} : options,
}; };
} }
@ -70,7 +66,7 @@ if (!SERVER_RENDERED) {
return; return;
} }
if(target.className !== 'cm-variable-valid') { if (target.className !== 'cm-variable-valid') {
return; return;
} }
@ -79,19 +75,19 @@ if (!SERVER_RENDERED) {
const hoverTime = getHoverTime(cm); const hoverTime = getHoverTime(cm);
state.hoverTimeout = setTimeout(onHover, hoverTime); state.hoverTimeout = setTimeout(onHover, hoverTime);
const onMouseMove = function() { const onMouseMove = function () {
clearTimeout(state.hoverTimeout); clearTimeout(state.hoverTimeout);
state.hoverTimeout = setTimeout(onHover, hoverTime); state.hoverTimeout = setTimeout(onHover, hoverTime);
}; };
const onMouseOut = function() { const onMouseOut = function () {
CodeMirror.off(document, 'mousemove', onMouseMove); CodeMirror.off(document, 'mousemove', onMouseMove);
CodeMirror.off(cm.getWrapperElement(), 'mouseout', onMouseOut); CodeMirror.off(cm.getWrapperElement(), 'mouseout', onMouseOut);
clearTimeout(state.hoverTimeout); clearTimeout(state.hoverTimeout);
state.hoverTimeout = undefined; state.hoverTimeout = undefined;
}; };
const onHover = function() { const onHover = function () {
CodeMirror.off(document, 'mousemove', onMouseMove); CodeMirror.off(document, 'mousemove', onMouseMove);
CodeMirror.off(cm.getWrapperElement(), 'mouseout', onMouseOut); CodeMirror.off(cm.getWrapperElement(), 'mouseout', onMouseOut);
state.hoverTimeout = undefined; state.hoverTimeout = undefined;
@ -105,7 +101,7 @@ if (!SERVER_RENDERED) {
function onMouseHover(cm, box) { function onMouseHover(cm, box) {
const pos = cm.coordsChar({ const pos = cm.coordsChar({
left: (box.left + box.right) / 2, left: (box.left + box.right) / 2,
top: (box.top + box.bottom) / 2, top: (box.top + box.bottom) / 2
}); });
const state = cm.state.brunoVarInfo; const state = cm.state.brunoVarInfo;
@ -128,21 +124,12 @@ if (!SERVER_RENDERED) {
const popupBox = popup.getBoundingClientRect(); const popupBox = popup.getBoundingClientRect();
const popupStyle = popup.currentStyle || window.getComputedStyle(popup); const popupStyle = popup.currentStyle || window.getComputedStyle(popup);
const popupWidth = const popupWidth =
popupBox.right - popupBox.right - popupBox.left + parseFloat(popupStyle.marginLeft) + parseFloat(popupStyle.marginRight);
popupBox.left +
parseFloat(popupStyle.marginLeft) +
parseFloat(popupStyle.marginRight);
const popupHeight = const popupHeight =
popupBox.bottom - popupBox.bottom - popupBox.top + parseFloat(popupStyle.marginTop) + parseFloat(popupStyle.marginBottom);
popupBox.top +
parseFloat(popupStyle.marginTop) +
parseFloat(popupStyle.marginBottom);
let topPos = box.bottom; let topPos = box.bottom;
if ( if (popupHeight > window.innerHeight - box.bottom - 15 && box.top > window.innerHeight - box.bottom) {
popupHeight > window.innerHeight - box.bottom - 15 &&
box.top > window.innerHeight - box.bottom
) {
topPos = box.top - popupHeight; topPos = box.top - popupHeight;
} }
@ -166,23 +153,23 @@ if (!SERVER_RENDERED) {
let popupTimeout; let popupTimeout;
const onMouseOverPopup = function() { const onMouseOverPopup = function () {
clearTimeout(popupTimeout); clearTimeout(popupTimeout);
}; };
const onMouseOut = function() { const onMouseOut = function () {
clearTimeout(popupTimeout); clearTimeout(popupTimeout);
popupTimeout = setTimeout(hidePopup, 200); popupTimeout = setTimeout(hidePopup, 200);
}; };
const hidePopup = function() { const hidePopup = function () {
CodeMirror.off(popup, 'mouseover', onMouseOverPopup); CodeMirror.off(popup, 'mouseover', onMouseOverPopup);
CodeMirror.off(popup, 'mouseout', onMouseOut); CodeMirror.off(popup, 'mouseout', onMouseOut);
CodeMirror.off(cm.getWrapperElement(), 'mouseout', onMouseOut); CodeMirror.off(cm.getWrapperElement(), 'mouseout', onMouseOut);
if (popup.style.opacity) { if (popup.style.opacity) {
popup.style.opacity = 0; popup.style.opacity = 0;
setTimeout(function() { setTimeout(function () {
if (popup.parentNode) { if (popup.parentNode) {
popup.parentNode.removeChild(popup); popup.parentNode.removeChild(popup);
} }

View File

@ -32,11 +32,11 @@ const transformItem = (items = []) => {
item.request.query = item.request.params; item.request.query = item.request.params;
delete item.request.params; delete item.request.params;
if(item.type === 'graphql-request') { if (item.type === 'graphql-request') {
item.type = 'graphql'; item.type = 'graphql';
} }
if(item.type === 'http-request') { if (item.type === 'http-request') {
item.type = 'http'; item.type = 'http';
} }
} }
@ -61,7 +61,6 @@ const exportCollection = (collection) => {
deleteUidsInEnvs(collection.environments); deleteUidsInEnvs(collection.environments);
transformItem(collection.items); transformItem(collection.items);
const fileName = `${collection.name}.json`; const fileName = `${collection.name}.json`;
const fileBlob = new Blob([JSON.stringify(collection, null, 2)], { type: 'application/json' }); const fileBlob = new Blob([JSON.stringify(collection, null, 2)], { type: 'application/json' });

View File

@ -158,13 +158,13 @@ export const moveCollectionItemToRootOfCollection = (collection, draggedItem) =>
let draggedItemParent = findParentItemInCollection(collection, draggedItem.uid); let draggedItemParent = findParentItemInCollection(collection, draggedItem.uid);
// If the dragged item is already at the root of the collection, do nothing // If the dragged item is already at the root of the collection, do nothing
if(!draggedItemParent) { if (!draggedItemParent) {
return; return;
} }
draggedItemParent.items = filter(draggedItemParent.items, (i) => i.uid !== draggedItem.uid); draggedItemParent.items = filter(draggedItemParent.items, (i) => i.uid !== draggedItem.uid);
collection.items.push(draggedItem); collection.items.push(draggedItem);
if(draggedItem.type == 'folder') { if (draggedItem.type == 'folder') {
draggedItem.pathname = path.join(collection.pathname, draggedItem.name); draggedItem.pathname = path.join(collection.pathname, draggedItem.name);
} else { } else {
draggedItem.pathname = path.join(collection.pathname, draggedItem.filename); draggedItem.pathname = path.join(collection.pathname, draggedItem.filename);
@ -174,10 +174,10 @@ export const moveCollectionItemToRootOfCollection = (collection, draggedItem) =>
export const getItemsToResequence = (parent, collection) => { export const getItemsToResequence = (parent, collection) => {
let itemsToResequence = []; let itemsToResequence = [];
if(!parent) { if (!parent) {
let index = 1; let index = 1;
each(collection.items, (item) => { each(collection.items, (item) => {
if(isItemARequest(item)) { if (isItemARequest(item)) {
itemsToResequence.push({ itemsToResequence.push({
pathname: item.pathname, pathname: item.pathname,
seq: index++ seq: index++
@ -190,7 +190,7 @@ export const getItemsToResequence = (parent, collection) => {
if (parent.items && parent.items.length) { if (parent.items && parent.items.length) {
let index = 1; let index = 1;
each(parent.items, (item) => { each(parent.items, (item) => {
if(isItemARequest(item)) { if (isItemARequest(item)) {
itemsToResequence.push({ itemsToResequence.push({
pathname: item.pathname, pathname: item.pathname,
seq: index++ seq: index++
@ -499,11 +499,11 @@ export const areItemsTheSameExceptSeqUpdate = (_item1, _item2) => {
}; };
export const getDefaultRequestPaneTab = (item) => { export const getDefaultRequestPaneTab = (item) => {
if(item.type === 'http-request') { if (item.type === 'http-request') {
return 'params'; return 'params';
} }
if(item.type === 'graphql-request') { if (item.type === 'graphql-request') {
return 'query'; return 'query';
} }
}; };
@ -514,7 +514,7 @@ export const getEnvironmentVariables = (collection) => {
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid); const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
if (environment) { if (environment) {
each(environment.variables, (variable) => { each(environment.variables, (variable) => {
if(variable.name && variable.value && variable.enabled) { if (variable.name && variable.value && variable.enabled) {
variables[variable.name] = variable.value; variables[variable.name] = variable.value;
} }
}); });
@ -522,7 +522,7 @@ export const getEnvironmentVariables = (collection) => {
} }
return variables; return variables;
} };
export const getTotalRequestCountInCollection = (collection) => { export const getTotalRequestCountInCollection = (collection) => {
let count = 0; let count = 0;
@ -544,4 +544,4 @@ export const getAllVariables = (collection) => {
...environmentVariables, ...environmentVariables,
...collection.collectionVariables ...collection.collectionVariables
}; };
} };

Some files were not shown because too many files have changed in this diff Show More