feat: prettier config

This commit is contained in:
Anoop M D 2022-10-20 15:09:30 +05:30
parent 93544f8ae6
commit ba219d66db
160 changed files with 3029 additions and 2830 deletions

View File

@ -0,0 +1,7 @@
{
"trailingComma": "none",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"printWidth": 180
}

View File

@ -5,16 +5,17 @@
"dev": "next dev", "dev": "next dev",
"build": "next build && next export", "build": "next build && next export",
"start": "next start", "start": "next start",
"lint": "next lint" "lint": "next lint",
"prettier": "prettier --write \"./src/**/*.{js,jsx,json,ts,tsx}\""
}, },
"dependencies": { "dependencies": {
"@usebruno/schema": "0.1.0",
"@fortawesome/fontawesome-svg-core": "^1.2.36", "@fortawesome/fontawesome-svg-core": "^1.2.36",
"@fortawesome/free-solid-svg-icons": "^5.15.4", "@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/react-fontawesome": "^0.1.16", "@fortawesome/react-fontawesome": "^0.1.16",
"@reduxjs/toolkit": "^1.8.0", "@reduxjs/toolkit": "^1.8.0",
"@tabler/icons": "^1.46.0", "@tabler/icons": "^1.46.0",
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",
"@usebruno/schema": "0.1.0",
"axios": "^0.26.0", "axios": "^0.26.0",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"codemirror": "^5.65.2", "codemirror": "^5.65.2",
@ -57,6 +58,7 @@
"html-loader": "^3.0.1", "html-loader": "^3.0.1",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"mini-css-extract-plugin": "^2.4.5", "mini-css-extract-plugin": "^2.4.5",
"prettier": "^2.7.1",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"webpack": "^5.64.4", "webpack": "^5.64.4",
"webpack-cli": "^4.9.1" "webpack-cli": "^4.9.1"

View File

@ -6,17 +6,18 @@ const AuthApi = {
signup: (params) => post('auth/v1/user/signup', params), signup: (params) => post('auth/v1/user/signup', params),
login: (params) => { login: (params) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const { ipcRenderer } = window.require("electron"); const { ipcRenderer } = window.require('electron');
ipcRenderer.invoke('bruno-account-request', { ipcRenderer
data: params, .invoke('bruno-account-request', {
method: 'POST', data: params,
url: `${process.env.NEXT_PUBLIC_BRUNO_SERVER_API}/auth/v1/user/login`, method: 'POST',
}) url: `${process.env.NEXT_PUBLIC_BRUNO_SERVER_API}/auth/v1/user/login`
.then(resolve) })
.catch(reject); .then(resolve)
.catch(reject);
}); });
} }
}; };
export default AuthApi; export default AuthApi;

View File

@ -1,22 +1,25 @@
import axios from "axios"; import axios from 'axios';
const apiClient = axios.create({ const apiClient = axios.create({
baseURL: process.env.NEXT_PUBLIC_GRAFNODE_SERVER_API baseURL: process.env.NEXT_PUBLIC_GRAFNODE_SERVER_API
}); });
apiClient.interceptors.request.use((config) => { apiClient.interceptors.request.use(
const headers = { (config) => {
'Content-Type': 'application/json' const headers = {
}; 'Content-Type': 'application/json'
};
return ({ return {
...config, ...config,
headers: headers headers: headers
}); };
}, error => Promise.reject(error)); },
(error) => Promise.reject(error)
);
apiClient.interceptors.response.use((response) => apiClient.interceptors.response.use(
response, (response) => response,
async (error) => { async (error) => {
return Promise.reject(error.response ? error.response.data : error); return Promise.reject(error.response ? error.response.data : error);
} }

View File

@ -1,30 +1,94 @@
import React from 'react'; import React from 'react';
const Bruno = ({width}) => { const Bruno = ({ width }) => {
return ( return (
<svg id="emoji" width={width} viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg"> <svg id="emoji" width={width} viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
<g id="color"> <g id="color">
<path fill="#F4AA41" stroke="none" d="M23.5,14.5855l-4.5,1.75l-7.25,8.5l-4.5,10.75l2,5.25c1.2554,3.7911,3.5231,7.1832,7.25,10l2.5-3.3333 c0,0,3.8218,7.7098,10.7384,8.9598c0,0,10.2616,1.936,15.5949-0.8765c3.4203-1.8037,4.4167-4.4167,4.4167-4.4167l3.4167-3.4167 l1.5833,2.3333l2.0833-0.0833l5.4167-7.25L64,37.3355l-0.1667-4.5l-2.3333-5.5l-4.8333-7.4167c0,0-2.6667-4.9167-8.1667-3.9167 c0,0-6.5-4.8333-11.8333-4.0833S32.0833,10.6688,23.5,14.5855z"/> <path
<polygon fill="#EA5A47" 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"/> fill="#F4AA41"
<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"/> stroke="none"
d="M23.5,14.5855l-4.5,1.75l-7.25,8.5l-4.5,10.75l2,5.25c1.2554,3.7911,3.5231,7.1832,7.25,10l2.5-3.3333 c0,0,3.8218,7.7098,10.7384,8.9598c0,0,10.2616,1.936,15.5949-0.8765c3.4203-1.8037,4.4167-4.4167,4.4167-4.4167l3.4167-3.4167 l1.5833,2.3333l2.0833-0.0833l5.4167-7.25L64,37.3355l-0.1667-4.5l-2.3333-5.5l-4.8333-7.4167c0,0-2.6667-4.9167-8.1667-3.9167 c0,0-6.5-4.8333-11.8333-4.0833S32.0833,10.6688,23.5,14.5855z"
/>
<polygon
fill="#EA5A47"
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"
/>
<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" />
<g id="skin-shadow"/> <g id="skin-shadow" />
<g id="line"> <g id="line">
<path fill="#000000" stroke="none" d="M29.5059,30.1088c0,0-1.8051,1.2424-2.7484,0.6679c-0.9434-0.5745-1.2424-1.8051-0.6679-2.7484 s1.805-1.2424,2.7484-0.6679S29.5059,30.1088,29.5059,30.1088z"/> <path
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M33.1089,37.006h6.1457c0.4011,0,0.7634,0.2397,0.9203,0.6089l1.1579,2.7245l-2.1792,1.1456 c-0.6156,0.3236-1.3654-0.0645-1.4567-0.754"/> fill="#000000"
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M34.7606,40.763c-0.1132,0.6268-0.7757,0.9895-1.3647,0.7471l-2.3132-0.952l1.0899-2.9035 c0.1465-0.3901,0.5195-0.6486,0.9362-0.6486"/> stroke="none"
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M30.4364,50.0268c0,0-0.7187,8.7934,3.0072,9.9375c2.6459,0.8125,5.1497,0.5324,6.0625-0.25 c0.875-0.75,2.6323-4.4741,1.8267-9.6875"/> d="M29.5059,30.1088c0,0-1.8051,1.2424-2.7484,0.6679c-0.9434-0.5745-1.2424-1.8051-0.6679-2.7484 s1.805-1.2424,2.7484-0.6679S29.5059,30.1088,29.5059,30.1088z"
<path fill="#000000" stroke="none" d="M44.2636,30.1088c0,0,1.805,1.2424,2.7484,0.6679c0.9434-0.5745,1.2424-1.8051,0.6679-2.7484 c-0.5745-0.9434-1.805-1.2424-2.7484-0.6679C43.9881,27.9349,44.2636,30.1088,44.2636,30.1088z"/> />
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M25.6245,42.8393c-0.475,3.6024,2.2343,5.7505,4.2847,6.8414c1.1968,0.6367,2.6508,0.5182,3.7176-0.3181l2.581-2.0233l2.581,2.0233 c1.0669,0.8363,2.5209,0.9548,3.7176,0.3181c2.0504-1.0909,4.7597-3.239,4.2847-6.8414"/> <path
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M19.9509,28.3572c-2.3166,5.1597-0.5084,13.0249,0.119,15.3759c0.122,0.4571,0.0755,0.9355-0.1271,1.3631l-1.9874,4.1937 c-0.623,1.3146-2.3934,1.5533-3.331,0.4409c-3.1921-3.7871-8.5584-11.3899-6.5486-16.686 c7.0625-18.6104,15.8677-18.1429,15.8677-18.1429c2.8453-1.9336,13.1042-6.9375,24.8125,0.875c0,0,8.6323-1.7175,14.9375,16.9375 c1.8036,5.3362-3.4297,12.8668-6.5506,16.6442c-0.9312,1.127-2.7162,0.8939-3.3423-0.4272l-1.9741-4.1656 c-0.2026-0.4275-0.2491-0.906-0.1271-1.3631c0.6275-2.3509,2.4356-10.2161,0.119-15.3759"/> fill="none"
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M52.6309,46.4628c0,0-3.0781,6.7216-7.8049,8.2712"/> stroke="#000000"
<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"/> strokeLinecap="round"
<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"/> strokeLinejoin="round"
strokeMiterlimit="10"
strokeWidth="2"
d="M33.1089,37.006h6.1457c0.4011,0,0.7634,0.2397,0.9203,0.6089l1.1579,2.7245l-2.1792,1.1456 c-0.6156,0.3236-1.3654-0.0645-1.4567-0.754"
/>
<path
fill="none"
stroke="#000000"
strokeLinecap="round"
strokeLinejoin="round"
strokeMiterlimit="10"
strokeWidth="2"
d="M34.7606,40.763c-0.1132,0.6268-0.7757,0.9895-1.3647,0.7471l-2.3132-0.952l1.0899-2.9035 c0.1465-0.3901,0.5195-0.6486,0.9362-0.6486"
/>
<path
fill="none"
stroke="#000000"
strokeLinecap="round"
strokeLinejoin="round"
strokeMiterlimit="10"
strokeWidth="2"
d="M30.4364,50.0268c0,0-0.7187,8.7934,3.0072,9.9375c2.6459,0.8125,5.1497,0.5324,6.0625-0.25 c0.875-0.75,2.6323-4.4741,1.8267-9.6875"
/>
<path
fill="#000000"
stroke="none"
d="M44.2636,30.1088c0,0,1.805,1.2424,2.7484,0.6679c0.9434-0.5745,1.2424-1.8051,0.6679-2.7484 c-0.5745-0.9434-1.805-1.2424-2.7484-0.6679C43.9881,27.9349,44.2636,30.1088,44.2636,30.1088z"
/>
<path
fill="none"
stroke="#000000"
strokeLinecap="round"
strokeLinejoin="round"
strokeMiterlimit="10"
strokeWidth="2"
d="M25.6245,42.8393c-0.475,3.6024,2.2343,5.7505,4.2847,6.8414c1.1968,0.6367,2.6508,0.5182,3.7176-0.3181l2.581-2.0233l2.581,2.0233 c1.0669,0.8363,2.5209,0.9548,3.7176,0.3181c2.0504-1.0909,4.7597-3.239,4.2847-6.8414"
/>
<path
fill="none"
stroke="#000000"
strokeLinecap="round"
strokeLinejoin="round"
strokeMiterlimit="10"
strokeWidth="2"
d="M19.9509,28.3572c-2.3166,5.1597-0.5084,13.0249,0.119,15.3759c0.122,0.4571,0.0755,0.9355-0.1271,1.3631l-1.9874,4.1937 c-0.623,1.3146-2.3934,1.5533-3.331,0.4409c-3.1921-3.7871-8.5584-11.3899-6.5486-16.686 c7.0625-18.6104,15.8677-18.1429,15.8677-18.1429c2.8453-1.9336,13.1042-6.9375,24.8125,0.875c0,0,8.6323-1.7175,14.9375,16.9375 c1.8036,5.3362-3.4297,12.8668-6.5506,16.6442c-0.9312,1.127-2.7162,0.8939-3.3423-0.4272l-1.9741-4.1656 c-0.2026-0.4275-0.2491-0.906-0.1271-1.3631c0.6275-2.3509,2.4356-10.2161,0.119-15.3759"
/>
<path
fill="none"
stroke="#000000"
strokeLinecap="round"
strokeLinejoin="round"
strokeMiterlimit="10"
strokeWidth="2"
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" />
<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>
) );
}; };
export default Bruno; export default Bruno;

View File

@ -1,40 +1,36 @@
import React from "react"; import React from 'react';
import Modal from "components/Modal/index"; import Modal from 'components/Modal/index';
import {IconSpeakerphone, IconBrandTwitter} from "@tabler/icons"; import { IconSpeakerphone, IconBrandTwitter } from '@tabler/icons';
import StyledWrapper from "./StyledWrapper"; import StyledWrapper from './StyledWrapper';
import GithubSvg from 'assets/github.svg'; import GithubSvg from 'assets/github.svg';
const BrunoSupport = ({onClose}) => { const BrunoSupport = ({ onClose }) => {
return ( return (
<StyledWrapper> <StyledWrapper>
<Modal <Modal size="sm" title={'Support'} handleCancel={onClose} hideFooter={true}>
size="sm"
title={"Support"}
handleCancel={onClose}
hideFooter={true}
>
<div className="collection-options"> <div className="collection-options">
<div className="mt-2"> <div className="mt-2">
<a href="https://github.com/usebruno/bruno/issues" target="_blank" className="flex items-center"> <a href="https://github.com/usebruno/bruno/issues" target="_blank" className="flex items-center">
<IconSpeakerphone size={18} strokeWidth={2}/><span className="label ml-2">Report Issues</span> <IconSpeakerphone size={18} strokeWidth={2} />
<span className="label ml-2">Report Issues</span>
</a> </a>
</div> </div>
<div className="mt-2"> <div className="mt-2">
<a href="https://github.com/usebruno/bruno" target="_blank" className="flex items-center"> <a href="https://github.com/usebruno/bruno" target="_blank" className="flex items-center">
<img src={GithubSvg.src} style={{width: "18px"}}/> <img src={GithubSvg.src} style={{ width: '18px' }} />
<span className="label ml-2">Github</span> <span className="label ml-2">Github</span>
</a> </a>
</div> </div>
<div className="mt-2"> <div className="mt-2">
<a href="https://twitter.com/use_bruno" target="_blank" className="flex items-center"> <a href="https://twitter.com/use_bruno" target="_blank" className="flex items-center">
<IconBrandTwitter size={18} strokeWidth={2}/><span className="label ml-2">Twitter</span> <IconBrandTwitter size={18} strokeWidth={2} />
<span className="label ml-2">Twitter</span>
</a> </a>
</div> </div>
</div> </div>
</Modal> </Modal>
</StyledWrapper> </StyledWrapper>
); );
} };
export default BrunoSupport; export default BrunoSupport;

View File

@ -11,4 +11,3 @@ const StyledWrapper = styled.div`
`; `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -37,33 +37,33 @@ export default class QueryEditor extends React.Component {
matchBrackets: true, matchBrackets: true,
showCursorWhenSelecting: true, showCursorWhenSelecting: true,
foldGutter: true, foldGutter: true,
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
readOnly: this.props.readOnly ? 'nocursor' : false, readOnly: this.props.readOnly ? 'nocursor' : false,
extraKeys: { extraKeys: {
'Cmd-Enter': () => { 'Cmd-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-S': () => { 'Cmd-S': () => {
if(this.props.onSave) { if (this.props.onSave) {
this.props.onSave(); this.props.onSave();
} }
}, },
'Ctrl-S': () => { 'Ctrl-S': () => {
if(this.props.onSave) { if (this.props.onSave) {
this.props.onSave(); this.props.onSave();
} }
}, },
'Tab': function(cm){ Tab: function (cm) {
cm.replaceSelection(" " , "end"); cm.replaceSelection(' ', 'end');
} }
}, }
})); }));
if (editor) { if (editor) {
editor.on('change', this._onEdit); editor.on('change', this._onEdit);
@ -82,14 +82,10 @@ export default class QueryEditor extends React.Component {
this.editor.options.jump.schema = this.props.schema; this.editor.options.jump.schema = this.props.schema;
CodeMirror.signal(this.editor, 'change', this.editor); CodeMirror.signal(this.editor, 'change', this.editor);
} }
if ( if (this.props.value !== prevProps.value && this.props.value !== this.cachedValue && this.editor) {
this.props.value !== prevProps.value &&
this.props.value !== this.cachedValue &&
this.editor
) {
this.cachedValue = this.props.value; this.cachedValue = this.props.value;
this.editor.setValue(this.props.value); this.editor.setValue(this.props.value);
this.editor.setOption("mode", this.props.mode); this.editor.setOption('mode', this.props.mode);
} }
this.ignoreChangeEvent = false; this.ignoreChangeEvent = false;
} }
@ -106,7 +102,7 @@ export default class QueryEditor extends React.Component {
<StyledWrapper <StyledWrapper
className="h-full" className="h-full"
aria-label="Code Editor" aria-label="Code Editor"
ref={node => { ref={(node) => {
this._node = node; this._node = node;
}} }}
/> />

View File

@ -24,13 +24,13 @@ const Wrapper = styled.div`
.label-item { .label-item {
display: flex; display: flex;
align-items: center; align-items: center;
padding: .35rem .6rem; padding: 0.35rem 0.6rem;
} }
.dropdown-item { .dropdown-item {
display: flex; display: flex;
align-items: center; align-items: center;
padding: .35rem .6rem; padding: 0.35rem 0.6rem;
cursor: pointer; cursor: pointer;
&:hover { &:hover {

View File

@ -2,19 +2,10 @@ import React from 'react';
import Tippy from '@tippyjs/react'; import Tippy from '@tippyjs/react';
import StyledWrapper from './StyledWrapper'; 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 <Tippy content={children} placement={placement || 'bottom-end'} animation={false} arrow={false} onCreate={onCreate} interactive={true} trigger="click" appendTo="parent">
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

@ -3,68 +3,77 @@ import find from 'lodash/find';
import Dropdown from 'components/Dropdown'; import Dropdown from 'components/Dropdown';
import { selectEnvironment } from 'providers/ReduxStore/slices/collections/actions'; import { selectEnvironment } from 'providers/ReduxStore/slices/collections/actions';
import { IconSettings, IconCaretDown, IconDatabase } from '@tabler/icons'; import { IconSettings, IconCaretDown, IconDatabase } from '@tabler/icons';
import EnvironmentSettings from "../EnvironmentSettings"; import EnvironmentSettings from '../EnvironmentSettings';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const EnvironmentSelector = ({collection}) => { const EnvironmentSelector = ({ collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const dropdownTippyRef = useRef(); const dropdownTippyRef = useRef();
const [openSettingsModal, setOpenSettingsModal] = useState(false); const [openSettingsModal, setOpenSettingsModal] = useState(false);
const { environments, activeEnvironmentUid } = collection; const { environments, activeEnvironmentUid } = collection;
const activeEnvironment = activeEnvironmentUid ? find(environments, e => e.uid === activeEnvironmentUid) : null; const activeEnvironment = activeEnvironmentUid ? find(environments, (e) => e.uid === activeEnvironmentUid) : null;
const Icon = forwardRef((props, ref) => { const Icon = forwardRef((props, ref) => {
return ( return (
<div ref={ref} className="current-enviroment flex items-center justify-center pl-3 pr-2 py-1 select-none"> <div ref={ref} className="current-enviroment flex items-center justify-center pl-3 pr-2 py-1 select-none">
{activeEnvironment ? activeEnvironment.name : 'No Environment'} {activeEnvironment ? activeEnvironment.name : 'No Environment'}
<IconCaretDown className="caret" size={14} strokeWidth={2}/> <IconCaretDown className="caret" size={14} strokeWidth={2} />
</div> </div>
); );
}); });
const onDropdownCreate = (ref) => dropdownTippyRef.current = ref; const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const onSelect = (environment) => { const onSelect = (environment) => {
dispatch(selectEnvironment(environment ? environment.uid : null, collection.uid)) dispatch(selectEnvironment(environment ? environment.uid : null, collection.uid))
.then(() => { .then(() => {
if(environment) { if (environment) {
toast.success(`Environment changed to ${environment.name}`); toast.success(`Environment changed to ${environment.name}`);
} else { } else {
toast.success(`No Environments are active now`); toast.success(`No Environments are active now`);
} }
}) })
.catch((err) => console.log(err) && toast.error("An error occured while selecting the environment")); .catch((err) => console.log(err) && toast.error('An error occured while selecting the environment'));
}; };
return ( return (
<StyledWrapper> <StyledWrapper>
<div className="flex items-center cursor-pointer environment-selector"> <div className="flex items-center cursor-pointer environment-selector">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement='bottom-end'> <Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
{(environments && environments.length) ? environments.map((e) => ( {environments && environments.length
<div className="dropdown-item" key={e.uid} onClick={() => { ? environments.map((e) => (
onSelect(e); <div
className="dropdown-item"
key={e.uid}
onClick={() => {
onSelect(e);
dropdownTippyRef.current.hide();
}}
>
<IconDatabase size={18} strokeWidth={1.5} /> <span className="ml-2">{e.name}</span>
</div>
))
: null}
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide(); dropdownTippyRef.current.hide();
}}> onSelect(null);
<IconDatabase size={18} strokeWidth={1.5}/> <span className="ml-2">{e.name}</span> }}
</div> >
)) : null}
<div className="dropdown-item" onClick={() => {
dropdownTippyRef.current.hide();
onSelect(null);
}}>
<span>No Environment</span> <span>No Environment</span>
</div> </div>
<div className="dropdown-item" style={{borderTop: 'solid 1px #e7e7e7'}} onClick={() => setOpenSettingsModal(true)}> <div className="dropdown-item" style={{ borderTop: 'solid 1px #e7e7e7' }} onClick={() => setOpenSettingsModal(true)}>
<div className="pr-2 text-gray-600"> <div className="pr-2 text-gray-600">
<IconSettings size={18} strokeWidth={1.5}/> <IconSettings size={18} strokeWidth={1.5} />
</div> </div>
<span>Settings</span> <span>Settings</span>
</div> </div>
</Dropdown> </Dropdown>
</div> </div>
{openSettingsModal && <EnvironmentSettings collection={collection} onClose={() => setOpenSettingsModal(false)}/>} {openSettingsModal && <EnvironmentSettings collection={collection} onClose={() => setOpenSettingsModal(false)} />}
</StyledWrapper> </StyledWrapper>
); );
}; };

View File

@ -1,75 +1,70 @@
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef } from 'react';
import Portal from "components/Portal/index"; import Portal from 'components/Portal/index';
import Modal from "components/Modal/index"; import Modal from 'components/Modal/index';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import { addEnvironment } from 'providers/ReduxStore/slices/collections/actions'; import { addEnvironment } from 'providers/ReduxStore/slices/collections/actions';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
const CreateEnvironment = ({collection, onClose}) => { const CreateEnvironment = ({ collection, onClose }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const inputRef = useRef(); const inputRef = useRef();
const formik = useFormik({ const formik = useFormik({
enableReinitialize: true, enableReinitialize: true,
initialValues: { initialValues: {
name: "" name: ''
}, },
validationSchema: Yup.object({ validationSchema: Yup.object({
name: Yup.string() name: Yup.string().min(1, 'must be atleast 1 characters').max(50, 'must be 50 characters or less').required('name is required')
.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))
.then(() => { .then(() => {
toast.success("Environment created in collection"); toast.success('Environment created in collection');
onClose(); onClose();
}) })
.catch(() => toast.error("An error occured while created the environment")); .catch(() => toast.error('An error occured while created the environment'));
} }
}); });
useEffect(() => { useEffect(() => {
if(inputRef && inputRef.current) { if (inputRef && inputRef.current) {
inputRef.current.focus(); inputRef.current.focus();
} }
}, [inputRef]); }, [inputRef]);
const onSubmit = () => { const onSubmit = () => {
formik.handleSubmit(); formik.handleSubmit();
} };
return ( return (
<Portal> <Portal>
<Modal <Modal size="sm" title={'Create Environment'} confirmText="Create" handleConfirm={onSubmit} handleCancel={onClose}>
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">Environment Name</label> <label htmlFor="name" className="block font-semibold">
Environment Name
</label>
<input <input
id="environment-name" type="text" name="name" id="environment-name"
type="text"
name="name"
ref={inputRef} ref={inputRef}
className="block textbox mt-2 w-full" className="block textbox mt-2 w-full"
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange} onChange={formik.handleChange}
value={formik.values.name || ''} value={formik.values.name || ''}
/> />
{formik.touched.name && formik.errors.name ? ( {formik.touched.name && formik.errors.name ? <div className="text-red-500">{formik.errors.name}</div> : null}
<div className="text-red-500">{formik.errors.name}</div>
) : null}
</div> </div>
</form> </form>
</Modal> </Modal>
</Portal> </Portal>
); );
} };
export default CreateEnvironment; export default CreateEnvironment;

View File

@ -1,39 +1,31 @@
import React from 'react'; import React from 'react';
import Portal from "components/Portal/index"; import Portal from 'components/Portal/index';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import Modal from "components/Modal/index"; import Modal from 'components/Modal/index';
import { deleteEnvironment } from 'providers/ReduxStore/slices/collections/actions'; import { deleteEnvironment } from 'providers/ReduxStore/slices/collections/actions';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const DeleteEnvironment = ({onClose, environment, collection}) => { const DeleteEnvironment = ({ onClose, environment, collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const onConfirm = () =>{ const onConfirm = () => {
dispatch(deleteEnvironment(environment.uid, collection.uid)) dispatch(deleteEnvironment(environment.uid, collection.uid))
.then(() => { .then(() => {
toast.success("Environment deleted successfully"); toast.success('Environment deleted successfully');
onClose(); onClose();
}) })
.catch(() => toast.error("An error occured while deleting the environment")); .catch(() => toast.error('An error occured while deleting the environment'));
}; };
return ( return (
<Portal> <Portal>
<StyledWrapper> <StyledWrapper>
<Modal <Modal size="sm" title={'Delete Environment'} confirmText="Delete" handleConfirm={onConfirm} handleCancel={onClose}>
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>
</Portal> </Portal>
); );
} };
export default DeleteEnvironment; export default DeleteEnvironment;

View File

@ -6,7 +6,8 @@ const Wrapper = styled.div`
border-collapse: collapse; border-collapse: collapse;
font-weight: 600; font-weight: 600;
thead, td { thead,
td {
border: 1px solid #efefef; border: 1px solid #efefef;
} }
@ -24,18 +25,18 @@ const Wrapper = styled.div`
font-size: 0.8125rem; font-size: 0.8125rem;
} }
input[type="text"] { input[type='text'] {
width: 100%; width: 100%;
border: solid 1px transparent; border: solid 1px transparent;
outline: none !important; outline: none !important;
&:focus{ &:focus {
outline: none !important; outline: none !important;
border: solid 1px transparent; border: solid 1px transparent;
} }
} }
input[type="checkbox"] { input[type='checkbox'] {
cursor: pointer; cursor: pointer;
position: relative; position: relative;
top: 1px; top: 1px;

View File

@ -7,23 +7,20 @@ import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions
import reducer from './reducer'; import reducer from './reducer';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const EnvironmentVariables = ({environment, collection}) => { const EnvironmentVariables = ({ environment, collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [state, reducerDispatch] = useReducer(reducer, {hasChanges: false, variables: environment.variables || []}); const [state, reducerDispatch] = useReducer(reducer, { hasChanges: false, variables: environment.variables || [] });
const { const { variables, hasChanges } = state;
variables,
hasChanges
} = state;
const saveChanges = () => { const saveChanges = () => {
dispatch(saveEnvironment(cloneDeep(variables), environment.uid, collection.uid)) dispatch(saveEnvironment(cloneDeep(variables), environment.uid, collection.uid))
.then(() => { .then(() => {
toast.success("Changes saved successfully"); toast.success('Changes saved successfully');
reducerDispatch({ reducerDispatch({
type: 'CHANGES_SAVED' type: 'CHANGES_SAVED'
}); });
}) })
.catch(() => toast.error("An error occured while saving the changes")); .catch(() => toast.error('An error occured while saving the changes'));
}; };
const addVariable = () => { const addVariable = () => {
@ -34,16 +31,16 @@ const EnvironmentVariables = ({environment, collection}) => {
const handleVarChange = (e, _variable, type) => { const handleVarChange = (e, _variable, type) => {
const variable = cloneDeep(_variable); const variable = cloneDeep(_variable);
switch(type) { switch (type) {
case 'name' : { case 'name': {
variable.name = e.target.value; variable.name = e.target.value;
break; break;
} }
case 'value' : { case 'value': {
variable.value = e.target.value; variable.value = e.target.value;
break; break;
} }
case 'enabled' : { case 'enabled': {
variable.enabled = e.target.checked; variable.enabled = e.target.checked;
break; break;
} }
@ -53,14 +50,14 @@ const EnvironmentVariables = ({environment, collection}) => {
variable variable
}); });
}; };
const handleRemoveVars = (variable) => { const handleRemoveVars = (variable) => {
reducerDispatch({ reducerDispatch({
type: 'DELETE_VAR', type: 'DELETE_VAR',
variable variable
}); });
}; };
return ( return (
<StyledWrapper className="w-full mt-6 mb-6"> <StyledWrapper className="w-full mt-6 mb-6">
<table> <table>
@ -72,48 +69,53 @@ const EnvironmentVariables = ({environment, collection}) => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{variables && variables.length ? variables.map((variable, index) => { {variables && variables.length
return ( ? variables.map((variable, index) => {
<tr key={variable.uid}> return (
<td> <tr key={variable.uid}>
<input <td>
type="text" <input
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" type="text"
value={variable.name} autoComplete="off"
className="mousetrap" autoCorrect="off"
onChange={(e) => handleVarChange(e, variable, 'name')} autoCapitalize="off"
/> spellCheck="false"
</td> value={variable.name}
<td> className="mousetrap"
<input onChange={(e) => handleVarChange(e, variable, 'name')}
type="text" />
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" </td>
value={variable.value} <td>
className="mousetrap" <input
onChange={(e) => handleVarChange(e, variable, 'value')} type="text"
/> autoComplete="off"
</td> autoCorrect="off"
<td> autoCapitalize="off"
<div className="flex items-center"> spellCheck="false"
<input value={variable.value}
type="checkbox" className="mousetrap"
checked={variable.enabled} onChange={(e) => handleVarChange(e, variable, 'value')}
className="mr-3 mousetrap" />
onChange={(e) => handleVarChange(e, variable, 'enabled')} </td>
/> <td>
<button onClick={() => handleRemoveVars(variable)}> <div className="flex items-center">
<IconTrash strokeWidth={1.5} size={20}/> <input type="checkbox" checked={variable.enabled} className="mr-3 mousetrap" onChange={(e) => handleVarChange(e, variable, 'enabled')} />
</button> <button onClick={() => handleRemoveVars(variable)}>
</div> <IconTrash strokeWidth={1.5} size={20} />
</td> </button>
</tr> </div>
); </td>
}) : null} </tr>
);
})
: null}
</tbody> </tbody>
</table> </table>
<div> <div>
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={addVariable}>+ Add Variable</button> <button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={addVariable}>
+ Add Variable
</button>
</div> </div>
<div> <div>
@ -122,6 +124,6 @@ const EnvironmentVariables = ({environment, collection}) => {
</button> </button>
</div> </div>
</StyledWrapper> </StyledWrapper>
) );
}; };
export default EnvironmentVariables; export default EnvironmentVariables;

View File

@ -20,7 +20,7 @@ const reducer = (state, action) => {
case 'UPDATE_VAR': { case 'UPDATE_VAR': {
return produce(state, (draft) => { return produce(state, (draft) => {
const variable = find(draft.variables, (v) => v.uid === action.variable.uid); const variable = find(draft.variables, (v) => v.uid === action.variable.uid);
variable.name = action.variable.name; variable.name = action.variable.name;
variable.value = action.variable.value; variable.value = action.variable.value;
variable.enabled = action.variable.enabled; variable.enabled = action.variable.enabled;
@ -47,4 +47,4 @@ const reducer = (state, action) => {
} }
}; };
export default reducer; export default reducer;

View File

@ -1,34 +1,33 @@
import React, {useState } from "react"; import React, { useState } from 'react';
import { IconEdit, IconTrash, IconDatabase } from "@tabler/icons"; import { IconEdit, IconTrash, IconDatabase } from '@tabler/icons';
import EnvironmentVariables from './EnvironmentVariables'; import EnvironmentVariables from './EnvironmentVariables';
import RenameEnvironment from "../../RenameEnvironment"; import RenameEnvironment from '../../RenameEnvironment';
import DeleteEnvironment from "../../DeleteEnvironment"; import DeleteEnvironment from '../../DeleteEnvironment';
const EnvironmentDetails = ({environment, collection}) => { const EnvironmentDetails = ({ environment, collection }) => {
const [ openEditModal, setOpenEditModal] = useState(false); const [openEditModal, setOpenEditModal] = useState(false);
const [ openDeleteModal, setOpenDeleteModal] = useState(false); const [openDeleteModal, setOpenDeleteModal] = useState(false);
console.log(environment); console.log(environment);
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 && <RenameEnvironment onClose={() => setOpenEditModal(false)} environment={environment} collection={collection} />}
{openDeleteModal && <DeleteEnvironment onClose={() => setOpenDeleteModal(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} />
<span className="ml-1 font-semibold">{environment.name}</span> <span className="ml-1 font-semibold">{environment.name}</span>
</div> </div>
<div className="flex gap-x-4 pl-4"> <div className="flex gap-x-4 pl-4">
<IconEdit className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenEditModal(true)}/> <IconEdit className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenEditModal(true)} />
<IconTrash className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenDeleteModal(true)}/> <IconTrash className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenDeleteModal(true)} />
</div> </div>
</div> </div>
<div> <div>
<EnvironmentVariables key={environment.uid} environment={environment} collection={collection}/> <EnvironmentVariables key={environment.uid} environment={environment} collection={collection} />
</div> </div>
</div> </div>
); );
}; };

View File

@ -1,4 +1,4 @@
import styled from "styled-components"; import styled from 'styled-components';
const StyledWrapper = styled.div` const StyledWrapper = styled.div`
margin-inline: -1rem; margin-inline: -1rem;
@ -17,7 +17,7 @@ const StyledWrapper = styled.div`
padding: 8px 10px; padding: 8px 10px;
border-left: solid 2px transparent; border-left: solid 2px transparent;
text-decoration: none; text-decoration: none;
&:hover { &:hover {
text-decoration: none; text-decoration: none;
background-color: #e4e4e4; background-color: #e4e4e4;
@ -46,4 +46,4 @@ const StyledWrapper = styled.div`
} }
`; `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -1,9 +1,9 @@
import React, { useEffect, useState, forwardRef, useRef } from "react"; import React, { useEffect, useState, forwardRef, useRef } from 'react';
import EnvironmentDetails from "./EnvironmentDetails"; import EnvironmentDetails from './EnvironmentDetails';
import CreateEnvironment from "../CreateEnvironment/index"; import CreateEnvironment from '../CreateEnvironment/index';
import StyledWrapper from "./StyledWrapper"; import StyledWrapper from './StyledWrapper';
const EnvironmentList = ({collection}) => { const EnvironmentList = ({ collection }) => {
const { environments } = collection; const { environments } = collection;
const [selectedEnvironment, setSelectedEnvironment] = useState(null); const [selectedEnvironment, setSelectedEnvironment] = useState(null);
const [openCreateModal, setOpenCreateModal] = useState(false); const [openCreateModal, setOpenCreateModal] = useState(false);
@ -12,31 +12,29 @@ const EnvironmentList = ({collection}) => {
setSelectedEnvironment(environments[0]); setSelectedEnvironment(environments[0]);
}, []); }, []);
if(!selectedEnvironment) { if (!selectedEnvironment) {
return null; return null;
} }
return ( return (
<StyledWrapper> <StyledWrapper>
{openCreateModal && <CreateEnvironment collection={collection} onClose={() => setOpenCreateModal(false)}/>} {openCreateModal && <CreateEnvironment collection={collection} onClose={() => setOpenCreateModal(false)} />}
<div className="flex"> <div className="flex">
<div> <div>
<div className="environments-sidebar"> <div className="environments-sidebar">
{environments && environments.length && environments.map((env) => ( {environments &&
<div environments.length &&
key={env.uid} environments.map((env) => (
className={selectedEnvironment.uid === env.uid ? "environment-item active": "environment-item"} <div key={env.uid} className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'} onClick={() => setSelectedEnvironment(env)}>
onClick={() => setSelectedEnvironment(env)} <span>{env.name}</span>
> </div>
<span>{env.name}</span> ))}
</div>
))}
<div className="btn-create-environment" onClick={() => setOpenCreateModal(true)}> <div className="btn-create-environment" onClick={() => setOpenCreateModal(true)}>
+ <span>Create</span> + <span>Create</span>
</div> </div>
</div> </div>
</div> </div>
<EnvironmentDetails environment={selectedEnvironment} collection={collection}/> <EnvironmentDetails environment={selectedEnvironment} collection={collection} />
</div> </div>
</StyledWrapper> </StyledWrapper>
); );

View File

@ -1,75 +1,70 @@
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef } from 'react';
import Portal from "components/Portal/index"; import Portal from 'components/Portal/index';
import Modal from "components/Modal/index"; import Modal from 'components/Modal/index';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import { renameEnvironment } from 'providers/ReduxStore/slices/collections/actions'; import { renameEnvironment } from 'providers/ReduxStore/slices/collections/actions';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
const RenameEnvironment = ({onClose, environment, collection}) => { const RenameEnvironment = ({ onClose, environment, collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const inputRef = useRef(); const inputRef = useRef();
const formik = useFormik({ const formik = useFormik({
enableReinitialize: true, enableReinitialize: true,
initialValues: { initialValues: {
name: environment.name name: environment.name
}, },
validationSchema: Yup.object({ validationSchema: Yup.object({
name: Yup.string() name: Yup.string().min(1, 'must be atleast 1 characters').max(50, 'must be 50 characters or less').required('name is required')
.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))
.then(() => { .then(() => {
toast.success("Environment renamed successfully"); toast.success('Environment renamed successfully');
onClose(); onClose();
}) })
.catch(() => toast.error("An error occured while renaming the environment")); .catch(() => toast.error('An error occured while renaming the environment'));
} }
}); });
useEffect(() => { useEffect(() => {
if(inputRef && inputRef.current) { if (inputRef && inputRef.current) {
inputRef.current.focus(); inputRef.current.focus();
} }
}, [inputRef]); }, [inputRef]);
const onSubmit = () => { const onSubmit = () => {
formik.handleSubmit(); formik.handleSubmit();
} };
return ( return (
<Portal> <Portal>
<Modal <Modal size="sm" title={'Rename Environment'} confirmText="Rename" handleConfirm={onSubmit} handleCancel={onClose}>
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">Environment Name</label> <label htmlFor="name" className="block font-semibold">
Environment Name
</label>
<input <input
id="environment-name" type="text" name="name" id="environment-name"
type="text"
name="name"
ref={inputRef} ref={inputRef}
className="block textbox mt-2 w-full" className="block textbox mt-2 w-full"
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange} onChange={formik.handleChange}
value={formik.values.name || ''} value={formik.values.name || ''}
/> />
{formik.touched.name && formik.errors.name ? ( {formik.touched.name && formik.errors.name ? <div className="text-red-500">{formik.errors.name}</div> : null}
<div className="text-red-500">{formik.errors.name}</div>
) : null}
</div> </div>
</form> </form>
</Modal> </Modal>
</Portal> </Portal>
); );
} };
export default RenameEnvironment; export default RenameEnvironment;

View File

@ -1,4 +1,4 @@
import styled from "styled-components"; import styled from 'styled-components';
const StyledWrapper = styled.div` const StyledWrapper = styled.div`
button.btn-create-environment { button.btn-create-environment {
@ -10,4 +10,4 @@ const StyledWrapper = styled.div`
} }
`; `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -1,50 +1,34 @@
import Modal from "components/Modal/index"; import Modal from 'components/Modal/index';
import React, { useState } from "react"; import React, { useState } from 'react';
import CreateEnvironment from "./CreateEnvironment"; import CreateEnvironment from './CreateEnvironment';
import EnvironmentList from "./EnvironmentList"; import EnvironmentList from './EnvironmentList';
import StyledWrapper from "./StyledWrapper"; import StyledWrapper from './StyledWrapper';
const EnvironmentSettings = ({collection, onClose}) => { const EnvironmentSettings = ({ collection, onClose }) => {
const { environments } = collection; const { environments } = collection;
const [openCreateModal, setOpenCreateModal] = useState(false) const [openCreateModal, setOpenCreateModal] = useState(false);
if(!environments || !environments.length) { if (!environments || !environments.length) {
return ( return (
<StyledWrapper> <StyledWrapper>
<Modal <Modal size="md" title="Environments" confirmText={'Close'} handleConfirm={onClose} handleCancel={onClose} hideCancel={true}>
size="md" {openCreateModal && <CreateEnvironment collection={collection} onClose={() => setOpenCreateModal(false)} />}
title="Environments" <div className="text-center">
confirmText={"Close"} <p>No environments found!</p>
handleConfirm={onClose} <button className="btn-create-environment text-link pr-2 py-3 mt-2 select-none" onClick={() => setOpenCreateModal(true)}>
handleCancel={onClose} + <span>Create Environment</span>
hideCancel={true} </button>
> </div>
{openCreateModal && <CreateEnvironment collection={collection} onClose={() => setOpenCreateModal(false)}/>} </Modal>
<div className="text-center"> </StyledWrapper>
<p>No environments found!</p> );
<button }
className="btn-create-environment text-link pr-2 py-3 mt-2 select-none"
onClick={() => setOpenCreateModal(true)}
>
+ <span>Create Environment</span>
</button>
</div>
</Modal>
</StyledWrapper>
)
}
return ( return (
<Modal <Modal size="lg" title="Environments" handleCancel={onClose} hideFooter={true}>
size="lg" <EnvironmentList collection={collection} />
title="Environments"
handleCancel={onClose}
hideFooter={true}
>
<EnvironmentList collection={collection}/>
</Modal> </Modal>
) );
};
}
export default EnvironmentSettings; export default EnvironmentSettings;

View File

@ -1,11 +1,11 @@
import styled from 'styled-components'; import styled from 'styled-components';
const Wrapper = styled.div` const Wrapper = styled.div`
&.modal--animate-out{ &.modal--animate-out {
animation: fade-out 0.5s forwards cubic-bezier(.19,1,.22,1); animation: fade-out 0.5s forwards cubic-bezier(0.19, 1, 0.22, 1);
.bruno-modal-card { .bruno-modal-card {
animation: fade-and-slide-out-from-top .50s forwards cubic-bezier(.19,1,.22,1); animation: fade-and-slide-out-from-top 0.5s forwards cubic-bezier(0.19, 1, 0.22, 1);
} }
} }
@ -23,8 +23,8 @@ const Wrapper = styled.div`
} }
.bruno-modal-card { .bruno-modal-card {
animation-duration: .85s; animation-duration: 0.85s;
animation-delay: .1s; animation-delay: 0.1s;
background: var(--color-background-top); background: var(--color-background-top);
border-radius: var(--border-radius); border-radius: var(--border-radius);
position: relative; position: relative;
@ -33,7 +33,7 @@ const Wrapper = styled.div`
box-shadow: var(--box-shadow-base); box-shadow: var(--box-shadow-base);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
will-change: opacity,transform; will-change: opacity, transform;
flex-grow: 0; flex-grow: 0;
margin: 3vh 10vw; margin: 3vh 10vw;
margin-top: 50px; margin-top: 50px;
@ -58,7 +58,7 @@ const Wrapper = styled.div`
max-width: calc(100% - 30px); max-width: calc(100% - 30px);
} }
animation: fade-and-slide-in-from-top .50s forwards cubic-bezier(.19,1,.22,1); animation: fade-and-slide-in-from-top 0.5s forwards cubic-bezier(0.19, 1, 0.22, 1);
} }
.bruno-modal-header { .bruno-modal-header {
@ -102,18 +102,18 @@ const Wrapper = styled.div`
will-change: opacity; will-change: opacity;
background: transparent; background: transparent;
&:before{ &:before {
content: ""; content: '';
height: 100%; height: 100%;
width: 100%; width: 100%;
left: 0; left: 0;
opacity: .4; opacity: 0.4;
top: 0; top: 0;
background: black; background: black;
position: fixed; position: fixed;
} }
animation: fade-in .1s forwards cubic-bezier(.19,1,.22,1); animation: fade-in 0.1s forwards cubic-bezier(0.19, 1, 0.22, 1);
} }
.bruno-modal-footer { .bruno-modal-footer {

View File

@ -1,7 +1,7 @@
import React, {useState, useEffect} from 'react'; import React, { useState, useEffect } from 'react';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const ModalHeader = ({title, handleCancel}) => ( const ModalHeader = ({ title, handleCancel }) => (
<div className="bruno-modal-header"> <div className="bruno-modal-header">
{title ? <div className="bruno-modal-heade-title">{title}</div> : null} {title ? <div className="bruno-modal-heade-title">{title}</div> : null}
{handleCancel ? ( {handleCancel ? (
@ -12,13 +12,9 @@ const ModalHeader = ({title, handleCancel}) => (
</div> </div>
); );
const ModalContent = ({children}) => ( const ModalContent = ({ children }) => <div className="bruno-modal-content px-4 py-6">{children}</div>;
<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';
@ -28,32 +24,21 @@ const ModalFooter = ({confirmText, cancelText, handleSubmit, handleCancel, confi
return ( return (
<div className="flex justify-end p-4 bruno-modal-footer"> <div className="flex justify-end p-4 bruno-modal-footer">
<span className={hideCancel ? "hidden" : "mr-2"}> <span className={hideCancel ? 'hidden' : 'mr-2'}>
<button type="button" onClick={handleCancel} className="btn btn-md btn-close"> <button type="button" onClick={handleCancel} className="btn btn-md btn-close">
{cancelText} {cancelText}
</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>
</div> </div>
); );
} };
const Modal = ({ const Modal = ({ size, title, confirmText, cancelText, handleCancel, handleConfirm, children, confirmDisabled, hideCancel, hideFooter }) => {
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;
@ -72,7 +57,7 @@ const Modal = ({
return () => { return () => {
document.removeEventListener('keydown', escFunction, false); document.removeEventListener('keydown', escFunction, false);
} };
}, []); }, []);
let classes = 'bruno-modal'; let classes = 'bruno-modal';
@ -84,11 +69,11 @@ const Modal = ({
<div className={`bruno-modal-card modal-${size}`}> <div className={`bruno-modal-card modal-${size}`}>
<ModalHeader title={title} handleCancel={() => closeModal()} /> <ModalHeader title={title} handleCancel={() => closeModal()} />
<ModalContent>{children}</ModalContent> <ModalContent>{children}</ModalContent>
<ModalFooter <ModalFooter
confirmText={confirmText} confirmText={confirmText}
cancelText={cancelText} cancelText={cancelText}
handleCancel={() => closeModal()} handleCancel={() => closeModal()}
handleSubmit={handleConfirm} handleSubmit={handleConfirm}
confirmDisabled={confirmDisabled} confirmDisabled={confirmDisabled}
hideCancel={hideCancel} hideCancel={hideCancel}
hideFooter={hideFooter} hideFooter={hideFooter}

View File

@ -14,7 +14,6 @@ const StyledWrapper = styled.div`
user-select: none; user-select: none;
} }
} }
`; `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -1,7 +1,7 @@
import React, { useState, forwardRef, useRef } from 'react'; import React, { useState, forwardRef, useRef } from 'react';
import Dropdown from '../Dropdown'; import Dropdown from '../Dropdown';
import { faCaretDown } from "@fortawesome/free-solid-svg-icons"; import { faCaretDown } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { IconBox, IconSearch, IconDots } from '@tabler/icons'; import { IconBox, IconSearch, IconDots } from '@tabler/icons';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
@ -9,11 +9,11 @@ const Navbar = () => {
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
const menuDropdownTippyRef = useRef(); const menuDropdownTippyRef = useRef();
const onMenuDropdownCreate = (ref) => menuDropdownTippyRef.current = ref; const onMenuDropdownCreate = (ref) => (menuDropdownTippyRef.current = ref);
const MenuIcon = forwardRef((props, ref) => { const MenuIcon = forwardRef((props, ref) => {
return ( return (
<div ref={ref} className="dropdown-icon cursor-pointer"> <div ref={ref} className="dropdown-icon cursor-pointer">
<IconDots size={22}/> <IconDots size={22} />
</div> </div>
); );
}); });
@ -25,27 +25,36 @@ const Navbar = () => {
{/* <FontAwesomeIcon className="ml-2" icon={faCaretDown} style={{fontSize: 13}}/> */} {/* <FontAwesomeIcon className="ml-2" icon={faCaretDown} style={{fontSize: 13}}/> */}
</div> </div>
<div className="collection-dropdown flex flex-grow items-center justify-end"> <div className="collection-dropdown flex flex-grow items-center justify-end">
<Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement='bottom-start'> <Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement="bottom-start">
<div className="dropdown-item" onClick={(e) => { <div
menuDropdownTippyRef.current.hide(); className="dropdown-item"
setModalOpen(true); onClick={(e) => {
}}> menuDropdownTippyRef.current.hide();
setModalOpen(true);
}}
>
Create Collection Create Collection
</div> </div>
<div className="dropdown-item" onClick={(e) => { <div
menuDropdownTippyRef.current.hide(); className="dropdown-item"
}}> onClick={(e) => {
menuDropdownTippyRef.current.hide();
}}
>
Import Collection Import Collection
</div> </div>
<div className="dropdown-item" onClick={(e) => { <div
menuDropdownTippyRef.current.hide(); className="dropdown-item"
}}> onClick={(e) => {
menuDropdownTippyRef.current.hide();
}}
>
Settings Settings
</div> </div>
</Dropdown> </Dropdown>
</div> </div>
</StyledWrapper> </StyledWrapper>
) );
}; };
export default Navbar; export default Navbar;

View File

@ -1,7 +1,7 @@
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
function Portal({ children, wrapperId }) { function Portal({ children, wrapperId }) {
wrapperId = wrapperId || "bruno-app-body"; wrapperId = wrapperId || 'bruno-app-body';
return createPortal(children, document.getElementById(wrapperId)); return createPortal(children, document.getElementById(wrapperId));
} }

View File

@ -6,7 +6,8 @@ const Wrapper = styled.div`
border-collapse: collapse; border-collapse: collapse;
font-weight: 600; font-weight: 600;
thead, td { thead,
td {
border: 1px solid #efefef; border: 1px solid #efefef;
} }
@ -24,18 +25,18 @@ const Wrapper = styled.div`
font-size: 0.8125rem; font-size: 0.8125rem;
} }
input[type="text"] { input[type='text'] {
width: 100%; width: 100%;
border: solid 1px transparent; border: solid 1px transparent;
outline: none !important; outline: none !important;
&:focus{ &:focus {
outline: none !important; outline: none !important;
border: solid 1px transparent; border: solid 1px transparent;
} }
} }
input[type="checkbox"] { input[type='checkbox'] {
cursor: pointer; cursor: pointer;
position: relative; position: relative;
top: 1px; top: 1px;

View File

@ -6,52 +6,58 @@ import { useDispatch } from 'react-redux';
import { addFormUrlEncodedParam, updateFormUrlEncodedParam, deleteFormUrlEncodedParam } from 'providers/ReduxStore/slices/collections'; import { addFormUrlEncodedParam, updateFormUrlEncodedParam, deleteFormUrlEncodedParam } from 'providers/ReduxStore/slices/collections';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const FormUrlEncodedParams = ({item, collection}) => { const FormUrlEncodedParams = ({ item, collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const params = item.draft ? get(item, 'draft.request.body.formUrlEncoded') : get(item, 'request.body.formUrlEncoded'); const params = item.draft ? get(item, 'draft.request.body.formUrlEncoded') : get(item, 'request.body.formUrlEncoded');
const addParam = () => { const addParam = () => {
dispatch(addFormUrlEncodedParam({ dispatch(
itemUid: item.uid, addFormUrlEncodedParam({
collectionUid: collection.uid, itemUid: item.uid,
})); collectionUid: collection.uid
})
);
}; };
const handleParamChange = (e, _param, type) => { const handleParamChange = (e, _param, type) => {
const param = cloneDeep(_param); const param = cloneDeep(_param);
switch(type) { switch (type) {
case 'name' : { case 'name': {
param.name = e.target.value; param.name = e.target.value;
break; break;
} }
case 'value' : { case 'value': {
param.value = e.target.value; param.value = e.target.value;
break; break;
} }
case 'description' : { case 'description': {
param.description = e.target.value; param.description = e.target.value;
break; break;
} }
case 'enabled' : { case 'enabled': {
param.enabled = e.target.checked; param.enabled = e.target.checked;
break; break;
} }
} }
dispatch(updateFormUrlEncodedParam({ dispatch(
param: param, updateFormUrlEncodedParam({
itemUid: item.uid, param: param,
collectionUid: collection.uid itemUid: item.uid,
})); collectionUid: collection.uid
})
);
}; };
const handleRemoveParams = (param) => { const handleRemoveParams = (param) => {
dispatch(deleteFormUrlEncodedParam({ dispatch(
paramUid: param.uid, deleteFormUrlEncodedParam({
itemUid: item.uid, paramUid: param.uid,
collectionUid: collection.uid itemUid: item.uid,
})); collectionUid: collection.uid
})
);
}; };
return ( return (
<StyledWrapper className="w-full"> <StyledWrapper className="w-full">
<table> <table>
@ -64,56 +70,64 @@ const FormUrlEncodedParams = ({item, collection}) => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{params && params.length ? params.map((param, index) => { {params && params.length
return ( ? params.map((param, index) => {
<tr key={param.uid}> return (
<td> <tr key={param.uid}>
<input <td>
type="text" <input
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" type="text"
value={param.name} autoComplete="off"
className="mousetrap" autoCorrect="off"
onChange={(e) => handleParamChange(e, param, 'name')} autoCapitalize="off"
/> spellCheck="false"
</td> value={param.name}
<td> className="mousetrap"
<input onChange={(e) => handleParamChange(e, param, 'name')}
type="text" />
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" </td>
value={param.value} <td>
className="mousetrap" <input
onChange={(e) => handleParamChange(e, param, 'value')} type="text"
/> autoComplete="off"
</td> autoCorrect="off"
<td> autoCapitalize="off"
<input spellCheck="false"
type="text" value={param.value}
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" className="mousetrap"
value={param.description} onChange={(e) => handleParamChange(e, param, 'value')}
className="mousetrap" />
onChange={(e) => handleParamChange(e, param, 'description')} </td>
/> <td>
</td> <input
<td> type="text"
<div className="flex items-center"> autoComplete="off"
<input autoCorrect="off"
type="checkbox" autoCapitalize="off"
checked={param.enabled} spellCheck="false"
className="mr-3 mousetrap" value={param.description}
onChange={(e) => handleParamChange(e, param, 'enabled')} className="mousetrap"
/> onChange={(e) => handleParamChange(e, param, 'description')}
<button onClick={() => handleRemoveParams(param)}> />
<IconTrash strokeWidth={1.5} size={20}/> </td>
</button> <td>
</div> <div className="flex items-center">
</td> <input type="checkbox" checked={param.enabled} className="mr-3 mousetrap" onChange={(e) => handleParamChange(e, param, 'enabled')} />
</tr> <button onClick={() => handleRemoveParams(param)}>
); <IconTrash strokeWidth={1.5} size={20} />
}) : null} </button>
</div>
</td>
</tr>
);
})
: null}
</tbody> </tbody>
</table> </table>
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={addParam}>+ Add Param</button> <button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={addParam}>
+ Add Param
</button>
</StyledWrapper> </StyledWrapper>
) );
}; };
export default FormUrlEncodedParams; export default FormUrlEncodedParams;

View File

@ -18,7 +18,11 @@ const StyledWrapper = styled.div`
color: rgb(125 125 125); color: rgb(125 125 125);
outline: none !important; outline: none !important;
&:focus, &:active, &:focus-within, &:focus-visible, &:target { &:focus,
&:active,
&:focus-within,
&:focus-visible,
&:target {
outline: none !important; outline: none !important;
box-shadow: none !important; box-shadow: none !important;
} }
@ -38,7 +42,11 @@ const StyledWrapper = styled.div`
outline: none !important; outline: none !important;
box-shadow: none !important; box-shadow: none !important;
&:focus, &:active, &:focus-within, &:focus-visible, &:target { &:focus,
&:active,
&:focus-within,
&:focus-visible,
&:target {
border: none; border: none;
outline: none !important; outline: none !important;
box-shadow: none !important; box-shadow: none !important;
@ -49,7 +57,6 @@ const StyledWrapper = styled.div`
box-shadow: none !important; box-shadow: none !important;
} }
} }
`; `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -4,23 +4,17 @@ import QueryEditor from 'components/RequestPane/QueryEditor';
import RequestHeaders from 'components/RequestPane/RequestHeaders'; import RequestHeaders from 'components/RequestPane/RequestHeaders';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const GraphQLRequestPane = ({onRunQuery, schema, leftPaneWidth, value, onQueryChange}) => { const GraphQLRequestPane = ({ onRunQuery, schema, leftPaneWidth, value, onQueryChange }) => {
return ( return (
<StyledWrapper className="h-full"> <StyledWrapper className="h-full">
<Tabs className='react-tabs mt-1 flex flex-grow flex-col h-full' forceRenderTabPanel> <Tabs className="react-tabs mt-1 flex flex-grow flex-col h-full" forceRenderTabPanel>
<TabList> <TabList>
<Tab tabIndex="-1">Query</Tab> <Tab tabIndex="-1">Query</Tab>
<Tab tabIndex="-1">Headers</Tab> <Tab tabIndex="-1">Headers</Tab>
</TabList> </TabList>
<TabPanel> <TabPanel>
<div className="mt-4"> <div className="mt-4">
<QueryEditor <QueryEditor schema={schema} width={leftPaneWidth} value={value} onRunQuery={onRunQuery} onEdit={onQueryChange} />
schema={schema}
width={leftPaneWidth}
value={value}
onRunQuery={onRunQuery}
onEdit={onQueryChange}
/>
</div> </div>
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
@ -28,7 +22,7 @@ const GraphQLRequestPane = ({onRunQuery, schema, leftPaneWidth, value, onQueryCh
</TabPanel> </TabPanel>
</Tabs> </Tabs>
</StyledWrapper> </StyledWrapper>
) );
}; };
export default GraphQLRequestPane; export default GraphQLRequestPane;

View File

@ -10,7 +10,11 @@ const StyledWrapper = styled.div`
color: var(--color-tab-inactive); color: var(--color-tab-inactive);
cursor: pointer; cursor: pointer;
&:focus, &:active, &:focus-within, &:focus-visible, &:target { &:focus,
&:active,
&:focus-within,
&:focus-visible,
&:target {
outline: none !important; outline: none !important;
box-shadow: none !important; box-shadow: none !important;
} }
@ -23,4 +27,4 @@ const StyledWrapper = styled.div`
} }
`; `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -9,73 +9,75 @@ import RequestBody from 'components/RequestPane/RequestBody';
import RequestBodyMode from 'components/RequestPane/RequestBody/RequestBodyMode'; import RequestBodyMode from 'components/RequestPane/RequestBody/RequestBodyMode';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const HttpRequestPane = ({item, collection, leftPaneWidth}) => { const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
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 selectTab = (tab) => { const selectTab = (tab) => {
dispatch(updateRequestPaneTab({ dispatch(
uid: item.uid, updateRequestPaneTab({
requestPaneTab: tab uid: item.uid,
})) requestPaneTab: tab
})
);
}; };
const getTabPanel = (tab) => { const getTabPanel = (tab) => {
switch(tab) { switch (tab) {
case 'params': { case 'params': {
return <QueryParams item={item} collection={collection}/>; return <QueryParams item={item} collection={collection} />;
} }
case 'body': { case 'body': {
return <RequestBody item={item} collection={collection}/>; return <RequestBody item={item} collection={collection} />;
} }
case 'headers': { case 'headers': {
return <RequestHeaders item={item} collection={collection}/>; return <RequestHeaders item={item} collection={collection} />;
} }
default: { default: {
return <div className="mt-4">404 | Not found</div>; return <div className="mt-4">404 | Not found</div>;
} }
} }
} };
if(!activeTabUid) { if (!activeTabUid) {
return ( return <div>Something went wrong</div>;
<div>Something went wrong</div>
);
} }
const focusedTab = find(tabs, (t) => t.uid === activeTabUid); const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
if(!focusedTab || !focusedTab.uid || !focusedTab.requestPaneTab) { if (!focusedTab || !focusedTab.uid || !focusedTab.requestPaneTab) {
return ( return <div className="pb-4 px-4">An error occured!</div>;
<div className="pb-4 px-4">An error occured!</div>
);
} }
const getTabClassname = (tabName) => { const getTabClassname = (tabName) => {
return classnames(`tab select-none ${tabName}`, { return classnames(`tab select-none ${tabName}`, {
'active': tabName === focusedTab.requestPaneTab active: tabName === focusedTab.requestPaneTab
}); });
}; };
return ( return (
<StyledWrapper className="flex flex-col h-full relative"> <StyledWrapper className="flex flex-col h-full relative">
<div className="flex items-center tabs" role="tablist"> <div className="flex items-center tabs" role="tablist">
<div className={getTabClassname('params')} role="tab" onClick={() => selectTab('params')}>Params</div> <div className={getTabClassname('params')} role="tab" onClick={() => selectTab('params')}>
<div className={getTabClassname('body')} role="tab" onClick={() => selectTab('body')}>Body</div> Params
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>Headers</div> </div>
<div className={getTabClassname('body')} role="tab" onClick={() => selectTab('body')}>
Body
</div>
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
Headers
</div>
{/* Moved to post mvp */} {/* Moved to post mvp */}
{/* <div className={getTabClassname('auth')} role="tab" onClick={() => selectTab('auth')}>Auth</div> */} {/* <div className={getTabClassname('auth')} role="tab" onClick={() => selectTab('auth')}>Auth</div> */}
{focusedTab.requestPaneTab === 'body' ? ( {focusedTab.requestPaneTab === 'body' ? (
<div className="flex flex-grow justify-end items-center"> <div className="flex flex-grow justify-end items-center">
<RequestBodyMode item={item} collection={collection}/> <RequestBodyMode item={item} collection={collection} />
</div> </div>
) : null } ) : null}
</div> </div>
<section className="flex w-full mt-5"> <section className="flex w-full mt-5">{getTabPanel(focusedTab.requestPaneTab)}</section>
{getTabPanel(focusedTab.requestPaneTab)}
</section>
</StyledWrapper> </StyledWrapper>
) );
}; };
export default HttpRequestPane; export default HttpRequestPane;

View File

@ -6,7 +6,8 @@ const Wrapper = styled.div`
border-collapse: collapse; border-collapse: collapse;
font-weight: 600; font-weight: 600;
thead, td { thead,
td {
border: 1px solid #efefef; border: 1px solid #efefef;
} }
@ -24,18 +25,18 @@ const Wrapper = styled.div`
font-size: 0.8125rem; font-size: 0.8125rem;
} }
input[type="text"] { input[type='text'] {
width: 100%; width: 100%;
border: solid 1px transparent; border: solid 1px transparent;
outline: none !important; outline: none !important;
&:focus{ &:focus {
outline: none !important; outline: none !important;
border: solid 1px transparent; border: solid 1px transparent;
} }
} }
input[type="checkbox"] { input[type='checkbox'] {
cursor: pointer; cursor: pointer;
position: relative; position: relative;
top: 1px; top: 1px;

View File

@ -6,52 +6,58 @@ import { useDispatch } from 'react-redux';
import { addMultipartFormParam, updateMultipartFormParam, deleteMultipartFormParam } from 'providers/ReduxStore/slices/collections'; import { addMultipartFormParam, updateMultipartFormParam, deleteMultipartFormParam } from 'providers/ReduxStore/slices/collections';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const MultipartFormParams = ({item, collection}) => { const MultipartFormParams = ({ item, collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const params = item.draft ? get(item, 'draft.request.body.multipartForm') : get(item, 'request.body.multipartForm'); const params = item.draft ? get(item, 'draft.request.body.multipartForm') : get(item, 'request.body.multipartForm');
const addParam = () => { const addParam = () => {
dispatch(addMultipartFormParam({ dispatch(
itemUid: item.uid, addMultipartFormParam({
collectionUid: collection.uid, itemUid: item.uid,
})); collectionUid: collection.uid
})
);
}; };
const handleParamChange = (e, _param, type) => { const handleParamChange = (e, _param, type) => {
const param = cloneDeep(_param); const param = cloneDeep(_param);
switch(type) { switch (type) {
case 'name' : { case 'name': {
param.name = e.target.value; param.name = e.target.value;
break; break;
} }
case 'value' : { case 'value': {
param.value = e.target.value; param.value = e.target.value;
break; break;
} }
case 'description' : { case 'description': {
param.description = e.target.value; param.description = e.target.value;
break; break;
} }
case 'enabled' : { case 'enabled': {
param.enabled = e.target.checked; param.enabled = e.target.checked;
break; break;
} }
} }
dispatch(updateMultipartFormParam({ dispatch(
param: param, updateMultipartFormParam({
itemUid: item.uid, param: param,
collectionUid: collection.uid itemUid: item.uid,
})); collectionUid: collection.uid
})
);
}; };
const handleRemoveParams = (param) => { const handleRemoveParams = (param) => {
dispatch(deleteMultipartFormParam({ dispatch(
paramUid: param.uid, deleteMultipartFormParam({
itemUid: item.uid, paramUid: param.uid,
collectionUid: collection.uid itemUid: item.uid,
})); collectionUid: collection.uid
})
);
}; };
return ( return (
<StyledWrapper className="w-full"> <StyledWrapper className="w-full">
<table> <table>
@ -64,56 +70,64 @@ const MultipartFormParams = ({item, collection}) => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{params && params.length ? params.map((param, index) => { {params && params.length
return ( ? params.map((param, index) => {
<tr key={param.uid}> return (
<td> <tr key={param.uid}>
<input <td>
type="text" <input
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" type="text"
value={param.name} autoComplete="off"
className="mousetrap" autoCorrect="off"
onChange={(e) => handleParamChange(e, param, 'name')} autoCapitalize="off"
/> spellCheck="false"
</td> value={param.name}
<td> className="mousetrap"
<input onChange={(e) => handleParamChange(e, param, 'name')}
type="text" />
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" </td>
value={param.value} <td>
className="mousetrap" <input
onChange={(e) => handleParamChange(e, param, 'value')} type="text"
/> autoComplete="off"
</td> autoCorrect="off"
<td> autoCapitalize="off"
<input spellCheck="false"
type="text" value={param.value}
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" className="mousetrap"
value={param.description} onChange={(e) => handleParamChange(e, param, 'value')}
className="mousetrap" />
onChange={(e) => handleParamChange(e, param, 'description')} </td>
/> <td>
</td> <input
<td> type="text"
<div className="flex items-center"> autoComplete="off"
<input autoCorrect="off"
type="checkbox" autoCapitalize="off"
checked={param.enabled} spellCheck="false"
className="mr-3 mousetrap" value={param.description}
onChange={(e) => handleParamChange(e, param, 'enabled')} className="mousetrap"
/> onChange={(e) => handleParamChange(e, param, 'description')}
<button onClick={() => handleRemoveParams(param)}> />
<IconTrash strokeWidth={1.5} size={20}/> </td>
</button> <td>
</div> <div className="flex items-center">
</td> <input type="checkbox" checked={param.enabled} className="mr-3 mousetrap" onChange={(e) => handleParamChange(e, param, 'enabled')} />
</tr> <button onClick={() => handleRemoveParams(param)}>
); <IconTrash strokeWidth={1.5} size={20} />
}) : null} </button>
</div>
</td>
</tr>
);
})
: null}
</tbody> </tbody>
</table> </table>
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={addParam}>+ Add Param</button> <button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={addParam}>
+ Add Param
</button>
</StyledWrapper> </StyledWrapper>
) );
}; };
export default MultipartFormParams; export default MultipartFormParams;

View File

@ -13,4 +13,3 @@ const StyledWrapper = styled.div`
`; `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -44,44 +44,37 @@ export default class QueryEditor extends React.Component {
showCursorWhenSelecting: true, showCursorWhenSelecting: true,
readOnly: this.props.readOnly ? 'nocursor' : false, readOnly: this.props.readOnly ? 'nocursor' : false,
foldGutter: { foldGutter: {
minFoldSize: 4, minFoldSize: 4
}, },
lint: { lint: {
schema: this.props.schema, schema: this.props.schema,
validationRules: this.props.validationRules ?? null, validationRules: this.props.validationRules ?? null,
// linting accepts string or FragmentDefinitionNode[] // linting accepts string or FragmentDefinitionNode[]
externalFragments: this.props?.externalFragments, externalFragments: this.props?.externalFragments
}, },
hintOptions: { hintOptions: {
schema: this.props.schema, schema: this.props.schema,
closeOnUnfocus: false, closeOnUnfocus: false,
completeSingle: false, completeSingle: false,
container: this._node, container: this._node,
externalFragments: this.props?.externalFragments, externalFragments: this.props?.externalFragments
}, },
info: { info: {
schema: this.props.schema, schema: this.props.schema,
renderDescription: (text) => md.render(text), renderDescription: (text) => md.render(text),
onClick: (reference) => onClick: (reference) => this.props.onClickReference && this.props.onClickReference(reference)
this.props.onClickReference && this.props.onClickReference(reference),
}, },
jump: { jump: {
schema: this.props.schema, schema: this.props.schema,
onClick: (reference) => onClick: (reference) => this.props.onClickReference && this.props.onClickReference(reference)
this.props.onClickReference && this.props.onClickReference(reference)
}, },
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
extraKeys: { extraKeys: {
'Cmd-Space': () => 'Cmd-Space': () => editor.showHint({ completeSingle: true, container: this._node }),
editor.showHint({ completeSingle: true, container: this._node }), 'Ctrl-Space': () => editor.showHint({ completeSingle: true, container: this._node }),
'Ctrl-Space': () => 'Alt-Space': () => editor.showHint({ completeSingle: true, container: this._node }),
editor.showHint({ completeSingle: true, container: this._node }), 'Shift-Space': () => editor.showHint({ completeSingle: true, container: this._node }),
'Alt-Space': () => 'Shift-Alt-Space': () => editor.showHint({ completeSingle: true, container: this._node }),
editor.showHint({ completeSingle: true, container: this._node }),
'Shift-Space': () =>
editor.showHint({ completeSingle: true, container: this._node }),
'Shift-Alt-Space': () =>
editor.showHint({ completeSingle: true, container: this._node }),
'Cmd-Enter': () => { 'Cmd-Enter': () => {
if (this.props.onRunQuery) { if (this.props.onRunQuery) {
@ -129,8 +122,8 @@ export default class QueryEditor extends React.Component {
if (this.props.onRunQuery) { if (this.props.onRunQuery) {
// empty // empty
} }
}, }
}, }
})); }));
if (editor) { if (editor) {
editor.on('change', this._onEdit); editor.on('change', this._onEdit);
@ -152,11 +145,7 @@ export default class QueryEditor extends React.Component {
this.editor.options.jump.schema = this.props.schema; this.editor.options.jump.schema = this.props.schema;
CodeMirror.signal(this.editor, 'change', this.editor); CodeMirror.signal(this.editor, 'change', this.editor);
} }
if ( if (this.props.value !== prevProps.value && this.props.value !== this.cachedValue && this.editor) {
this.props.value !== prevProps.value &&
this.props.value !== this.cachedValue &&
this.editor
) {
this.cachedValue = this.props.value; this.cachedValue = this.props.value;
this.editor.setValue(this.props.value); this.editor.setValue(this.props.value);
} }
@ -177,7 +166,7 @@ export default class QueryEditor extends React.Component {
<StyledWrapper <StyledWrapper
className="h-full" className="h-full"
aria-label="Query Editor" aria-label="Query Editor"
ref={node => { ref={(node) => {
this._node = node; this._node = node;
}} }}
/> />

View File

@ -7,10 +7,7 @@
import escapeHTML from 'escape-html'; import escapeHTML from 'escape-html';
import MD from 'markdown-it'; import MD from 'markdown-it';
import { import { GraphQLNonNull, GraphQLList } from 'graphql';
GraphQLNonNull,
GraphQLList
} from 'graphql';
const md = new MD(); const md = new MD();
@ -18,85 +15,65 @@ const md = new MD();
* Render a custom UI for CodeMirror's hint which includes additional info * Render a custom UI for CodeMirror's hint which includes additional info
* about the type and description for the selected context. * about the type and description for the selected context.
*/ */
export default function onHasCompletion( export default function onHasCompletion(_cm, data, onHintInformationRender) {
_cm,
data,
onHintInformationRender,
) {
const CodeMirror = require('codemirror'); const CodeMirror = require('codemirror');
let information; let information;
let deprecation; let deprecation;
// When a hint result is selected, we augment the UI with information. // When a hint result is selected, we augment the UI with information.
CodeMirror.on( CodeMirror.on(data, 'select', (ctx, el) => {
data, // Only the first time (usually when the hint UI is first displayed)
'select', // do we create the information nodes.
(ctx, el) => { if (!information) {
// Only the first time (usually when the hint UI is first displayed) const hintsUl = el.parentNode;
// do we create the information nodes.
if (!information) {
const hintsUl = el.parentNode;
// This "information" node will contain the additional info about the // This "information" node will contain the additional info about the
// highlighted typeahead option. // highlighted typeahead option.
information = document.createElement('div'); information = document.createElement('div');
information.className = 'CodeMirror-hint-information'; information.className = 'CodeMirror-hint-information';
hintsUl.appendChild(information); hintsUl.appendChild(information);
// This "deprecation" node will contain info about deprecated usage. // This "deprecation" node will contain info about deprecated usage.
deprecation = document.createElement('div'); deprecation = document.createElement('div');
deprecation.className = 'CodeMirror-hint-deprecation'; deprecation.className = 'CodeMirror-hint-deprecation';
hintsUl.appendChild(deprecation); hintsUl.appendChild(deprecation);
// When CodeMirror attempts to remove the hint UI, we detect that it was // When CodeMirror attempts to remove the hint UI, we detect that it was
// removed and in turn remove the information nodes. // removed and in turn remove the information nodes.
let onRemoveFn; let onRemoveFn;
hintsUl.addEventListener( hintsUl.addEventListener(
'DOMNodeRemoved', 'DOMNodeRemoved',
(onRemoveFn = (event) => { (onRemoveFn = (event) => {
if (event.target === hintsUl) { if (event.target === hintsUl) {
hintsUl.removeEventListener('DOMNodeRemoved', onRemoveFn); hintsUl.removeEventListener('DOMNodeRemoved', onRemoveFn);
information = null; information = null;
deprecation = null; deprecation = null;
onRemoveFn = null; onRemoveFn = null;
} }
}), })
); );
} }
// Now that the UI has been set up, add info to information. // Now that the UI has been set up, add info to information.
const description = ctx.description const description = ctx.description ? md.render(ctx.description) : 'Self descriptive.';
? md.render(ctx.description) const type = ctx.type ? '<span className="infoType">' + renderType(ctx.type) + '</span>' : '';
: 'Self descriptive.';
const type = ctx.type
? '<span className="infoType">' + renderType(ctx.type) + '</span>'
: '';
information.innerHTML = information.innerHTML = '<div className="content">' + (description.slice(0, 3) === '<p>' ? '<p>' + type + description.slice(3) : type + description) + '</div>';
'<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 const reason = ctx.deprecationReason ? md.render(ctx.deprecationReason) : '';
? md.render(ctx.deprecationReason) deprecation.innerHTML = '<span className="deprecation-label">Deprecated</span>' + reason;
: ''; deprecation.style.display = 'block';
deprecation.innerHTML = } else if (deprecation) {
'<span className="deprecation-label">Deprecated</span>' + reason; deprecation.style.display = 'none';
deprecation.style.display = 'block'; }
} else if (deprecation) {
deprecation.style.display = 'none';
}
// Additional rendering? // Additional rendering?
if (onHintInformationRender) { if (onHintInformationRender) {
onHintInformationRender(information); onHintInformationRender(information);
} }
}, });
);
} }
function renderType(type) { function renderType(type) {

View File

@ -6,7 +6,8 @@ const Wrapper = styled.div`
border-collapse: collapse; border-collapse: collapse;
font-weight: 600; font-weight: 600;
thead, td { thead,
td {
border: 1px solid #efefef; border: 1px solid #efefef;
} }
@ -27,18 +28,18 @@ const Wrapper = styled.div`
} }
} }
input[type="text"] { input[type='text'] {
width: 100%; width: 100%;
border: solid 1px transparent; border: solid 1px transparent;
outline: none !important; outline: none !important;
&:focus{ &:focus {
outline: none !important; outline: none !important;
border: solid 1px transparent; border: solid 1px transparent;
} }
} }
input[type="checkbox"] { input[type='checkbox'] {
cursor: pointer; cursor: pointer;
position: relative; position: relative;
top: 1px; top: 1px;

View File

@ -7,52 +7,58 @@ import { addQueryParam, updateQueryParam, deleteQueryParam } from 'providers/Red
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const QueryParams = ({item, collection}) => { const QueryParams = ({ item, collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const params = item.draft ? get(item, 'draft.request.params') : get(item, 'request.params'); const params = item.draft ? get(item, 'draft.request.params') : get(item, 'request.params');
const handleAddParam = () => { const handleAddParam = () => {
dispatch(addQueryParam({ dispatch(
itemUid: item.uid, addQueryParam({
collectionUid: collection.uid, itemUid: item.uid,
})); collectionUid: collection.uid
})
);
}; };
const handleParamChange = (e, _param, type) => { const handleParamChange = (e, _param, type) => {
const param = cloneDeep(_param); const param = cloneDeep(_param);
switch(type) { switch (type) {
case 'name' : { case 'name': {
param.name = e.target.value; param.name = e.target.value;
break; break;
} }
case 'value' : { case 'value': {
param.value = e.target.value; param.value = e.target.value;
break; break;
} }
case 'description' : { case 'description': {
param.description = e.target.value; param.description = e.target.value;
break; break;
} }
case 'enabled' : { case 'enabled': {
param.enabled = e.target.checked; param.enabled = e.target.checked;
break; break;
} }
} }
dispatch(updateQueryParam({ dispatch(
param, updateQueryParam({
itemUid: item.uid, param,
collectionUid: collection.uid itemUid: item.uid,
})); collectionUid: collection.uid
})
);
}; };
const handleRemoveParam = (param) => { const handleRemoveParam = (param) => {
dispatch(deleteQueryParam({ dispatch(
paramUid: param.uid, deleteQueryParam({
itemUid: item.uid, paramUid: param.uid,
collectionUid: collection.uid itemUid: item.uid,
})); collectionUid: collection.uid
})
);
}; };
return ( return (
@ -67,58 +73,64 @@ const QueryParams = ({item, collection}) => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{params && params.length ? params.map((param, index) => { {params && params.length
return ( ? params.map((param, index) => {
<tr key={param.uid}> return (
<td> <tr key={param.uid}>
<input <td>
type="text" <input
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" type="text"
value={param.name} autoComplete="off"
className="mousetrap" autoCorrect="off"
onChange={(e) => handleParamChange(e, param, 'name')} autoCapitalize="off"
/> spellCheck="false"
</td> value={param.name}
<td> className="mousetrap"
<input onChange={(e) => handleParamChange(e, param, 'name')}
type="text" />
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" </td>
value={param.value} <td>
className="mousetrap" <input
onChange={(e) => handleParamChange(e, param, 'value')} type="text"
/> autoComplete="off"
</td> autoCorrect="off"
<td> autoCapitalize="off"
<input spellCheck="false"
type="text" value={param.value}
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" className="mousetrap"
value={param.description} onChange={(e) => handleParamChange(e, param, 'value')}
className="mousetrap" />
onChange={(e) => handleParamChange(e, param, 'description')} </td>
/> <td>
</td> <input
<td> type="text"
<div className="flex items-center"> autoComplete="off"
<input autoCorrect="off"
type="checkbox" autoCapitalize="off"
checked={param.enabled} spellCheck="false"
className="mr-3 mousetrap" value={param.description}
onChange={(e) => handleParamChange(e, param, 'enabled')} className="mousetrap"
/> onChange={(e) => handleParamChange(e, param, 'description')}
<button onClick={() => handleRemoveParam(param)}> />
<IconTrash strokeWidth={1.5} size={20}/> </td>
</button> <td>
</div> <div className="flex items-center">
</td> <input type="checkbox" checked={param.enabled} className="mr-3 mousetrap" onChange={(e) => handleParamChange(e, param, 'enabled')} />
</tr> <button onClick={() => handleRemoveParam(param)}>
); <IconTrash strokeWidth={1.5} size={20} />
}) : null} </button>
</div>
</td>
</tr>
);
})
: null}
</tbody> </tbody>
</table> </table>
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={handleAddParam}> <button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={handleAddParam}>
+&nbsp;<span>Add Param</span> +&nbsp;<span>Add Param</span>
</button> </button>
</StyledWrapper> </StyledWrapper>
) );
}; };
export default QueryParams; export default QueryParams;

View File

@ -17,7 +17,7 @@ const Wrapper = styled.div`
} }
.dropdown-item { .dropdown-item {
padding: .25rem .6rem !important; padding: 0.25rem 0.6rem !important;
} }
} }

View File

@ -3,43 +3,48 @@ import { IconCaretDown } from '@tabler/icons';
import Dropdown from 'components/Dropdown'; import Dropdown from 'components/Dropdown';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const HttpMethodSelector = ({method, onMethodSelect}) => { const HttpMethodSelector = ({ method, onMethodSelect }) => {
const dropdownTippyRef = useRef(); const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => dropdownTippyRef.current = ref; const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
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">{method}</div> <div className="flex-grow font-medium">{method}</div>
<div><IconCaretDown className="caret ml-2 mr-2" size={14} strokeWidth={2}/></div> <div>
<IconCaretDown className="caret ml-2 mr-2" size={14} strokeWidth={2} />
</div>
</div> </div>
); );
}); });
const handleMethodSelect = (verb) => onMethodSelect(verb); const handleMethodSelect = (verb) => onMethodSelect(verb);
const Verb = ({verb}) => { const Verb = ({ verb }) => {
return ( return (
<div className="dropdown-item" onClick={() => { <div
dropdownTippyRef.current.hide(); className="dropdown-item"
handleMethodSelect(verb); onClick={() => {
}}> dropdownTippyRef.current.hide();
handleMethodSelect(verb);
}}
>
{verb} {verb}
</div> </div>
); );
}; };
return( return (
<StyledWrapper> <StyledWrapper>
<div className="flex items-center cursor-pointer method-selector"> <div className="flex items-center cursor-pointer method-selector">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement='bottom-start'> <Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-start">
<Verb verb='GET' /> <Verb verb="GET" />
<Verb verb='POST' /> <Verb verb="POST" />
<Verb verb='PUT' /> <Verb verb="PUT" />
<Verb verb='DELETE' /> <Verb verb="DELETE" />
<Verb verb='PATCH' /> <Verb verb="PATCH" />
<Verb verb='OPTIONS' /> <Verb verb="OPTIONS" />
<Verb verb='HEAD' /> <Verb verb="HEAD" />
</Dropdown> </Dropdown>
</div> </div>
</StyledWrapper> </StyledWrapper>

View File

@ -6,45 +6,53 @@ import HttpMethodSelector from './HttpMethodSelector';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
import SendSvg from 'assets/send.svg'; import SendSvg from 'assets/send.svg';
const QueryUrl = ({item, collection, handleRun}) => { const QueryUrl = ({ item, collection, handleRun }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const method = item.draft ? get(item, 'draft.request.method') : get(item, 'request.method'); const method = item.draft ? get(item, 'draft.request.method') : get(item, 'request.method');
let url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url'); let url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
const onUrlChange = (value) => { const onUrlChange = (value) => {
dispatch(requestUrlChanged({ dispatch(
itemUid: item.uid, requestUrlChanged({
collectionUid: collection.uid, itemUid: item.uid,
url: value collectionUid: collection.uid,
})); url: value
})
);
}; };
const onMethodSelect = (verb) => { const onMethodSelect = (verb) => {
dispatch(updateRequestMethod({ dispatch(
method: verb, updateRequestMethod({
itemUid: item.uid, method: verb,
collectionUid: collection.uid itemUid: item.uid,
})); collectionUid: collection.uid
})
);
}; };
return ( return (
<StyledWrapper className="flex items-center"> <StyledWrapper className="flex items-center">
<div className="flex items-center h-full method-selector-container"> <div className="flex items-center h-full method-selector-container">
<HttpMethodSelector method={method} onMethodSelect={onMethodSelect}/> <HttpMethodSelector method={method} onMethodSelect={onMethodSelect} />
</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
className="px-3 w-full mousetrap" className="px-3 w-full mousetrap"
type="text" value={url} type="text"
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" value={url}
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={(event) => onUrlChange(event.target.value)} onChange={(event) => onUrlChange(event.target.value)}
/> />
<div className="flex items-center h-full mr-2 cursor-pointer" onClick={handleRun}> <div className="flex items-center h-full mr-2 cursor-pointer" onClick={handleRun}>
<img src={SendSvg.src} style={{width: '22px'}}/> <img src={SendSvg.src} style={{ width: '22px' }} />
</div> </div>
</div> </div>
</StyledWrapper> </StyledWrapper>
) );
}; };
export default QueryUrl; export default QueryUrl;

View File

@ -8,12 +8,12 @@ const Wrapper = styled.div`
border-radius: 3px; border-radius: 3px;
.dropdown-item { .dropdown-item {
padding: .2rem .6rem !important; padding: 0.2rem 0.6rem !important;
padding-left: 1.5rem !important; padding-left: 1.5rem !important;
} }
.label-item { .label-item {
padding: .2rem .6rem !important; padding: 0.2rem 0.6rem !important;
} }
} }

View File

@ -7,76 +7,89 @@ import { updateRequestBodyMode } from 'providers/ReduxStore/slices/collections';
import { humanizeRequestBodyMode } from 'utils/collections'; import { humanizeRequestBodyMode } from 'utils/collections';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const RequestBodyMode = ({item, collection}) => { const RequestBodyMode = ({ item, collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const dropdownTippyRef = useRef(); const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => dropdownTippyRef.current = ref; const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
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 Icon = forwardRef((props, ref) => { const Icon = forwardRef((props, ref) => {
return ( return (
<div ref={ref} className="flex items-center justify-center pl-3 py-1 select-none"> <div ref={ref} className="flex items-center justify-center pl-3 py-1 select-none">
{humanizeRequestBodyMode(bodyMode)} <IconCaretDown className="caret ml-2 mr-2" size={14} strokeWidth={2}/> {humanizeRequestBodyMode(bodyMode)} <IconCaretDown className="caret ml-2 mr-2" size={14} strokeWidth={2} />
</div> </div>
); );
}); });
const onModeChange = (value) => { const onModeChange = (value) => {
dispatch(updateRequestBodyMode({ dispatch(
itemUid: item.uid, updateRequestBodyMode({
collectionUid: collection.uid, itemUid: item.uid,
mode: value collectionUid: collection.uid,
})); mode: value
})
);
}; };
return( return (
<StyledWrapper> <StyledWrapper>
<div className="inline-flex items-center cursor-pointer body-mode-selector"> <div className="inline-flex items-center cursor-pointer body-mode-selector">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement='bottom-end'> <Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
<div className="label-item font-medium"> <div className="label-item font-medium">Form</div>
Form <div
</div> className="dropdown-item"
<div className="dropdown-item" onClick={() => { onClick={() => {
dropdownTippyRef.current.hide(); dropdownTippyRef.current.hide();
onModeChange('multipartForm'); onModeChange('multipartForm');
}}> }}
>
Multipart Form Multipart Form
</div> </div>
<div className="dropdown-item" onClick={() => { <div
dropdownTippyRef.current.hide(); className="dropdown-item"
onModeChange('formUrlEncoded'); onClick={() => {
}}> dropdownTippyRef.current.hide();
onModeChange('formUrlEncoded');
}}
>
Form Url Encoded Form Url Encoded
</div> </div>
<div className="label-item font-medium"> <div className="label-item font-medium">Raw</div>
Raw <div
</div> className="dropdown-item"
<div className="dropdown-item" onClick={() => { onClick={() => {
dropdownTippyRef.current.hide(); dropdownTippyRef.current.hide();
onModeChange('json'); onModeChange('json');
}}> }}
>
JSON JSON
</div> </div>
<div className="dropdown-item" onClick={() => { <div
dropdownTippyRef.current.hide(); className="dropdown-item"
onModeChange('xml'); onClick={() => {
}}> dropdownTippyRef.current.hide();
onModeChange('xml');
}}
>
XML XML
</div> </div>
<div className="dropdown-item" onClick={() => { <div
dropdownTippyRef.current.hide(); className="dropdown-item"
onModeChange('text'); onClick={() => {
}}> dropdownTippyRef.current.hide();
onModeChange('text');
}}
>
TEXT TEXT
</div> </div>
<div className="label-item font-medium"> <div className="label-item font-medium">Other</div>
Other <div
</div> className="dropdown-item"
<div className="dropdown-item" onClick={() => { onClick={() => {
dropdownTippyRef.current.hide(); dropdownTippyRef.current.hide();
onModeChange('none'); onModeChange('none');
}}> }}
>
No Body No Body
</div> </div>
</Dropdown> </Dropdown>

View File

@ -8,23 +8,25 @@ import { updateRequestBody } from 'providers/ReduxStore/slices/collections';
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';
const RequestBody = ({item, collection}) => { 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 onEdit = (value) => { const onEdit = (value) => {
dispatch(updateRequestBody({ dispatch(
content: value, updateRequestBody({
itemUid: item.uid, content: value,
collectionUid: collection.uid, itemUid: item.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));
if(['json', 'xml', 'text'].includes(bodyMode)) { if (['json', 'xml', 'text'].includes(bodyMode)) {
let codeMirrorMode = { let codeMirrorMode = {
json: 'application/ld+json', json: 'application/ld+json',
text: 'application/text', text: 'application/text',
@ -37,31 +39,21 @@ const RequestBody = ({item, collection}) => {
xml: body.xml xml: body.xml
}; };
return( return (
<StyledWrapper className="w-full"> <StyledWrapper className="w-full">
<CodeEditor <CodeEditor value={bodyContent[bodyMode] || ''} onEdit={onEdit} onRun={onRun} onSave={onSave} mode={codeMirrorMode[bodyMode]} />
value={bodyContent[bodyMode] || ''}
onEdit={onEdit}
onRun={onRun}
onSave={onSave}
mode={codeMirrorMode[bodyMode]}
/>
</StyledWrapper> </StyledWrapper>
); );
} }
if(bodyMode === 'formUrlEncoded') { if (bodyMode === 'formUrlEncoded') {
return <FormUrlEncodedParams item={item} collection={collection}/>; return <FormUrlEncodedParams item={item} collection={collection} />;
} }
if(bodyMode === 'multipartForm') { if (bodyMode === 'multipartForm') {
return <MultipartFormParams item={item} collection={collection}/>; return <MultipartFormParams item={item} collection={collection} />;
} }
return( return <StyledWrapper className="w-full">No Body</StyledWrapper>;
<StyledWrapper className="w-full">
No Body
</StyledWrapper>
);
}; };
export default RequestBody; export default RequestBody;

View File

@ -6,7 +6,8 @@ const Wrapper = styled.div`
border-collapse: collapse; border-collapse: collapse;
font-weight: 600; font-weight: 600;
thead, td { thead,
td {
border: 1px solid #efefef; border: 1px solid #efefef;
} }
@ -26,18 +27,18 @@ const Wrapper = styled.div`
padding: 5px; padding: 5px;
} }
input[type="text"] { input[type='text'] {
width: 100%; width: 100%;
border: solid 1px transparent; border: solid 1px transparent;
outline: none !important; outline: none !important;
&:focus{ &:focus {
outline: none !important; outline: none !important;
border: solid 1px transparent; border: solid 1px transparent;
} }
} }
input[type="checkbox"] { input[type='checkbox'] {
cursor: pointer; cursor: pointer;
position: relative; position: relative;
top: 1px; top: 1px;

View File

@ -6,52 +6,58 @@ import { useDispatch } from 'react-redux';
import { addRequestHeader, updateRequestHeader, deleteRequestHeader } from 'providers/ReduxStore/slices/collections'; import { addRequestHeader, updateRequestHeader, deleteRequestHeader } from 'providers/ReduxStore/slices/collections';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const RequestHeaders = ({item, collection}) => { const RequestHeaders = ({ item, collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const headers = item.draft ? get(item, 'draft.request.headers') : get(item, 'request.headers'); const headers = item.draft ? get(item, 'draft.request.headers') : get(item, 'request.headers');
const addHeader = () => { const addHeader = () => {
dispatch(addRequestHeader({ dispatch(
itemUid: item.uid, addRequestHeader({
collectionUid: collection.uid, itemUid: item.uid,
})); collectionUid: collection.uid
})
);
}; };
const handleHeaderValueChange = (e, _header, type) => { const handleHeaderValueChange = (e, _header, type) => {
const header = cloneDeep(_header); const header = cloneDeep(_header);
switch(type) { switch (type) {
case 'name' : { case 'name': {
header.name = e.target.value; header.name = e.target.value;
break; break;
} }
case 'value' : { case 'value': {
header.value = e.target.value; header.value = e.target.value;
break; break;
} }
case 'description' : { case 'description': {
header.description = e.target.value; header.description = e.target.value;
break; break;
} }
case 'enabled' : { case 'enabled': {
header.enabled = e.target.checked; header.enabled = e.target.checked;
break; break;
} }
} }
dispatch(updateRequestHeader({ dispatch(
header: header, updateRequestHeader({
itemUid: item.uid, header: header,
collectionUid: collection.uid itemUid: item.uid,
})); collectionUid: collection.uid
})
);
}; };
const handleRemoveHeader = (header) => { const handleRemoveHeader = (header) => {
dispatch(deleteRequestHeader({ dispatch(
headerUid: header.uid, deleteRequestHeader({
itemUid: item.uid, headerUid: header.uid,
collectionUid: collection.uid itemUid: item.uid,
})); collectionUid: collection.uid
})
);
}; };
return ( return (
<StyledWrapper className="w-full"> <StyledWrapper className="w-full">
<table> <table>
@ -64,56 +70,64 @@ const RequestHeaders = ({item, collection}) => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{headers && headers.length ? headers.map((header, index) => { {headers && headers.length
return ( ? headers.map((header, index) => {
<tr key={header.uid}> return (
<td> <tr key={header.uid}>
<input <td>
type="text" <input
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" type="text"
value={header.name} autoComplete="off"
className="mousetrap" autoCorrect="off"
onChange={(e) => handleHeaderValueChange(e, header, 'name')} autoCapitalize="off"
/> spellCheck="false"
</td> value={header.name}
<td> className="mousetrap"
<input onChange={(e) => handleHeaderValueChange(e, header, 'name')}
type="text" />
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" </td>
value={header.value} <td>
className="mousetrap" <input
onChange={(e) => handleHeaderValueChange(e, header, 'value')} type="text"
/> autoComplete="off"
</td> autoCorrect="off"
<td> autoCapitalize="off"
<input spellCheck="false"
type="text" value={header.value}
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" className="mousetrap"
value={header.description} onChange={(e) => handleHeaderValueChange(e, header, 'value')}
className="mousetrap" />
onChange={(e) => handleHeaderValueChange(e, header, 'description')} </td>
/> <td>
</td> <input
<td> type="text"
<div className="flex items-center"> autoComplete="off"
<input autoCorrect="off"
type="checkbox" autoCapitalize="off"
checked={header.enabled} spellCheck="false"
className="mr-3 mousetrap" value={header.description}
onChange={(e) => handleHeaderValueChange(e, header, 'enabled')} className="mousetrap"
/> onChange={(e) => handleHeaderValueChange(e, header, 'description')}
<button onClick={() => handleRemoveHeader(header)}> />
<IconTrash strokeWidth={1.5} size={20}/> </td>
</button> <td>
</div> <div className="flex items-center">
</td> <input type="checkbox" checked={header.enabled} className="mr-3 mousetrap" onChange={(e) => handleHeaderValueChange(e, header, 'enabled')} />
</tr> <button onClick={() => handleRemoveHeader(header)}>
); <IconTrash strokeWidth={1.5} size={20} />
}) : null} </button>
</div>
</td>
</tr>
);
})
: null}
</tbody> </tbody>
</table> </table>
<button className="btn-add-header select-none" onClick={addHeader}>+ Add Header</button> <button className="btn-add-header select-none" onClick={addHeader}>
+ Add Header
</button>
</StyledWrapper> </StyledWrapper>
) );
}; };
export default RequestHeaders; export default RequestHeaders;

View File

@ -1,53 +1,44 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { faFolder } from "@fortawesome/free-solid-svg-icons"; import { faFolder } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
import Modal from 'components//Modal'; import Modal from 'components//Modal';
const SaveRequest = ({items, onClose}) => { const SaveRequest = ({ items, onClose }) => {
const [showFolders, setShowFolders] = useState([]); const [showFolders, setShowFolders] = useState([]);
useEffect(() => { useEffect(() => {
setShowFolders(items || []); setShowFolders(items || []);
}, [items]) }, [items]);
const handleFolderClick = (folder) => { const handleFolderClick = (folder) => {
let subFolders = []; let subFolders = [];
if(folder.items && folder.items.length) { if (folder.items && folder.items.length) {
for (let item of folder.items) { for (let item of folder.items) {
if (item.items) { if (item.items) {
subFolders.push(item) subFolders.push(item);
} }
} }
if(subFolders.length) { if (subFolders.length) {
setShowFolders(subFolders); setShowFolders(subFolders);
} }
} }
} };
return ( return (
<StyledWrapper> <StyledWrapper>
<Modal <Modal size="md" title="Save Request" confirmText="Save" cancelText="Cancel" handleCancel={onClose} handleConfirm={onClose}>
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.map((folder) => ( {showFolders && showFolders.length
<div ? showFolders.map((folder) => (
key={folder.uid} <div key={folder.uid} className="folder-name" onClick={() => handleFolderClick(folder)}>
className="folder-name" <FontAwesomeIcon className="mr-3 text-gray-500" icon={faFolder} style={{ fontSize: 20 }} />
onClick={() => handleFolderClick(folder)} {folder.name}
> </div>
<FontAwesomeIcon className="mr-3 text-gray-500" icon={faFolder} style={{fontSize: 20}}/> ))
{folder.name} : null}
</div>
)): null}
</div> </div>
</Modal> </Modal>
</StyledWrapper> </StyledWrapper>

View File

@ -2,22 +2,22 @@ import React from 'react';
import { closeTabs } from 'providers/ReduxStore/slices/tabs'; import { closeTabs } from 'providers/ReduxStore/slices/tabs';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
const RequestNotFound = ({itemUid}) => { const RequestNotFound = ({ itemUid }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const closeTab = () => { const closeTab = () => {
dispatch(closeTabs({ dispatch(
tabUids: [itemUid] closeTabs({
})); tabUids: [itemUid]
})
);
}; };
return ( return (
<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"> <div className="mt-2">This can happen when the yml file associated with this request was deleted on your filesystem.</div>
This can happen when the yml 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

@ -27,4 +27,4 @@ const StyledWrapper = styled.div`
} }
`; `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -17,7 +17,7 @@ import useGraphqlSchema from '../../hooks/useGraphqlSchema';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const RequestTabPanel = () => { const RequestTabPanel = () => {
if(typeof window == 'undefined') { if (typeof window == 'undefined') {
return <div></div>; return <div></div>;
} }
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -28,12 +28,12 @@ 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 - 5); const [rightPaneWidth, setRightPaneWidth] = useState(screenWidth - asideWidth - leftPaneWidth - 5);
const [dragging, setDragging] = useState(false); const [dragging, setDragging] = useState(false);
useEffect(() => { useEffect(() => {
const leftPaneWidth = (screenWidth - asideWidth)/2.2; const leftPaneWidth = (screenWidth - asideWidth) / 2.2;
setLeftPaneWidth(leftPaneWidth); setLeftPaneWidth(leftPaneWidth);
}, [screenWidth]); }, [screenWidth]);
@ -42,20 +42,22 @@ const RequestTabPanel = () => {
}, [screenWidth, asideWidth, leftPaneWidth]); }, [screenWidth, asideWidth, leftPaneWidth]);
const handleMouseMove = (e) => { const handleMouseMove = (e) => {
if(dragging) { if (dragging) {
e.preventDefault(); e.preventDefault();
setLeftPaneWidth(e.clientX - asideWidth - 5); setLeftPaneWidth(e.clientX - asideWidth - 5);
setRightPaneWidth(screenWidth - (e.clientX) - 5); setRightPaneWidth(screenWidth - e.clientX - 5);
} }
}; };
const handleMouseUp = (e) => { const handleMouseUp = (e) => {
if(dragging) { if (dragging) {
e.preventDefault(); e.preventDefault();
setDragging(false); setDragging(false);
dispatch(updateRequestPaneTabWidth({ dispatch(
uid: activeTabUid, updateRequestPaneTabWidth({
requestPaneWidth: e.clientX - asideWidth - 5 uid: activeTabUid,
})); requestPaneWidth: e.clientX - asideWidth - 5
})
);
} }
}; };
const handleDragbarMouseDown = (e) => { const handleDragbarMouseDown = (e) => {
@ -78,38 +80,30 @@ const RequestTabPanel = () => {
}; };
}, [dragging, asideWidth]); }, [dragging, asideWidth]);
if (!activeTabUid) {
if(!activeTabUid) { return <Welcome />;
return (
<Welcome/>
);
} }
if(!focusedTab || !focusedTab.uid || !focusedTab.collectionUid) { if (!focusedTab || !focusedTab.uid || !focusedTab.collectionUid) {
return ( return <div className="pb-4 px-4">An error occured!</div>;
<div className="pb-4 px-4">An error occured!</div>
);
} }
let collection = find(collections, (c) => c.uid === focusedTab.collectionUid); let collection = find(collections, (c) => c.uid === focusedTab.collectionUid);
if(!collection || !collection.uid) { if (!collection || !collection.uid) {
return ( return <div className="pb-4 px-4">Collection not found!</div>;
<div className="pb-4 px-4">Collection not found!</div>
);
} }
const item = findItemInCollection(collection, activeTabUid); const item = findItemInCollection(collection, activeTabUid);
if(!item || !item.uid) { if (!item || !item.uid) {
return ( return <RequestNotFound itemUid={activeTabUid} />;
<RequestNotFound itemUid={activeTabUid}/> }
);
};
const handleRun = async () => { const handleRun = async () => {
dispatch(sendRequest(item, collection.uid)) dispatch(sendRequest(item, collection.uid)).catch((err) =>
.catch((err) => toast.custom((t) => (<NetworkError onClose={() => toast.dismiss(t.id)}/>), { toast.custom((t) => <NetworkError onClose={() => toast.dismiss(t.id)} />, {
duration: 5000 duration: 5000
})); })
);
}; };
const onGraphqlQueryChange = (value) => {}; const onGraphqlQueryChange = (value) => {};
const runQuery = async () => {}; const runQuery = async () => {};
@ -117,18 +111,11 @@ const RequestTabPanel = () => {
return ( return (
<StyledWrapper className={`flex flex-col flex-grow ${dragging ? 'dragging' : ''}`}> <StyledWrapper className={`flex flex-col flex-grow ${dragging ? 'dragging' : ''}`}>
<div className="pt-4 pb-3 px-4"> <div className="pt-4 pb-3 px-4">
<QueryUrl <QueryUrl item={item} collection={collection} handleRun={handleRun} />
item = {item}
collection={collection}
handleRun={handleRun}
/>
</div> </div>
<section className="main flex flex-grow pb-4"> <section className="main flex flex-grow pb-4">
<section className="request-pane"> <section className="request-pane">
<div <div className="px-4" style={{ width: `${leftPaneWidth}px`, height: 'calc(100% - 5px)' }}>
className="px-4"
style={{width: `${leftPaneWidth}px`, height: 'calc(100% - 5px)'}}
>
{item.type === 'graphql-request' ? ( {item.type === 'graphql-request' ? (
<GraphQLRequestPane <GraphQLRequestPane
onRunQuery={runQuery} onRunQuery={runQuery}
@ -139,13 +126,7 @@ const RequestTabPanel = () => {
/> />
) : null} ) : null}
{item.type === 'http-request' ? ( {item.type === 'http-request' ? <HttpRequestPane item={item} collection={collection} leftPaneWidth={leftPaneWidth} /> : null}
<HttpRequestPane
item={item}
collection={collection}
leftPaneWidth={leftPaneWidth}
/>
) : null}
</div> </div>
</section> </section>
@ -154,16 +135,11 @@ const RequestTabPanel = () => {
</div> </div>
<section className="response-pane flex-grow"> <section className="response-pane flex-grow">
<ResponsePane <ResponsePane item={item} collection={collection} rightPaneWidth={rightPaneWidth} response={item.response} />
item={item}
collection={collection}
rightPaneWidth={rightPaneWidth}
response={item.response}
/>
</section> </section>
</section> </section>
</StyledWrapper> </StyledWrapper>
) );
}; };
export default RequestTabPanel; export default RequestTabPanel;

View File

@ -1,6 +1,5 @@
import styled from 'styled-components'; import styled from 'styled-components';
const StyledWrapper = styled.div` const StyledWrapper = styled.div``;
`;
export default StyledWrapper; export default StyledWrapper;

View File

@ -3,20 +3,20 @@ import { IconFiles } from '@tabler/icons';
import EnvironmentSelector from 'components/Environments/EnvironmentSelector'; import EnvironmentSelector from 'components/Environments/EnvironmentSelector';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const CollectionToolBar = ({collection}) => { const CollectionToolBar = ({ collection }) => {
return ( return (
<StyledWrapper> <StyledWrapper>
<div className="flex items-center p-2"> <div className="flex items-center p-2">
<div className="flex flex-1 items-center"> <div className="flex flex-1 items-center">
<IconFiles size={18} strokeWidth={1.5}/> <IconFiles size={18} strokeWidth={1.5} />
<span className="ml-2 mr-4 font-semibold">{collection.name}</span> <span className="ml-2 mr-4 font-semibold">{collection.name}</span>
</div> </div>
<div className="flex flex-1 items-center justify-end"> <div className="flex flex-1 items-center justify-end">
<EnvironmentSelector collection={collection}/> <EnvironmentSelector collection={collection} />
</div> </div>
</div> </div>
</StyledWrapper> </StyledWrapper>
) );
}; };
export default CollectionToolBar; export default CollectionToolBar;

View File

@ -8,7 +8,7 @@ const StyledWrapper = styled.div`
.tab-name { .tab-name {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.close-icon-container { .close-icon-container {
@ -25,16 +25,16 @@ const StyledWrapper = styled.div`
padding-top: 6px; padding-top: 6px;
} }
&:hover, &:hover .close-icon { &:hover,
&:hover .close-icon {
background-color: #eaeaea; background-color: #eaeaea;
color: rgb(76 76 76); color: rgb(76 76 76);
} }
.has-changes-icon { .has-changes-icon {
height: 24px; height: 24px;
} }
} }
`; `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -6,21 +6,23 @@ import { findItemInCollection } from 'utils/collections';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
import { IconAlertTriangle } from '@tabler/icons'; import { IconAlertTriangle } from '@tabler/icons';
const RequestTab = ({tab, collection}) => { const RequestTab = ({ tab, collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const handleCloseClick = (event) => { const handleCloseClick = (event) => {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
dispatch(closeTabs({ dispatch(
tabUids: [tab.uid] closeTabs({
})) tabUids: [tab.uid]
})
);
}; };
const getMethodColor = (method = '') => { const getMethodColor = (method = '') => {
let color = ''; let color = '';
method = method.toLocaleLowerCase(); method = method.toLocaleLowerCase();
switch(method) { switch (method) {
case 'get': { case 'get': {
color = 'var(--color-method-get)'; color = 'var(--color-method-get)';
break; break;
@ -56,16 +58,19 @@ const RequestTab = ({tab, collection}) => {
const item = findItemInCollection(collection, tab.uid); const item = findItemInCollection(collection, tab.uid);
if(!item) { if (!item) {
return ( return (
<StyledWrapper className="flex items-center justify-between tab-container px-1"> <StyledWrapper className="flex items-center justify-between tab-container px-1">
<div className="flex items-center tab-label pl-2"> <div className="flex items-center tab-label pl-2">
<IconAlertTriangle size={18} strokeWidth={1.5} className="text-yellow-600"/> <IconAlertTriangle size={18} strokeWidth={1.5} className="text-yellow-600" />
<span className="ml-1">Not Found</span> <span className="ml-1">Not Found</span>
</div> </div>
<div className="flex px-2 close-icon-container" onClick={(e) => handleCloseClick(e)}> <div className="flex px-2 close-icon-container" onClick={(e) => handleCloseClick(e)}>
<svg focusable="false"xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon"> <svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
<path fill="currentColor" d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"></path> <path
fill="currentColor"
d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"
></path>
</svg> </svg>
</div> </div>
</StyledWrapper> </StyledWrapper>
@ -77,19 +82,26 @@ const RequestTab = ({tab, collection}) => {
return ( return (
<StyledWrapper className="flex items-center justify-between tab-container px-1"> <StyledWrapper className="flex items-center justify-between tab-container px-1">
<div className="flex items-baseline tab-label pl-2"> <div className="flex items-baseline tab-label pl-2">
<span className="tab-method uppercase" style={{color: getMethodColor(method), fontSize: 12}}>{method}</span> <span className="tab-method uppercase" style={{ color: getMethodColor(method), fontSize: 12 }}>
<span className="text-gray-700 ml-1 tab-name" title={item.name}>{item.name}</span> {method}
</span>
<span className="text-gray-700 ml-1 tab-name" title={item.name}>
{item.name}
</span>
</div> </div>
<div className="flex px-2 close-icon-container" onClick={(e) => handleCloseClick(e)}> <div className="flex px-2 close-icon-container" onClick={(e) => handleCloseClick(e)}>
{!item.draft ? ( {!item.draft ? (
<svg focusable="false"xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon"> <svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
<path fill="currentColor" d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"></path> <path
fill="currentColor"
d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"
></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>
) } )}
</div> </div>
</StyledWrapper> </StyledWrapper>
); );

View File

@ -46,7 +46,7 @@ const Wrapper = styled.div`
} }
} }
&:hover{ &:hover {
.close-icon-container .close-icon { .close-icon-container .close-icon {
display: block; display: block;
} }

View File

@ -21,38 +21,36 @@ const RequestTabs = () => {
const screenWidth = useSelector((state) => state.app.screenWidth); const screenWidth = useSelector((state) => state.app.screenWidth);
const getTabClassname = (tab, index) => { const getTabClassname = (tab, index) => {
return classnames("request-tab select-none", { return classnames('request-tab select-none', {
'active': tab.uid === activeTabUid, active: tab.uid === activeTabUid,
'last-tab': tabs && tabs.length && (index === tabs.length - 1) 'last-tab': tabs && tabs.length && index === tabs.length - 1
}); });
}; };
const handleClick = (tab) => { const handleClick = (tab) => {
dispatch(focusTab({ dispatch(
uid: tab.uid focusTab({
})); uid: tab.uid
})
);
}; };
const createNewTab = () => setNewRequestModalOpen(true); const createNewTab = () => setNewRequestModalOpen(true);
if(!activeTabUid) { if (!activeTabUid) {
return null; return null;
} }
const activeTab = find(tabs, (t) => t.uid === activeTabUid); const activeTab = find(tabs, (t) => t.uid === activeTabUid);
if(!activeTab) { if (!activeTab) {
return ( return <StyledWrapper>Something went wrong!</StyledWrapper>;
<StyledWrapper>
Something went wrong!
</StyledWrapper>
);
} }
const activeCollection = find(collections, (c) => c.uid === activeTab.collectionUid); const activeCollection = find(collections, (c) => c.uid === activeTab.collectionUid);
const collectionRequestTabs = filter(tabs, (t) => t.collectionUid === activeTab.collectionUid); const collectionRequestTabs = filter(tabs, (t) => t.collectionUid === activeTab.collectionUid);
const maxTablistWidth = screenWidth - leftSidebarWidth - 150; const maxTablistWidth = screenWidth - leftSidebarWidth - 150;
const tabsWidth = (collectionRequestTabs.length * 150) + 34; // 34: (+)icon const tabsWidth = collectionRequestTabs.length * 150 + 34; // 34: (+)icon
const showChevrons = maxTablistWidth < tabsWidth; const showChevrons = maxTablistWidth < tabsWidth;
const leftSlide = () => { const leftSlide = () => {
@ -81,16 +79,16 @@ 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} />
<div className="flex items-center pl-4"> <div className="flex items-center pl-4">
<ul role="tablist"> <ul role="tablist">
{showChevrons ? ( {showChevrons ? (
<li className="select-none short-tab" onClick={leftSlide}> <li className="select-none short-tab" onClick={leftSlide}>
<div className="flex items-center"> <div className="flex items-center">
<IconChevronLeft size={18} strokeWidth={1.5}/> <IconChevronLeft size={18} strokeWidth={1.5} />
</div> </div>
</li> </li>
) : null} ) : null}
@ -101,28 +99,30 @@ const RequestTabs = () => {
</div> </div>
</li> */} </li> */}
</ul> </ul>
<ul role="tablist" style={{maxWidth: maxTablistWidth}} ref={tabsRef}> <ul role="tablist" style={{ maxWidth: maxTablistWidth }} ref={tabsRef}>
{collectionRequestTabs && collectionRequestTabs.length ? collectionRequestTabs.map((tab, index) => { {collectionRequestTabs && collectionRequestTabs.length
return ( ? collectionRequestTabs.map((tab, index) => {
<li key={tab.uid} className={getTabClassname(tab, index)} role="tab" onClick={() => handleClick(tab)}> return (
<RequestTab key={tab.uid} tab={tab} collection={activeCollection} activeTab={activeTab}/> <li key={tab.uid} className={getTabClassname(tab, index)} role="tab" onClick={() => handleClick(tab)}>
</li> <RequestTab key={tab.uid} tab={tab} collection={activeCollection} activeTab={activeTab} />
) </li>
}) : null} );
})
: null}
</ul> </ul>
<ul role="tablist"> <ul role="tablist">
{showChevrons ? ( {showChevrons ? (
<li className="select-none short-tab" onClick={rightSlide}> <li className="select-none short-tab" onClick={rightSlide}>
<div className="flex items-center"> <div className="flex items-center">
<IconChevronRight size={18} strokeWidth={1.5}/> <IconChevronRight size={18} strokeWidth={1.5} />
</div> </div>
</li> </li>
) : null} ) : null}
<li className="select-none short-tab" onClick={createNewTab}> <li className="select-none short-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>
</li> </li>

View File

@ -1,30 +1,27 @@
import React from 'react'; import React from 'react';
const NetworkError = ({onClose}) => { const NetworkError = ({ onClose }) => {
return ( return (
<div className="max-w-md w-full bg-white shadow-lg rounded-lg pointer-events-auto flex bg-red-100"> <div className="max-w-md w-full bg-white shadow-lg rounded-lg pointer-events-auto flex bg-red-100">
<div className="flex-1 w-0 p-4"> <div className="flex-1 w-0 p-4">
<div className="flex items-start"> <div className="flex items-start">
<div className="ml-3 flex-1"> <div className="ml-3 flex-1">
<p className="text-sm font-medium text-red-800"> <p className="text-sm font-medium text-red-800">Network Error</p>
Network Error <p className="mt-2 text-xs text-gray-500">
</p> Please note that if you are using Bruno on the web, then the api you are connecting to must allow CORS. If not, please use the chrome extension or the desktop app
<p className="mt-2 text-xs text-gray-500"> </p>
Please note that if you are using Bruno on the web, then the api you are connecting to must allow CORS.
If not, please use the chrome extension or the desktop app
</p>
</div>
</div> </div>
</div> </div>
<div className="flex">
<button
onClick={onClose}
className="w-full border border-transparent rounded-none rounded-r-lg p-4 flex items-center justify-center text-sm font-medium focus:outline-none"
>
Close
</button>
</div>
</div> </div>
<div className="flex">
<button
onClick={onClose}
className="w-full border border-transparent rounded-none rounded-r-lg p-4 flex items-center justify-center text-sm font-medium focus:outline-none"
>
Close
</button>
</div>
</div>
); );
}; };

View File

@ -1,7 +1,7 @@
import styled from 'styled-components'; import styled from 'styled-components';
const StyledWrapper = styled.div` const StyledWrapper = styled.div`
div.overlay{ div.overlay {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
@ -18,4 +18,3 @@ const StyledWrapper = styled.div`
`; `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -5,7 +5,7 @@ import { cancelRequest } from 'providers/ReduxStore/slices/collections/actions';
import StopWatch from '../../StopWatch'; import StopWatch from '../../StopWatch';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const ResponseLoadingOverlay = ({item, collection}) => { const ResponseLoadingOverlay = ({ item, collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const handleCancelRequest = () => { const handleCancelRequest = () => {
@ -15,15 +15,16 @@ const ResponseLoadingOverlay = ({item, collection}) => {
return ( return (
<StyledWrapper className="mt-4 px-3 w-full"> <StyledWrapper className="mt-4 px-3 w-full">
<div className="overlay"> <div className="overlay">
<div style={{marginBottom: 15, fontSize: 26}}> <div style={{ marginBottom: 15, fontSize: 26 }}>
<div style={{display: 'inline-block', fontSize: 24, marginLeft: 5, marginRight: 5}}> <div style={{ display: 'inline-block', fontSize: 24, marginLeft: 5, marginRight: 5 }}>
<StopWatch/> <StopWatch />
</div> </div>
</div> </div>
<IconRefresh size={24} className="animate-spin"/> <IconRefresh size={24} className="animate-spin" />
<button <button
onClick={handleCancelRequest} onClick={handleCancelRequest}
className="mt-4 uppercase bg-gray-200 active:bg-blueGray-600 text-xs px-4 py-2 rounded shadow hover:shadow-md outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150" type="button" className="mt-4 uppercase bg-gray-200 active:bg-blueGray-600 text-xs px-4 py-2 rounded shadow hover:shadow-md outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150"
type="button"
> >
Cancel Request Cancel Request
</button> </button>

View File

@ -6,4 +6,3 @@ const StyledWrapper = styled.div`
`; `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -5,8 +5,8 @@ import StyledWrapper from './StyledWrapper';
const Placeholder = () => { const Placeholder = () => {
return ( return (
<StyledWrapper> <StyledWrapper>
<div className="text-gray-300 flex justify-center" style={{fontSize: 200}}> <div className="text-gray-300 flex justify-center" style={{ fontSize: 200 }}>
<IconSend size={150} strokeWidth={1}/> <IconSend size={150} strokeWidth={1} />
</div> </div>
<div className="flex mt-4"> <div className="flex mt-4">
<div className="flex flex-1 flex-col items-end px-1"> <div className="flex flex-1 flex-col items-end px-1">

View File

@ -8,4 +8,3 @@ const StyledWrapper = styled.div`
`; `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -2,11 +2,11 @@ import React from 'react';
import CodeEditor from 'components/CodeEditor'; import CodeEditor from 'components/CodeEditor';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const QueryResult = ({value, width}) => { const QueryResult = ({ value, width }) => {
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 value={value || ''} readOnly/> <CodeEditor value={value || ''} readOnly />
</div> </div>
</StyledWrapper> </StyledWrapper>
); );

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const ResponseHeaders = ({headers}) => { const ResponseHeaders = ({ headers }) => {
return ( return (
<StyledWrapper className="px-3 pb-4 w-full"> <StyledWrapper className="px-3 pb-4 w-full">
<table> <table>
@ -12,17 +12,19 @@ const ResponseHeaders = ({headers}) => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{headers && headers.length ? headers.map((header, index) => { {headers && headers.length
return ( ? headers.map((header, index) => {
<tr key={index}> return (
<td className="key">{header[0]}</td> <tr key={index}>
<td className="value">{header[1]}</td> <td className="key">{header[0]}</td>
</tr> <td className="value">{header[1]}</td>
); </tr>
}) : null} );
})
: null}
</tbody> </tbody>
</table> </table>
</StyledWrapper> </StyledWrapper>
) );
}; };
export default ResponseHeaders; export default ResponseHeaders;

View File

@ -1,21 +1,18 @@
import React from 'react'; import React from 'react';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const ResponseSize = ({size}) => { const ResponseSize = ({ size }) => {
let sizeToDisplay = '' let sizeToDisplay = '';
if(size > 1024 ) { // size is greater than 1kb if (size > 1024) {
let kb = Math.floor(size / 1024); // size is greater than 1kb
let decimal = ((size % 1024) / 1024).toFixed(2) * 100; let kb = Math.floor(size / 1024);
sizeToDisplay = kb + '.' + decimal + 'KB'; let decimal = ((size % 1024) / 1024).toFixed(2) * 100;
sizeToDisplay = kb + '.' + decimal + 'KB';
} else { } else {
sizeToDisplay = size + 'B' sizeToDisplay = size + 'B';
} }
return ( return <StyledWrapper className="ml-4">{sizeToDisplay}</StyledWrapper>;
<StyledWrapper className="ml-4">
{sizeToDisplay}
</StyledWrapper>
)
}; };
export default ResponseSize; export default ResponseSize;

View File

@ -1,21 +1,18 @@
import React from 'react'; import React from 'react';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const ResponseTime = ({duration}) => { const ResponseTime = ({ duration }) => {
let durationToDisplay = '' let durationToDisplay = '';
if(duration > 1000 ) { // duration greater than a second if (duration > 1000) {
let seconds = Math.floor(duration / 1000); // duration greater than a second
let decimal = ((duration % 1000) / 1000) * 100; let seconds = Math.floor(duration / 1000);
durationToDisplay = seconds + '.' + decimal.toFixed(0) + 's'; let decimal = ((duration % 1000) / 1000) * 100;
durationToDisplay = seconds + '.' + decimal.toFixed(0) + 's';
} else { } else {
durationToDisplay = duration + 'ms' durationToDisplay = duration + 'ms';
} }
return ( return <StyledWrapper className="ml-4">{durationToDisplay}</StyledWrapper>;
<StyledWrapper className="ml-4">
{durationToDisplay}
</StyledWrapper>
)
}; };
export default ResponseTime; export default ResponseTime;

View File

@ -41,7 +41,7 @@ const statusCodePhraseMap = {
415: 'Unsupported Media Type', 415: 'Unsupported Media Type',
416: 'Range Not Satisfiable', 416: 'Range Not Satisfiable',
417: 'Expectation Failed', 417: 'Expectation Failed',
418: 'I\'m a teapot', 418: "I'm a teapot",
421: 'Misdirected Request', 421: 'Misdirected Request',
422: 'Unprocessable Entity', 422: 'Unprocessable Entity',
423: 'Locked', 423: 'Locked',

View File

@ -3,7 +3,7 @@ import classnames from 'classnames';
import statusCodePhraseMap from './get-status-code-phrase'; import statusCodePhraseMap from './get-status-code-phrase';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const StatusCode = ({status}) => { const StatusCode = ({ status }) => {
const getTabClassname = () => { const getTabClassname = () => {
return classnames('', { return classnames('', {
'text-blue-700': status >= 100 && status < 200, 'text-blue-700': status >= 100 && status < 200,
@ -18,6 +18,6 @@ const StatusCode = ({status}) => {
<StyledWrapper className={getTabClassname()}> <StyledWrapper className={getTabClassname()}>
{status} {statusCodePhraseMap[status]} {status} {statusCodePhraseMap[status]}
</StyledWrapper> </StyledWrapper>
) );
}; };
export default StatusCode; export default StatusCode;

View File

@ -10,7 +10,11 @@ const StyledWrapper = styled.div`
color: var(--color-tab-inactive); color: var(--color-tab-inactive);
cursor: pointer; cursor: pointer;
&:focus, &:active, &:focus-within, &:focus-visible, &:target { &:focus,
&:active,
&:focus-within,
&:focus-visible,
&:target {
outline: none !important; outline: none !important;
box-shadow: none !important; box-shadow: none !important;
} }
@ -23,4 +27,4 @@ const StyledWrapper = styled.div`
} }
`; `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -12,35 +12,30 @@ import ResponseTime from './ResponseTime';
import ResponseSize from './ResponseSize'; import ResponseSize from './ResponseSize';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const ResponsePane = ({rightPaneWidth, item, collection}) => { const ResponsePane = ({ rightPaneWidth, item, collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
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 isLoading = item.response && item.response.state === 'sending'; const isLoading = item.response && item.response.state === 'sending';
const selectTab = (tab) => { const selectTab = (tab) => {
dispatch(updateResponsePaneTab({ dispatch(
uid: item.uid, updateResponsePaneTab({
responsePaneTab: tab uid: item.uid,
})) responsePaneTab: tab
})
);
}; };
const response = item.response || {}; const response = item.response || {};
const getTabPanel = (tab) => { const getTabPanel = (tab) => {
switch(tab) { switch (tab) {
case 'response': { case 'response': {
return ( return <QueryResult width={rightPaneWidth} value={response.data ? JSON.stringify(response.data, null, 2) : ''} />;
<QueryResult
width={rightPaneWidth}
value={response.data ? JSON.stringify(response.data, null, 2) : ''}
/>
);
} }
case 'headers': { case 'headers': {
return ( return <ResponseHeaders headers={response.headers} />;
<ResponseHeaders headers={response.headers}/>
);
} }
default: { default: {
@ -49,15 +44,15 @@ const ResponsePane = ({rightPaneWidth, item, collection}) => {
} }
}; };
if(isLoading) { if (isLoading) {
return ( return (
<StyledWrapper className="flex h-full relative"> <StyledWrapper className="flex h-full relative">
<Overlay item={item} collection={collection}/> <Overlay item={item} collection={collection} />
</StyledWrapper> </StyledWrapper>
); );
} }
if(response.state !== 'success') { if (response.state !== 'success') {
return ( return (
<StyledWrapper className="flex h-full relative"> <StyledWrapper className="flex h-full relative">
<Placeholder /> <Placeholder />
@ -65,43 +60,41 @@ const ResponsePane = ({rightPaneWidth, item, collection}) => {
); );
} }
if(!activeTabUid) { if (!activeTabUid) {
return ( return <div>Something went wrong</div>;
<div>Something went wrong</div>
);
} }
const focusedTab = find(tabs, (t) => t.uid === activeTabUid); const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
if(!focusedTab || !focusedTab.uid || !focusedTab.responsePaneTab) { if (!focusedTab || !focusedTab.uid || !focusedTab.responsePaneTab) {
return ( return <div className="pb-4 px-4">An error occured!</div>;
<div className="pb-4 px-4">An error occured!</div>
);
} }
const getTabClassname = (tabName) => { const getTabClassname = (tabName) => {
return classnames(`tab select-none ${tabName}`, { return classnames(`tab select-none ${tabName}`, {
'active': tabName === focusedTab.responsePaneTab active: tabName === focusedTab.responsePaneTab
}); });
}; };
return ( return (
<StyledWrapper className="flex flex-col h-full relative"> <StyledWrapper className="flex flex-col h-full relative">
<div className="flex items-center px-3 tabs" role="tablist"> <div className="flex items-center px-3 tabs" role="tablist">
<div className={getTabClassname('response')} role="tab" onClick={() => selectTab('response')}>Response</div> <div className={getTabClassname('response')} role="tab" onClick={() => selectTab('response')}>
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>Headers</div> Response
</div>
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
Headers
</div>
{!isLoading ? ( {!isLoading ? (
<div className="flex flex-grow justify-end items-center"> <div className="flex flex-grow justify-end items-center">
<StatusCode status={response.status}/> <StatusCode status={response.status} />
<ResponseTime duration={response.duration}/> <ResponseTime duration={response.duration} />
<ResponseSize size={response.size}/> <ResponseSize size={response.size} />
</div> </div>
) : null } ) : null}
</div> </div>
<section className="flex flex-grow mt-5"> <section className="flex flex-grow mt-5">{getTabPanel(focusedTab.responsePaneTab)}</section>
{getTabPanel(focusedTab.responsePaneTab)}
</section>
</StyledWrapper> </StyledWrapper>
) );
}; };
export default ResponsePane; export default ResponsePane;

View File

@ -6,20 +6,17 @@ import { useDispatch } from 'react-redux';
import { isItemAFolder } from 'utils/tabs'; import { isItemAFolder } from 'utils/tabs';
import { cloneItem } from 'providers/ReduxStore/slices/collections/actions'; import { cloneItem } from 'providers/ReduxStore/slices/collections/actions';
const CloneCollectionItem = ({collection, item, onClose}) => { const CloneCollectionItem = ({ collection, item, onClose }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const isFolder = isItemAFolder(item); const isFolder = isItemAFolder(item);
const inputRef = useRef(); const inputRef = useRef();
const formik = useFormik({ const formik = useFormik({
enableReinitialize: true, enableReinitialize: true,
initialValues: { initialValues: {
name: item.name name: item.name
}, },
validationSchema: Yup.object({ validationSchema: Yup.object({
name: Yup.string() name: Yup.string().min(1, 'must be atleast 1 characters').max(50, 'must be 50 characters or less').required('name is required')
.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));
@ -28,7 +25,7 @@ const CloneCollectionItem = ({collection, item, onClose}) => {
}); });
useEffect(() => { useEffect(() => {
if(inputRef && inputRef.current) { if (inputRef && inputRef.current) {
inputRef.current.focus(); inputRef.current.focus();
} }
}, [inputRef]); }, [inputRef]);
@ -36,27 +33,26 @@ const CloneCollectionItem = ({collection, item, onClose}) => {
const onSubmit = () => formik.handleSubmit(); const onSubmit = () => formik.handleSubmit();
return ( return (
<Modal <Modal size="sm" title={`Clone ${isFolder ? 'Folder' : 'Request'}`} confirmText="Clone" handleConfirm={onSubmit} handleCancel={onClose}>
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">{isFolder ? 'Folder' : 'Request'} Name</label> <label htmlFor="name" className="block font-semibold">
{isFolder ? 'Folder' : 'Request'} Name
</label>
<input <input
id="collection-item-name" type="text" name="name" id="collection-item-name"
type="text"
name="name"
ref={inputRef} ref={inputRef}
className="block textbox mt-2 w-full" className="block textbox mt-2 w-full"
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange} onChange={formik.handleChange}
value={formik.values.name || ''} value={formik.values.name || ''}
/> />
{formik.touched.name && formik.errors.name ? ( {formik.touched.name && formik.errors.name ? <div className="text-red-500">{formik.errors.name}</div> : null}
<div className="text-red-500">{formik.errors.name}</div>
) : null}
</div> </div>
</form> </form>
</Modal> </Modal>

View File

@ -7,34 +7,31 @@ import { deleteItem } from 'providers/ReduxStore/slices/collections/actions';
import { recursivelyGetAllItemUids } from 'utils/collections'; import { recursivelyGetAllItemUids } from 'utils/collections';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const DeleteCollectionItem = ({onClose, item, collection}) => { const DeleteCollectionItem = ({ onClose, item, collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const isFolder = isItemAFolder(item); const isFolder = isItemAFolder(item);
const onConfirm = () =>{ const onConfirm = () => {
dispatch(deleteItem(item.uid, collection.uid)) dispatch(deleteItem(item.uid, collection.uid)).then(() => {
.then(() => { if (isFolder) {
if(isFolder) { dispatch(
dispatch(closeTabs({ closeTabs({
tabUids: recursivelyGetAllItemUids(item.items) tabUids: recursivelyGetAllItemUids(item.items)
})); })
} else { );
dispatch(closeTabs({ } else {
dispatch(
closeTabs({
tabUids: [item.uid] tabUids: [item.uid]
})); })
} );
}); }
});
onClose(); onClose();
}; };
return ( return (
<StyledWrapper> <StyledWrapper>
<Modal <Modal size="sm" title={`Delete ${isFolder ? 'Folder' : 'Request'}`} confirmText="Delete" handleConfirm={onConfirm} handleCancel={onClose}>
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

@ -6,20 +6,17 @@ import { useDispatch } from 'react-redux';
import { isItemAFolder } from 'utils/tabs'; import { isItemAFolder } from 'utils/tabs';
import { renameItem } from 'providers/ReduxStore/slices/collections/actions'; import { renameItem } from 'providers/ReduxStore/slices/collections/actions';
const RenameCollectionItem = ({collection, item, onClose}) => { const RenameCollectionItem = ({ collection, item, onClose }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const isFolder = isItemAFolder(item); const isFolder = isItemAFolder(item);
const inputRef = useRef(); const inputRef = useRef();
const formik = useFormik({ const formik = useFormik({
enableReinitialize: true, enableReinitialize: true,
initialValues: { initialValues: {
name: item.name name: item.name
}, },
validationSchema: Yup.object({ validationSchema: Yup.object({
name: Yup.string() name: Yup.string().min(1, 'must be atleast 1 characters').max(50, 'must be 50 characters or less').required('name is required')
.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));
@ -28,7 +25,7 @@ const RenameCollectionItem = ({collection, item, onClose}) => {
}); });
useEffect(() => { useEffect(() => {
if(inputRef && inputRef.current) { if (inputRef && inputRef.current) {
inputRef.current.focus(); inputRef.current.focus();
} }
}, [inputRef]); }, [inputRef]);
@ -36,27 +33,26 @@ const RenameCollectionItem = ({collection, item, onClose}) => {
const onSubmit = () => formik.handleSubmit(); const onSubmit = () => formik.handleSubmit();
return ( return (
<Modal <Modal size="sm" title={`Rename ${isFolder ? 'Folder' : 'Request'}`} confirmText="Rename" handleConfirm={onSubmit} handleCancel={onClose}>
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">{isFolder ? 'Folder' : 'Request'} Name</label> <label htmlFor="name" className="block font-semibold">
{isFolder ? 'Folder' : 'Request'} Name
</label>
<input <input
id="collection-item-name" type="text" name="name" id="collection-item-name"
type="text"
name="name"
ref={inputRef} ref={inputRef}
className="block textbox mt-2 w-full" className="block textbox mt-2 w-full"
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange} onChange={formik.handleChange}
value={formik.values.name || ''} value={formik.values.name || ''}
/> />
{formik.touched.name && formik.errors.name ? ( {formik.touched.name && formik.errors.name ? <div className="text-red-500">{formik.errors.name}</div> : null}
<div className="text-red-500">{formik.errors.name}</div>
) : null}
</div> </div>
</form> </form>
</Modal> </Modal>

View File

@ -12,13 +12,27 @@ const Wrapper = styled.div`
top: 1px; top: 1px;
} }
.method-get { color: var(--color-method-get);} .method-get {
.method-post { color: var(--color-method-post);} color: var(--color-method-get);
.method-put { color: var(--color-method-put);} }
.method-delete { color: var(--color-method-delete);} .method-post {
.method-patch { color: var(--color-method-patch);} color: var(--color-method-post);
.method-options { color: var(--color-method-options);} }
.method-head { color: var(--color-method-head);} .method-put {
color: var(--color-method-put);
}
.method-delete {
color: var(--color-method-delete);
}
.method-patch {
color: var(--color-method-patch);
}
.method-options {
color: var(--color-method-options);
}
.method-head {
color: var(--color-method-head);
}
`; `;
export default Wrapper; export default Wrapper;

View File

@ -2,14 +2,14 @@ import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const RequestMethod = ({item}) => { const RequestMethod = ({ item }) => {
if(!['http-request', 'graphql-request'].includes(item.type)) { if (!['http-request', 'graphql-request'].includes(item.type)) {
return null; return null;
} }
const getClassname = (method = '') => { const getClassname = (method = '') => {
method = method.toLocaleLowerCase(); method = method.toLocaleLowerCase();
return classnames("mr-1", { return classnames('mr-1', {
'method-get': method === 'get', 'method-get': method === 'get',
'method-post': method === 'post', 'method-post': method === 'post',
'method-put': method === 'put', 'method-put': method === 'put',

View File

@ -4,11 +4,11 @@ const Wrapper = styled.div`
.menu-icon { .menu-icon {
color: rgb(110 110 110); color: rgb(110 110 110);
.dropdown { .dropdown {
div[aria-expanded="true"] { div[aria-expanded='true'] {
visibility: visible; visibility: visible;
} }
div[aria-expanded="false"] { div[aria-expanded='false'] {
visibility: hidden; visibility: hidden;
} }
} }
@ -37,7 +37,7 @@ const Wrapper = styled.div`
background: #e7e7e7; background: #e7e7e7;
.menu-icon { .menu-icon {
.dropdown { .dropdown {
div[aria-expanded="false"] { div[aria-expanded='false'] {
visibility: visible; visibility: visible;
} }
} }

View File

@ -19,7 +19,7 @@ import { hideHomePage } from 'providers/ReduxStore/slices/app';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const CollectionItem = ({item, collection, searchText}) => { const CollectionItem = ({ item, collection, searchText }) => {
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 isDragging = useSelector((state) => state.app.isDragging); const isDragging = useSelector((state) => state.app.isDragging);
@ -44,7 +44,7 @@ const CollectionItem = ({item, collection, searchText}) => {
const MenuIcon = forwardRef((props, ref) => { const MenuIcon = forwardRef((props, ref) => {
return ( return (
<div ref={ref}> <div ref={ref}>
<IconDots size={22}/> <IconDots size={22} />
</div> </div>
); );
}); });
@ -58,43 +58,49 @@ const CollectionItem = ({item, collection, searchText}) => {
}); });
const handleClick = (event) => { const handleClick = (event) => {
if(isItemARequest(item)) { if (isItemARequest(item)) {
if(itemIsOpenedInTabs(item, tabs)) { if (itemIsOpenedInTabs(item, tabs)) {
dispatch(focusTab({ dispatch(
uid: item.uid focusTab({
})); uid: item.uid
})
);
} else { } else {
dispatch(addTab({ dispatch(
uid: item.uid, addTab({
collectionUid: collection.uid uid: item.uid,
})); collectionUid: collection.uid
})
);
} }
dispatch(hideHomePage()); dispatch(hideHomePage());
} else { } else {
dispatch(collectionFolderClicked({ dispatch(
itemUid: item.uid, collectionFolderClicked({
collectionUid: collection.uid itemUid: item.uid,
})); collectionUid: collection.uid
})
);
} }
}; };
let indents = range(item.depth); let indents = range(item.depth);
const onDropdownCreate = (ref) => dropdownTippyRef.current = ref; const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const isFolder = isItemAFolder(item); const isFolder = isItemAFolder(item);
const className = classnames('flex flex-col w-full', { const className = classnames('flex flex-col w-full', {
'is-dragging': isDragging 'is-dragging': isDragging
}); });
if(searchText && searchText.length) { if (searchText && searchText.length) {
if(isItemARequest(item)) { if (isItemARequest(item)) {
if(!doesRequestMatchSearchText(item, searchText)) { if (!doesRequestMatchSearchText(item, searchText)) {
return null; return null;
} }
} else { } else {
if (!doesFolderHaveItemsMatchSearchText(item, searchText)) { if (!doesFolderHaveItemsMatchSearchText(item, searchText)) {
return null; return null;
}; }
} }
} }
@ -103,83 +109,100 @@ const CollectionItem = ({item, collection, searchText}) => {
return ( return (
<StyledWrapper className={className}> <StyledWrapper className={className}>
{renameItemModalOpen && <RenameCollectionItem item={item} collection={collection} onClose={() => setRenameItemModalOpen(false)}/>} {renameItemModalOpen && <RenameCollectionItem item={item} collection={collection} onClose={() => setRenameItemModalOpen(false)} />}
{cloneItemModalOpen && <CloneCollectionItem item={item} collection={collection} onClose={() => setCloneItemModalOpen(false)}/>} {cloneItemModalOpen && <CloneCollectionItem item={item} collection={collection} onClose={() => setCloneItemModalOpen(false)} />}
{deleteItemModalOpen && <DeleteCollectionItem item={item} collection={collection} onClose={() => setDeleteItemModalOpen(false)}/>} {deleteItemModalOpen && <DeleteCollectionItem item={item} collection={collection} onClose={() => setDeleteItemModalOpen(false)} />}
{newRequestModalOpen && <NewRequest item={item} collection={collection} onClose={() => setNewRequestModalOpen(false)}/>} {newRequestModalOpen && <NewRequest item={item} collection={collection} onClose={() => setNewRequestModalOpen(false)} />}
{newFolderModalOpen && <NewFolder item={item} collection={collection} onClose={() => setNewFolderModalOpen(false)}/>} {newFolderModalOpen && <NewFolder item={item} collection={collection} onClose={() => setNewFolderModalOpen(false)} />}
<div className={itemRowClassName}> <div className={itemRowClassName}>
<div className="flex items-center h-full w-full"> <div className="flex items-center h-full w-full">
{indents && indents.length ? indents.map((i) => { {indents && indents.length
return ( ? indents.map((i) => {
<div return (
onClick={handleClick} <div
className="indent-block" onClick={handleClick}
key={i} className="indent-block"
style = {{ key={i}
width: 16, style={{
minWidth: 16, width: 16,
height: '100%' minWidth: 16,
}} height: '100%'
> }}
&nbsp;{/* Indent */} >
</div> &nbsp;{/* Indent */}
); </div>
}) : null} );
})
: null}
<div <div
onClick={handleClick} onClick={handleClick}
className="flex flex-grow items-center h-full overflow-hidden" className="flex flex-grow items-center h-full overflow-hidden"
style = {{ style={{
paddingLeft: 8 paddingLeft: 8
}} }}
> >
<div style={{width:16, minWidth: 16}}> <div style={{ width: 16, minWidth: 16 }}>
{isFolder ? ( {isFolder ? <IconChevronRight size={16} strokeWidth={2} className={iconClassName} style={{ color: 'rgb(160 160 160)' }} /> : null}
<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">
<RequestMethod item={item}/> <RequestMethod item={item} />
<span className="item-name" title={item.name}>{item.name}</span> <span className="item-name" title={item.name}>
{item.name}
</span>
</div> </div>
</div> </div>
<div className="menu-icon pr-2"> <div className="menu-icon pr-2">
<Dropdown onCreate={onDropdownCreate} icon={<MenuIcon />} placement='bottom-start'> <Dropdown onCreate={onDropdownCreate} icon={<MenuIcon />} placement="bottom-start">
{isFolder && ( {isFolder && (
<> <>
<div className="dropdown-item" onClick={(e) => { <div
dropdownTippyRef.current.hide(); className="dropdown-item"
setNewRequestModalOpen(true); onClick={(e) => {
}}> dropdownTippyRef.current.hide();
setNewRequestModalOpen(true);
}}
>
New Request New Request
</div> </div>
<div className="dropdown-item" onClick={(e) => { <div
dropdownTippyRef.current.hide(); className="dropdown-item"
setNewFolderModalOpen(true); onClick={(e) => {
}}> dropdownTippyRef.current.hide();
setNewFolderModalOpen(true);
}}
>
New Folder New Folder
</div> </div>
</> </>
)} )}
<div className="dropdown-item" onClick={(e) => { <div
dropdownTippyRef.current.hide(); className="dropdown-item"
setRenameItemModalOpen(true); onClick={(e) => {
}}> dropdownTippyRef.current.hide();
setRenameItemModalOpen(true);
}}
>
Rename Rename
</div> </div>
{!isFolder && ( {!isFolder && (
<div className="dropdown-item" onClick={(e) => { <div
dropdownTippyRef.current.hide(); className="dropdown-item"
setCloneItemModalOpen(true); onClick={(e) => {
}}> dropdownTippyRef.current.hide();
setCloneItemModalOpen(true);
}}
>
Clone Clone
</div> </div>
)} )}
<div className="dropdown-item delete-item" onClick={(e) => { <div
dropdownTippyRef.current.hide(); className="dropdown-item delete-item"
setDeleteItemModalOpen(true); onClick={(e) => {
}}> dropdownTippyRef.current.hide();
setDeleteItemModalOpen(true);
}}
>
Delete Delete
</div> </div>
</Dropdown> </Dropdown>
@ -189,26 +212,20 @@ const CollectionItem = ({item, collection, searchText}) => {
{!itemIsCollapsed ? ( {!itemIsCollapsed ? (
<div> <div>
{requestItems && requestItems.length ? requestItems.map((i) => { {requestItems && requestItems.length
return <CollectionItem ? requestItems.map((i) => {
key={i.uid} return <CollectionItem key={i.uid} item={i} collection={collection} searchText={searchText} />;
item={i} })
collection={collection} : null}
searchText={searchText} {folderItems && folderItems.length
/> ? folderItems.map((i) => {
}) : null} return <CollectionItem key={i.uid} item={i} collection={collection} searchText={searchText} />;
{folderItems && folderItems.length ? folderItems.map((i) => { })
return <CollectionItem : null}
key={i.uid}
item={i}
collection={collection}
searchText={searchText}
/>
}) : null}
</div> </div>
) : null} ) : null}
</StyledWrapper> </StyledWrapper>
); );
}; };
export default CollectionItem; export default CollectionItem;

View File

@ -5,25 +5,19 @@ import { useDispatch } from 'react-redux';
import { deleteCollection } from 'providers/ReduxStore/slices/collections/actions'; import { deleteCollection } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const DeleteCollection = ({onClose, collection}) => { const DeleteCollection = ({ onClose, collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const onConfirm = () =>{ const onConfirm = () => {
dispatch(deleteCollection(collection.uid)) dispatch(deleteCollection(collection.uid))
.then(() => { .then(() => {
toast.success("Collection deleted"); toast.success('Collection deleted');
}) })
.catch(() => toast.error("An error occured while deleting the collection")); .catch(() => toast.error('An error occured while deleting the collection'));
}; };
return ( return (
<StyledWrapper> <StyledWrapper>
<Modal <Modal size="sm" title="Delete Collection" confirmText="Delete" handleConfirm={onConfirm} handleCancel={onClose}>
size="sm"
title="Delete Collection"
confirmText="Delete"
handleConfirm={onConfirm}
handleCancel={onClose}
>
Are you sure you want to delete the collection <span className="font-semibold">{collection.name}</span> ? Are you sure you want to delete the collection <span className="font-semibold">{collection.name}</span> ?
</Modal> </Modal>
</StyledWrapper> </StyledWrapper>

View File

@ -7,30 +7,26 @@ import { removeCollectionFromWorkspace } from 'providers/ReduxStore/slices/works
import { removeLocalCollection } from 'providers/ReduxStore/slices/collections/actions'; import { removeLocalCollection } from 'providers/ReduxStore/slices/collections/actions';
import { closeTabs } from 'providers/ReduxStore/slices/tabs'; import { closeTabs } from 'providers/ReduxStore/slices/tabs';
const RemoveCollectionFromWorkspace = ({onClose, collection}) => { const RemoveCollectionFromWorkspace = ({ onClose, collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { activeWorkspaceUid } = useSelector((state) => state.workspaces); const { activeWorkspaceUid } = useSelector((state) => state.workspaces);
const onConfirm = () =>{ const onConfirm = () => {
dispatch(removeCollectionFromWorkspace(activeWorkspaceUid, collection.uid)) dispatch(removeCollectionFromWorkspace(activeWorkspaceUid, collection.uid))
.then(() => { .then(() => {
dispatch(closeTabs({ dispatch(
tabUids: recursivelyGetAllItemUids(collection.items) closeTabs({
})); tabUids: recursivelyGetAllItemUids(collection.items)
})
);
}) })
.then(() => dispatch(removeLocalCollection(collection.uid))) .then(() => dispatch(removeLocalCollection(collection.uid)))
.then(() => toast.success("Collection removed from workspace")) .then(() => toast.success('Collection removed from workspace'))
.catch((err) => console.log(err) && toast.error("An error occured while removing the collection")); .catch((err) => console.log(err) && toast.error('An error occured while removing the collection'));
}; };
return ( return (
<Modal <Modal size="sm" title="Remove Collection from Workspace" confirmText="Remove" handleConfirm={onConfirm} handleCancel={onClose}>
size="sm"
title="Remove Collection from Workspace"
confirmText="Remove"
handleConfirm={onConfirm}
handleCancel={onClose}
>
Are you sure you want to remove the collection <span className="font-semibold">{collection.name}</span> from this workspace? Are you sure you want to remove the collection <span className="font-semibold">{collection.name}</span> from this workspace?
</Modal> </Modal>
); );

View File

@ -4,26 +4,20 @@ import Modal from 'components/Modal';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { removeLocalCollection } from 'providers/ReduxStore/slices/collections/actions'; import { removeLocalCollection } from 'providers/ReduxStore/slices/collections/actions';
const RemoveLocalCollection = ({onClose, collection}) => { const RemoveLocalCollection = ({ onClose, collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const onConfirm = () =>{ const onConfirm = () => {
dispatch(removeLocalCollection(collection.uid)) dispatch(removeLocalCollection(collection.uid))
.then(() => { .then(() => {
toast.success("Collection removed"); toast.success('Collection removed');
onClose(); onClose();
}) })
.catch(() => toast.error("An error occured while removing the collection")); .catch(() => toast.error('An error occured while removing the collection'));
}; };
return ( return (
<Modal <Modal size="sm" title="Remove Collection" confirmText="Remove" handleConfirm={onConfirm} handleCancel={onClose}>
size="sm"
title="Remove Collection"
confirmText="Remove"
handleConfirm={onConfirm}
handleCancel={onClose}
>
Are you sure you want to remove this collection? Are you sure you want to remove this collection?
</Modal> </Modal>
); );

View File

@ -6,19 +6,16 @@ import { useDispatch } from 'react-redux';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { renameCollection } from 'providers/ReduxStore/slices/collections/actions'; import { renameCollection } from 'providers/ReduxStore/slices/collections/actions';
const RenameCollection = ({collection, onClose}) => { const RenameCollection = ({ collection, onClose }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const inputRef = useRef(); const inputRef = useRef();
const formik = useFormik({ const formik = useFormik({
enableReinitialize: true, enableReinitialize: true,
initialValues: { initialValues: {
name: collection.name name: collection.name
}, },
validationSchema: Yup.object({ validationSchema: Yup.object({
name: Yup.string() name: Yup.string().min(1, 'must be atleast 1 characters').max(50, 'must be 50 characters or less').required('name is required')
.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));
@ -28,7 +25,7 @@ const RenameCollection = ({collection, onClose}) => {
}); });
useEffect(() => { useEffect(() => {
if(inputRef && inputRef.current) { if (inputRef && inputRef.current) {
inputRef.current.focus(); inputRef.current.focus();
} }
}, [inputRef]); }, [inputRef]);
@ -36,27 +33,26 @@ const RenameCollection = ({collection, onClose}) => {
const onSubmit = () => formik.handleSubmit(); const onSubmit = () => formik.handleSubmit();
return ( return (
<Modal <Modal size="sm" title="Rename Collection" confirmText="Rename" handleConfirm={onSubmit} handleCancel={onClose}>
size="sm"
title="Rename Collection"
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">Name</label> <label htmlFor="name" className="block font-semibold">
Name
</label>
<input <input
id="collection-name" type="text" name="name" id="collection-name"
type="text"
name="name"
ref={inputRef} ref={inputRef}
className="block textbox mt-2 w-full" className="block textbox mt-2 w-full"
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange} onChange={formik.handleChange}
value={formik.values.name || ''} value={formik.values.name || ''}
/> />
{formik.touched.name && formik.errors.name ? ( {formik.touched.name && formik.errors.name ? <div className="text-red-500">{formik.errors.name}</div> : null}
<div className="text-red-500">{formik.errors.name}</div>
) : null}
</div> </div>
</form> </form>
</Modal> </Modal>

View File

@ -14,14 +14,13 @@ const Wrapper = styled.div`
.collection-actions { .collection-actions {
.dropdown { .dropdown {
div[aria-expanded="true"] { div[aria-expanded='true'] {
visibility: visible; visibility: visible;
} }
div[aria-expanded="false"] { div[aria-expanded='false'] {
visibility: hidden; visibility: hidden;
} }
} }
svg { svg {
height: 22px; height: 22px;
@ -32,7 +31,7 @@ const Wrapper = styled.div`
&:hover { &:hover {
.collection-actions { .collection-actions {
.dropdown { .dropdown {
div[aria-expanded="false"] { div[aria-expanded='false'] {
visibility: visible; visibility: visible;
} }
} }

View File

@ -19,7 +19,7 @@ import RenameCollection from './RenameCollection';
import DeleteCollection from './DeleteCollection'; import DeleteCollection from './DeleteCollection';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const Collection = ({collection, searchText}) => { const Collection = ({ collection, searchText }) => {
const [showNewFolderModal, setShowNewFolderModal] = useState(false); const [showNewFolderModal, setShowNewFolderModal] = useState(false);
const [showNewRequestModal, setShowNewRequestModal] = useState(false); const [showNewRequestModal, setShowNewRequestModal] = useState(false);
const [showRenameCollectionModal, setShowRenameCollectionModal] = useState(false); const [showRenameCollectionModal, setShowRenameCollectionModal] = useState(false);
@ -30,11 +30,11 @@ const Collection = ({collection, searchText}) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const menuDropdownTippyRef = useRef(); const menuDropdownTippyRef = useRef();
const onMenuDropdownCreate = (ref) => menuDropdownTippyRef.current = ref; const onMenuDropdownCreate = (ref) => (menuDropdownTippyRef.current = ref);
const MenuIcon = forwardRef((props, ref) => { const MenuIcon = forwardRef((props, ref) => {
return ( return (
<div ref={ref} className="pr-2"> <div ref={ref} className="pr-2">
<IconDots size={22}/> <IconDots size={22} />
</div> </div>
); );
}); });
@ -55,8 +55,8 @@ const Collection = ({collection, searchText}) => {
dispatch(collectionClicked(collection.uid)); dispatch(collectionClicked(collection.uid));
}; };
if(searchText && searchText.length) { if (searchText && searchText.length) {
if(!doesCollectionHaveItemsMatchingSearchText(collection, searchText)) { if (!doesCollectionHaveItemsMatchingSearchText(collection, searchText)) {
return null; return null;
} }
} }
@ -73,69 +73,86 @@ const Collection = ({collection, searchText}) => {
return ( return (
<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 && <RenameCollection collection={collection} onClose={() => setShowRenameCollectionModal(false)} />}
{showRemoveCollectionFromWSModal && <RemoveCollectionFromWorkspace collection={collection} onClose={() => setShowRemoveCollectionFromWSModal(false)}/>} {showRemoveCollectionFromWSModal && <RemoveCollectionFromWorkspace collection={collection} onClose={() => setShowRemoveCollectionFromWSModal(false)} />}
{showDeleteCollectionModal && <DeleteCollection collection={collection} onClose={() => setShowDeleteCollectionModal(false)}/>} {showDeleteCollectionModal && <DeleteCollection collection={collection} onClose={() => setShowDeleteCollectionModal(false)} />}
{showRemoveLocalCollectionModal && <RemoveLocalCollection collection={collection} onClose={() => setShowRemoveLocalCollectionModal(false)}/>} {showRemoveLocalCollectionModal && <RemoveLocalCollection collection={collection} onClose={() => setShowRemoveLocalCollectionModal(false)} />}
<div className="flex py-1 collection-name items-center"> <div className="flex py-1 collection-name items-center">
<div className="flex flex-grow items-center" onClick={handleClick}> <div className="flex flex-grow items-center" onClick={handleClick}>
<IconChevronRight size={16} strokeWidth={2} className={iconClassName} style={{width:16, color: 'rgb(160 160 160)'}}/> <IconChevronRight size={16} strokeWidth={2} className={iconClassName} style={{ width: 16, color: 'rgb(160 160 160)' }} />
<div className="ml-1">{collection.name}</div> <div className="ml-1">{collection.name}</div>
</div> </div>
<div className='collection-actions'> <div className="collection-actions">
<Dropdown <Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement="bottom-start">
onCreate={onMenuDropdownCreate} <div
icon={<MenuIcon />} className="dropdown-item"
placement='bottom-start' onClick={(e) => {
> menuDropdownTippyRef.current.hide();
<div className="dropdown-item" onClick={(e) => { setShowNewRequestModal(true);
menuDropdownTippyRef.current.hide(); }}
setShowNewRequestModal(true); >
}}>
New Request New Request
</div> </div>
<div className="dropdown-item" onClick={(e) => { <div
menuDropdownTippyRef.current.hide(); className="dropdown-item"
setShowNewFolderModal(true); onClick={(e) => {
}}> menuDropdownTippyRef.current.hide();
setShowNewFolderModal(true);
}}
>
New Folder New Folder
</div> </div>
{!isLocal ? ( {!isLocal ? (
<div className="dropdown-item" onClick={(e) => { <div
menuDropdownTippyRef.current.hide(); className="dropdown-item"
setShowRenameCollectionModal(true); onClick={(e) => {
}}> menuDropdownTippyRef.current.hide();
setShowRenameCollectionModal(true);
}}
>
Rename Rename
</div> </div>
) : null} ) : null}
<div className="dropdown-item" onClick={(e) => { <div
menuDropdownTippyRef.current.hide(); className="dropdown-item"
handleExportClick(true); onClick={(e) => {
}}> menuDropdownTippyRef.current.hide();
handleExportClick(true);
}}
>
Export Export
</div> </div>
{!isLocal ? ( {!isLocal ? (
<div className="dropdown-item" onClick={(e) => { <div
menuDropdownTippyRef.current.hide(); className="dropdown-item"
setShowRemoveCollectionFromWSModal(true); onClick={(e) => {
}}> menuDropdownTippyRef.current.hide();
setShowRemoveCollectionFromWSModal(true);
}}
>
Remove from Workspace Remove from Workspace
</div> </div>
) : ( ) : (
<div className="dropdown-item" onClick={(e) => { <div
menuDropdownTippyRef.current.hide(); className="dropdown-item"
setShowRemoveLocalCollectionModal(true); onClick={(e) => {
}}> menuDropdownTippyRef.current.hide();
setShowRemoveLocalCollectionModal(true);
}}
>
Remove Remove
</div> </div>
)} )}
{!isLocal ? ( {!isLocal ? (
<div className="dropdown-item delete-collection" onClick={(e) => { <div
menuDropdownTippyRef.current.hide(); className="dropdown-item delete-collection"
setShowDeleteCollectionModal(true); onClick={(e) => {
}}> menuDropdownTippyRef.current.hide();
setShowDeleteCollectionModal(true);
}}
>
Delete Delete
</div> </div>
) : null} ) : null}
@ -146,23 +163,17 @@ const Collection = ({collection, searchText}) => {
<div> <div>
{!collectionIsCollapsed ? ( {!collectionIsCollapsed ? (
<div> <div>
{requestItems && requestItems.length ? requestItems.map((i) => { {requestItems && requestItems.length
return <CollectionItem ? requestItems.map((i) => {
key={i.uid} return <CollectionItem key={i.uid} item={i} collection={collection} searchText={searchText} />;
item={i} })
collection={collection} : null}
searchText={searchText}
/>
}) : null}
{folderItems && folderItems.length ? folderItems.map((i) => { {folderItems && folderItems.length
return <CollectionItem ? folderItems.map((i) => {
key={i.uid} return <CollectionItem key={i.uid} item={i} collection={collection} searchText={searchText} />;
item={i} })
collection={collection} : null}
searchText={searchText}
/>
}) : null}
</div> </div>
) : null} ) : null}
</div> </div>
@ -170,4 +181,4 @@ const Collection = ({collection, searchText}) => {
); );
}; };
export default Collection; export default Collection;

View File

@ -16,43 +16,42 @@ const CreateOrAddCollection = () => {
setCreateCollectionModalOpen(false); setCreateCollectionModalOpen(false);
dispatch(createCollection(values.collectionName)) dispatch(createCollection(values.collectionName))
.then(() => { .then(() => {
toast.success("Collection created"); toast.success('Collection created');
}) })
.catch(() => toast.error("An error occured while creating the collection")); .catch(() => toast.error('An error occured while creating the collection'));
}; };
const handleAddCollectionToWorkspace = (collectionUid) => { const handleAddCollectionToWorkspace = (collectionUid) => {
setAddCollectionToWSModalOpen(false); setAddCollectionToWSModalOpen(false);
dispatch(addCollectionToWorkspace(activeWorkspaceUid, collectionUid)) dispatch(addCollectionToWorkspace(activeWorkspaceUid, collectionUid))
.then(() => { .then(() => {
toast.success("Collection added to workspace"); toast.success('Collection added to workspace');
}) })
.catch(() => toast.error("An error occured while adding collection to workspace")); .catch(() => toast.error('An error occured while adding collection to workspace'));
}; };
const CreateLink = () => <span className='underline text-link cursor-pointer' onClick={() => setCreateCollectionModalOpen(true)}>Create</span>; const CreateLink = () => (
const AddLink = () => <span className='underline text-link cursor-pointer' onClick={() => setAddCollectionToWSModalOpen(true)}>Add</span>; <span className="underline text-link cursor-pointer" onClick={() => setCreateCollectionModalOpen(true)}>
Create
</span>
);
const AddLink = () => (
<span className="underline text-link cursor-pointer" onClick={() => setAddCollectionToWSModalOpen(true)}>
Add
</span>
);
return ( return (
<div className='px-2 mt-4 text-gray-600'> <div className="px-2 mt-4 text-gray-600">
{createCollectionModalOpen ? ( {createCollectionModalOpen ? <CreateCollection handleCancel={() => setCreateCollectionModalOpen(false)} handleConfirm={handleCreateCollection} /> : null}
<CreateCollection
handleCancel={() => setCreateCollectionModalOpen(false)}
handleConfirm={handleCreateCollection}
/>
) : null}
{addCollectionToWSModalOpen ? (
<SelectCollection
title='Add Collection to Workspace'
onClose={() => setAddCollectionToWSModalOpen(false)}
onSelect={handleAddCollectionToWorkspace}
/>
): null}
<div className='text-xs text-center'> {addCollectionToWSModalOpen ? (
<SelectCollection title="Add Collection to Workspace" onClose={() => setAddCollectionToWSModalOpen(false)} onSelect={handleAddCollectionToWorkspace} />
) : null}
<div className="text-xs text-center">
<div>No collections found.</div> <div>No collections found.</div>
<div className='mt-2'> <div className="mt-2">
<CreateLink /> or <AddLink /> Collection to Workspace. <CreateLink /> or <AddLink /> Collection to Workspace.
</div> </div>
</div> </div>
@ -60,4 +59,4 @@ const CreateOrAddCollection = () => {
); );
}; };
export default CreateOrAddCollection; export default CreateOrAddCollection;

View File

@ -15,4 +15,4 @@ const StyledWrapper = styled.div`
} }
`; `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -1,35 +1,32 @@
import React from "react"; import React from 'react';
import filter from "lodash/filter"; import filter from 'lodash/filter';
import Modal from "components/Modal/index"; import Modal from 'components/Modal/index';
import { IconFiles } from '@tabler/icons'; import { IconFiles } from '@tabler/icons';
import { useSelector } from "react-redux"; import { useSelector } from 'react-redux';
import { isLocalCollection } from "utils/collections"; import { isLocalCollection } from 'utils/collections';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const SelectCollection = ({onClose, onSelect, title}) => { const SelectCollection = ({ onClose, onSelect, title }) => {
const { collections } = useSelector((state) => state.collections); const { collections } = useSelector((state) => state.collections);
const collectionsToDisplay = filter(collections, (c) => !isLocalCollection(c)); const collectionsToDisplay = filter(collections, (c) => !isLocalCollection(c));
return ( return (
<StyledWrapper> <StyledWrapper>
<Modal <Modal size="sm" title={title || 'Select Collection'} hideFooter={true} handleCancel={onClose}>
size="sm" <ul className="mb-2">
title={title || "Select Collection"} {collectionsToDisplay && collectionsToDisplay.length ? (
hideFooter={true} collectionsToDisplay.map((c) => (
handleCancel={onClose} <div className="collection" key={c.uid} onClick={() => onSelect(c.uid)}>
> <IconFiles size={18} strokeWidth={1.5} /> <span className="ml-2">{c.name}</span>
<ul className="mb-2" > </div>
{(collectionsToDisplay && collectionsToDisplay.length) ? collectionsToDisplay.map((c) => ( ))
<div className="collection" key={c.uid} onClick={() => onSelect(c.uid)}> ) : (
<IconFiles size={18} strokeWidth={1.5}/> <span className="ml-2">{c.name}</span>
</div>
)) : (
<div>No collections found</div> <div>No collections found</div>
)} )}
</ul> </ul>
</Modal> </Modal>
</StyledWrapper> </StyledWrapper>
); );
} };
export default SelectCollection; export default SelectCollection;

View File

@ -7,32 +7,30 @@ import CreateOrAddCollection from './CreateOrAddCollection';
import { findCollectionInWorkspace } from 'utils/workspaces'; import { findCollectionInWorkspace } from 'utils/workspaces';
import { isLocalCollection } from 'utils/collections'; import { isLocalCollection } from 'utils/collections';
const Collections = ({searchText}) => { const Collections = ({ searchText }) => {
const { collections } = useSelector((state) => state.collections); const { collections } = useSelector((state) => state.collections);
const { workspaces, activeWorkspaceUid } = useSelector((state) => state.workspaces); const { workspaces, activeWorkspaceUid } = useSelector((state) => state.workspaces);
const activeWorkspace = find(workspaces, w => w.uid === activeWorkspaceUid); const activeWorkspace = find(workspaces, (w) => w.uid === activeWorkspaceUid);
if(!activeWorkspace) { if (!activeWorkspace) {
return null; return null;
} }
const collectionToDisplay = filter(collections, (c) => findCollectionInWorkspace(activeWorkspace, c.uid) && !isLocalCollection(c)); const collectionToDisplay = filter(collections, (c) => findCollectionInWorkspace(activeWorkspace, c.uid) && !isLocalCollection(c));
if(!collectionToDisplay || !collectionToDisplay.length) { if (!collectionToDisplay || !collectionToDisplay.length) {
return <CreateOrAddCollection />; return <CreateOrAddCollection />;
} }
return ( return (
<div className="mt-4 flex flex-col"> <div className="mt-4 flex flex-col">
{collectionToDisplay && collectionToDisplay.length ? collectionToDisplay.map((c) => { {collectionToDisplay && collectionToDisplay.length
return <Collection ? collectionToDisplay.map((c) => {
searchText={searchText} return <Collection searchText={searchText} collection={c} key={c.uid} />;
collection={c} })
key={c.uid} : null}
/>
}) : null}
</div> </div>
); );
}; };
export default Collections; export default Collections;

View File

@ -8,34 +8,31 @@ import { createCollection, createLocalCollection } from 'providers/ReduxStore/sl
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import Modal from 'components/Modal'; import Modal from 'components/Modal';
const CreateCollection = ({onClose, isLocal}) => { const CreateCollection = ({ onClose, isLocal }) => {
const inputRef = useRef(); const inputRef = useRef();
const dispatch = useDispatch(); const dispatch = useDispatch();
const isPlatformElectron = isElectron(); const isPlatformElectron = isElectron();
const formik = useFormik({ const formik = useFormik({
enableReinitialize: true, enableReinitialize: true,
initialValues: { initialValues: {
collectionName: '', collectionName: '',
collectionLocation: '' collectionLocation: ''
}, },
validationSchema: Yup.object({ validationSchema: Yup.object({
collectionName: Yup.string() collectionName: Yup.string().min(1, 'must be atleast 1 characters').max(50, 'must be 50 characters or less').required('name is required')
.min(1, 'must be atleast 1 characters')
.max(50, 'must be 50 characters or less')
.required('name is required')
}), }),
onSubmit: (values) => { onSubmit: (values) => {
const action = isLocal && isPlatformElectron ? createLocalCollection : createCollection; const action = isLocal && isPlatformElectron ? createLocalCollection : createCollection;
dispatch(action(values.collectionName, values.collectionLocation)) dispatch(action(values.collectionName, values.collectionLocation))
.then(() => { .then(() => {
toast.success("Collection created"); toast.success('Collection created');
onClose(); onClose();
}) })
.catch(() => toast.error("An error occured while creating the collection")); .catch(() => toast.error('An error occured while creating the collection'));
} }
}); });
const browse = () => { const browse = () => {
dispatch(browserLocalDirectory()) dispatch(browserLocalDirectory())
.then((dirPath) => { .then((dirPath) => {
@ -48,7 +45,7 @@ const CreateCollection = ({onClose, isLocal}) => {
}; };
useEffect(() => { useEffect(() => {
if(inputRef && inputRef.current) { if (inputRef && inputRef.current) {
inputRef.current.focus(); inputRef.current.focus();
} }
}, [inputRef]); }, [inputRef]);
@ -56,16 +53,12 @@ const CreateCollection = ({onClose, isLocal}) => {
const onSubmit = () => formik.handleSubmit(); const onSubmit = () => formik.handleSubmit();
return ( return (
<Modal <Modal size="sm" title="Create Collection" confirmText="Create" handleConfirm={onSubmit} handleCancel={onClose}>
size="sm"
title='Create Collection'
confirmText='Create'
handleConfirm={onSubmit}
handleCancel={onClose}
>
<form className="bruno-form" onSubmit={formik.handleSubmit}> <form className="bruno-form" onSubmit={formik.handleSubmit}>
<div> <div>
<label htmlFor="collectionName" className="block font-semibold">Name</label> <label htmlFor="collectionName" className="block font-semibold">
Name
</label>
<input <input
id="collection-name" id="collection-name"
type="text" type="text"
@ -73,23 +66,29 @@ const CreateCollection = ({onClose, isLocal}) => {
ref={inputRef} ref={inputRef}
className="block textbox mt-2 w-full" className="block textbox mt-2 w-full"
onChange={formik.handleChange} onChange={formik.handleChange}
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={formik.values.collectionName || ''} value={formik.values.collectionName || ''}
/> />
{formik.touched.collectionName && formik.errors.collectionName ? ( {formik.touched.collectionName && formik.errors.collectionName ? <div className="text-red-500">{formik.errors.collectionName}</div> : null}
<div className="text-red-500">{formik.errors.collectionName}</div>
) : null}
{isLocal && isPlatformElectron ? ( {isLocal && isPlatformElectron ? (
<> <>
<label htmlFor="collectionLocation" className="block font-semibold mt-3">Location</label> <label htmlFor="collectionLocation" className="block font-semibold mt-3">
Location
</label>
<input <input
id="collection-location" id="collection-location"
type="text" type="text"
name="collectionLocation" name="collectionLocation"
readOnly={true} readOnly={true}
className="block textbox mt-2 w-full" className="block textbox mt-2 w-full"
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={formik.values.collectionLocation || ''} value={formik.values.collectionLocation || ''}
onClick={browse} onClick={browse}
/> />
@ -101,9 +100,11 @@ const CreateCollection = ({onClose, isLocal}) => {
{isLocal && isPlatformElectron ? ( {isLocal && isPlatformElectron ? (
<div className="mt-1"> <div className="mt-1">
<span className="text-link cursor-pointer hover:underline" onClick={browse}>Browse</span> <span className="text-link cursor-pointer hover:underline" onClick={browse}>
Browse
</span>
</div> </div>
) : null } ) : null}
</div> </div>
</form> </form>
</Modal> </Modal>

View File

@ -2,7 +2,7 @@ import styled from 'styled-components';
const Wrapper = styled.div` const Wrapper = styled.div`
.current-workspace { .current-workspace {
margin-inline: .5rem; margin-inline: 0.5rem;
background: #e1e1e1; background: #e1e1e1;
border-radius: 5px; border-radius: 5px;

View File

@ -9,7 +9,7 @@ import CreateCollection from '../CreateCollection';
import { isLocalCollection } from 'utils/collections'; import { isLocalCollection } from 'utils/collections';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const LocalCollections = ({searchText}) => { const LocalCollections = ({ searchText }) => {
const dropdownTippyRef = useRef(); const dropdownTippyRef = useRef();
const dispatch = useDispatch(); const dispatch = useDispatch();
const { collections } = useSelector((state) => state.collections); const { collections } = useSelector((state) => state.collections);
@ -17,71 +17,60 @@ const LocalCollections = ({searchText}) => {
const collectionToDisplay = filter(collections, (c) => isLocalCollection(c)); const collectionToDisplay = filter(collections, (c) => isLocalCollection(c));
if(!collectionToDisplay || !collectionToDisplay.length) { if (!collectionToDisplay || !collectionToDisplay.length) {
return null; return null;
} }
const Icon = forwardRef((props, ref) => { const Icon = forwardRef((props, ref) => {
return ( return (
<div ref={ref} className="current-workspace flex justify-between items-center pl-2 pr-2 py-1 select-none"> <div ref={ref} className="current-workspace flex justify-between items-center pl-2 pr-2 py-1 select-none">
<div className='flex items-center'> <div className="flex items-center">
<span className='mr-2'> <span className="mr-2">
<IconFolders size={18} strokeWidth={1.5}/> <IconFolders size={18} strokeWidth={1.5} />
</span>
<span>
Local Collections
</span> </span>
<span>Local Collections</span>
</div> </div>
<IconCaretDown className="caret" size={14} strokeWidth={2}/> <IconCaretDown className="caret" size={14} strokeWidth={2} />
</div> </div>
); );
}); });
const onDropdownCreate = (ref) => dropdownTippyRef.current = ref; const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const handleOpenLocalCollection = () => { const handleOpenLocalCollection = () => {
dispatch(openLocalCollection()) dispatch(openLocalCollection()).catch((err) => console.log(err) && toast.error('An error occured while opening the local collection'));
.catch((err) => console.log(err) && toast.error("An error occured while opening the local collection")); };
}
return ( return (
<StyledWrapper> <StyledWrapper>
{createCollectionModalOpen ? ( {createCollectionModalOpen ? <CreateCollection isLocal={true} onClose={() => setCreateCollectionModalOpen(false)} /> : null}
<CreateCollection
isLocal={true}
onClose={() => setCreateCollectionModalOpen(false)}
/>
) : null}
<div className="items-center cursor-pointer mt-6 relative"> <div className="items-center cursor-pointer mt-6 relative">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement='bottom-end'> <Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
<div className="dropdown-item" onClick={() => setCreateCollectionModalOpen(true)}> <div className="dropdown-item" onClick={() => setCreateCollectionModalOpen(true)}>
<div className="pr-2 text-gray-600"> <div className="pr-2 text-gray-600">
<IconPlus size={18} strokeWidth={1.5}/> <IconPlus size={18} strokeWidth={1.5} />
</div> </div>
<span>Create Collection</span> <span>Create Collection</span>
</div> </div>
<div className="dropdown-item" onClick={handleOpenLocalCollection}> <div className="dropdown-item" onClick={handleOpenLocalCollection}>
<div className="pr-2 text-gray-600"> <div className="pr-2 text-gray-600">
<IconArrowForwardUp size={18} strokeWidth={1.5}/> <IconArrowForwardUp size={18} strokeWidth={1.5} />
</div> </div>
<span>Open Collection</span> <span>Open Collection</span>
</div> </div>
<div className='px-2 pt-2 text-gray-600' style={{fontSize: 10, borderTop: 'solid 1px #e7e7e7'}}> <div className="px-2 pt-2 text-gray-600" style={{ fontSize: 10, borderTop: 'solid 1px #e7e7e7' }}>
Note: Local collections are not tied to a workspace Note: Local collections are not tied to a workspace
</div> </div>
</Dropdown> </Dropdown>
</div> </div>
<div className="mt-4 flex flex-col"> <div className="mt-4 flex flex-col">
{collectionToDisplay && collectionToDisplay.length ? collectionToDisplay.map((c) => { {collectionToDisplay && collectionToDisplay.length
return <Collection ? collectionToDisplay.map((c) => {
searchText={searchText} return <Collection searchText={searchText} collection={c} key={c.uid} />;
collection={c} })
key={c.uid} : null}
/>
}) : null}
</div> </div>
</StyledWrapper> </StyledWrapper>
); );

View File

@ -9,7 +9,8 @@ const Wrapper = styled.div`
padding: 0.6rem; padding: 0.6rem;
cursor: pointer; cursor: pointer;
&:hover, &.active { &:hover,
&.active {
color: rgba(255, 255, 255); color: rgba(255, 255, 255);
} }
} }

View File

@ -1,7 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import Link from 'next/link'; import Link from 'next/link';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { IconCode, IconFiles, IconUser, IconUsers, IconSettings, IconChevronsLeft, IconLifebuoy} from '@tabler/icons'; import { IconCode, IconFiles, IconUser, IconUsers, IconSettings, IconChevronsLeft, IconLifebuoy } from '@tabler/icons';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { toggleLeftMenuBar } from 'providers/ReduxStore/slices/app'; import { toggleLeftMenuBar } from 'providers/ReduxStore/slices/app';
import BrunoSupport from 'components/BrunoSupport'; import BrunoSupport from 'components/BrunoSupport';
@ -15,7 +15,7 @@ const MenuBar = () => {
const isPlatformElectron = isElectron(); const isPlatformElectron = isElectron();
const getClassName = (menu) => { const getClassName = (menu) => {
return router.pathname === menu ? "active menu-item": "menu-item"; return router.pathname === menu ? 'active menu-item' : 'menu-item';
}; };
return ( return (
@ -23,14 +23,14 @@ const MenuBar = () => {
<div className="flex flex-col"> <div className="flex flex-col">
<Link href="/"> <Link href="/">
<div className={getClassName('/')}> <div className={getClassName('/')}>
<IconCode size={28} strokeWidth={1.5}/> <IconCode size={28} strokeWidth={1.5} />
</div> </div>
</Link> </Link>
{!isPlatformElectron ? ( {!isPlatformElectron ? (
<Link href="/collections"> <Link href="/collections">
<div className={getClassName('/collections')}> <div className={getClassName('/collections')}>
<IconFiles size={28} strokeWidth={1.5}/> <IconFiles size={28} strokeWidth={1.5} />
</div> </div>
</Link> </Link>
) : null} ) : null}
{/* <div className="menu-item"> {/* <div className="menu-item">
@ -44,15 +44,15 @@ const MenuBar = () => {
</div> </div>
</Link> */} </Link> */}
<div className="menu-item"> <div className="menu-item">
<IconLifebuoy size={28} strokeWidth={1.5} onClick={() => setOpenBrunoSupport(true)}/> <IconLifebuoy size={28} strokeWidth={1.5} onClick={() => setOpenBrunoSupport(true)} />
</div> </div>
<div className="menu-item" onClick={() => dispatch(toggleLeftMenuBar())}> <div className="menu-item" onClick={() => dispatch(toggleLeftMenuBar())}>
<IconChevronsLeft size={28} strokeWidth={1.5}/> <IconChevronsLeft size={28} strokeWidth={1.5} />
</div> </div>
</div> </div>
{openBrunoSupport && <BrunoSupport onClose={() => setOpenBrunoSupport(false)}/>} {openBrunoSupport && <BrunoSupport onClose={() => setOpenBrunoSupport(false)} />}
</StyledWrapper> </StyledWrapper>
); );
}; };
export default MenuBar; export default MenuBar;

View File

@ -1,34 +1,31 @@
import React, { useRef, useEffect } from 'react'; import React, { useRef, useEffect } from 'react';
import {useFormik} from 'formik'; import { useFormik } from 'formik';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import * as Yup from 'yup'; import * as Yup from 'yup';
import Modal from 'components/Modal'; import Modal from 'components/Modal';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { newFolder } from 'providers/ReduxStore/slices/collections/actions'; import { newFolder } from 'providers/ReduxStore/slices/collections/actions';
const NewFolder = ({collection, item, onClose}) => { const NewFolder = ({ collection, item, onClose }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const inputRef = useRef(); const inputRef = useRef();
const formik = useFormik({ const formik = useFormik({
enableReinitialize: true, enableReinitialize: true,
initialValues: { initialValues: {
folderName: '' folderName: ''
}, },
validationSchema: Yup.object({ validationSchema: Yup.object({
folderName: Yup.string() folderName: Yup.string().min(1, 'must be atleast 1 characters').max(50, 'must be 50 characters or less').required('name is required')
.min(1, 'must be atleast 1 characters')
.max(50, 'must be 50 characters or less')
.required('name is required')
}), }),
onSubmit: (values) => { onSubmit: (values) => {
dispatch(newFolder(values.folderName, collection.uid, item ? item.uid : null)) dispatch(newFolder(values.folderName, collection.uid, item ? item.uid : null))
.then(() => onClose()) .then(() => onClose())
.catch(() => toast.error("An error occured while adding the request")); .catch(() => toast.error('An error occured while adding the request'));
} }
}); });
useEffect(() => { useEffect(() => {
if(inputRef && inputRef.current) { if (inputRef && inputRef.current) {
inputRef.current.focus(); inputRef.current.focus();
} }
}, [inputRef]); }, [inputRef]);
@ -36,27 +33,26 @@ const NewFolder = ({collection, item, onClose}) => {
const onSubmit = () => formik.handleSubmit(); const onSubmit = () => formik.handleSubmit();
return ( return (
<Modal <Modal size="sm" title="New Folder" confirmText="Create" handleConfirm={onSubmit} handleCancel={onClose}>
size="sm"
title='New Folder'
confirmText='Create'
handleConfirm={onSubmit}
handleCancel={onClose}
>
<form className="bruno-form" onSubmit={formik.handleSubmit}> <form className="bruno-form" onSubmit={formik.handleSubmit}>
<div> <div>
<label htmlFor="folderName" className="block font-semibold">Folder Name</label> <label htmlFor="folderName" className="block font-semibold">
Folder Name
</label>
<input <input
id="collection-name" type="text" name="folderName" id="collection-name"
type="text"
name="folderName"
ref={inputRef} ref={inputRef}
className="block textbox mt-2 w-full" className="block textbox mt-2 w-full"
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange} onChange={formik.handleChange}
value={formik.values.folderName || ''} value={formik.values.folderName || ''}
/> />
{formik.touched.folderName && formik.errors.folderName ? ( {formik.touched.folderName && formik.errors.folderName ? <div className="text-red-500">{formik.errors.folderName}</div> : null}
<div className="text-red-500">{formik.errors.folderName}</div>
) : null}
</div> </div>
</form> </form>
</Modal> </Modal>

View File

@ -13,7 +13,8 @@ const StyledWrapper = styled.div`
} }
} }
div.method-selector-container, div.input-container { div.method-selector-container,
div.input-container {
height: 2.3rem; height: 2.3rem;
} }
@ -34,4 +35,4 @@ const StyledWrapper = styled.div`
} }
`; `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -2,7 +2,7 @@ import React, { useRef, useEffect } from 'react';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import * as Yup from 'yup'; import * as Yup from 'yup';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { uuid } from 'utils/common';; import { uuid } from 'utils/common';
import Modal from 'components/Modal'; import Modal from 'components/Modal';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { newEphermalHttpRequest } from 'providers/ReduxStore/slices/collections'; import { newEphermalHttpRequest } from 'providers/ReduxStore/slices/collections';
@ -11,11 +11,11 @@ import { addTab } from 'providers/ReduxStore/slices/tabs';
import HttpMethodSelector from 'components/RequestPane/QueryUrl/HttpMethodSelector'; import HttpMethodSelector from 'components/RequestPane/QueryUrl/HttpMethodSelector';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const NewRequest = ({collection, item, isEphermal, onClose}) => { const NewRequest = ({ collection, item, isEphermal, onClose }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const inputRef = useRef(); const inputRef = useRef();
const formik = useFormik({ const formik = useFormik({
enableReinitialize: true, enableReinitialize: true,
initialValues: { initialValues: {
requestName: '', requestName: '',
requestType: 'http-request', requestType: 'http-request',
@ -23,47 +23,50 @@ const NewRequest = ({collection, item, isEphermal, onClose}) => {
requestMethod: 'GET' requestMethod: 'GET'
}, },
validationSchema: Yup.object({ validationSchema: Yup.object({
requestName: Yup.string() requestName: Yup.string().min(1, 'must be atleast 1 characters').max(50, 'must be 50 characters or less').required('name is required')
.min(1, 'must be atleast 1 characters')
.max(50, 'must be 50 characters or less')
.required('name is required')
}), }),
onSubmit: (values) => { onSubmit: (values) => {
if(isEphermal) { if (isEphermal) {
const uid = uuid(); const uid = uuid();
dispatch(newEphermalHttpRequest({ dispatch(
uid: uid, newEphermalHttpRequest({
requestName: values.requestName, uid: uid,
requestType: values.requestType, requestName: values.requestName,
requestUrl: values.requestUrl, requestType: values.requestType,
requestMethod: values.requestMethod, requestUrl: values.requestUrl,
collectionUid: collection.uid requestMethod: values.requestMethod,
})) collectionUid: collection.uid
})
)
.then(() => { .then(() => {
dispatch(addTab({ dispatch(
uid: uid, addTab({
collectionUid: collection.uid uid: uid,
})); collectionUid: collection.uid
})
);
onClose(); onClose();
}) })
.catch(() => toast.error("An error occured while adding the request")); .catch(() => toast.error('An error occured while adding the request'));
} else { } else {
dispatch(newHttpRequest({ dispatch(
requestName: values.requestName, newHttpRequest({
requestType: values.requestType, requestName: values.requestName,
requestUrl: values.requestUrl, requestType: values.requestType,
requestMethod: values.requestMethod, requestUrl: values.requestUrl,
collectionUid: collection.uid, requestMethod: values.requestMethod,
itemUid: item ? item.uid : null collectionUid: collection.uid,
})) itemUid: item ? item.uid : null
})
)
.then(() => onClose()) .then(() => onClose())
.catch(() => toast.error("An error occured while adding the request")); .catch(() => toast.error('An error occured while adding the request'));
} }
} }
}); });
useEffect(() => { useEffect(() => {
if(inputRef && inputRef.current) { if (inputRef && inputRef.current) {
inputRef.current.focus(); inputRef.current.focus();
} }
}, [inputRef]); }, [inputRef]);
@ -72,78 +75,90 @@ const NewRequest = ({collection, item, isEphermal, onClose}) => {
return ( return (
<StyledWrapper> <StyledWrapper>
<Modal <Modal size="md" title="New Request" confirmText="Create" handleConfirm={onSubmit} handleCancel={onClose}>
size="md"
title='New Request'
confirmText='Create'
handleConfirm={onSubmit}
handleCancel={onClose}
>
<form className="bruno-form" onSubmit={formik.handleSubmit}> <form className="bruno-form" onSubmit={formik.handleSubmit}>
<div className="hidden"> <div className="hidden">
<label htmlFor="requestName" className="block font-semibold">Type</label> <label htmlFor="requestName" className="block font-semibold">
Type
</label>
<div className="flex items-center mt-2"> <div className="flex items-center mt-2">
<input <input
id="http" id="http"
className="cursor-pointer" className="cursor-pointer"
type="radio" name="requestType" type="radio"
name="requestType"
onChange={formik.handleChange} onChange={formik.handleChange}
value="http" value="http"
checked={formik.values.requestType === 'http-request'} checked={formik.values.requestType === 'http-request'}
/> />
<label htmlFor="http" className="ml-1 cursor-pointer select-none">Http</label> <label htmlFor="http" className="ml-1 cursor-pointer select-none">
Http
</label>
<input <input
id="graphql" id="graphql"
className="ml-4 cursor-pointer" className="ml-4 cursor-pointer"
type="radio" name="requestType" type="radio"
name="requestType"
onChange={(event) => { onChange={(event) => {
formik.setFieldValue('requestMethod', 'POST') formik.setFieldValue('requestMethod', 'POST');
formik.handleChange(event); formik.handleChange(event);
}} }}
value="graphql" value="graphql"
checked={formik.values.requestType === 'graphql-request'} checked={formik.values.requestType === 'graphql-request'}
/> />
<label htmlFor="graphql" className="ml-1 cursor-pointer select-none">Graphql</label> <label htmlFor="graphql" className="ml-1 cursor-pointer select-none">
Graphql
</label>
</div> </div>
</div> </div>
<div> <div>
<label htmlFor="requestName" className="block font-semibold">Name</label> <label htmlFor="requestName" className="block font-semibold">
Name
</label>
<input <input
id="collection-name" type="text" name="requestName" id="collection-name"
type="text"
name="requestName"
ref={inputRef} ref={inputRef}
className="block textbox mt-2 w-full" className="block textbox mt-2 w-full"
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange} onChange={formik.handleChange}
value={formik.values.requestName || ''} value={formik.values.requestName || ''}
/> />
{formik.touched.requestName && formik.errors.requestName ? ( {formik.touched.requestName && formik.errors.requestName ? <div className="text-red-500">{formik.errors.requestName}</div> : null}
<div className="text-red-500">{formik.errors.requestName}</div>
) : null}
</div> </div>
<div className="mt-4"> <div className="mt-4">
<label htmlFor="request-url" className="block font-semibold">Url</label> <label htmlFor="request-url" className="block font-semibold">
Url
</label>
<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
id="request-url" type="text" name="requestUrl" id="request-url"
type="text"
name="requestUrl"
className="px-3 w-full " className="px-3 w-full "
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false" autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange} onChange={formik.handleChange}
value={formik.values.requestUrl || ''} value={formik.values.requestUrl || ''}
/> />
</div> </div>
</div> </div>
{formik.touched.requestUrl && formik.errors.requestUrl ? ( {formik.touched.requestUrl && formik.errors.requestUrl ? <div className="text-red-500">{formik.errors.requestUrl}</div> : null}
<div className="text-red-500">{formik.errors.requestUrl}</div>
) : null}
</div> </div>
</form> </form>
</Modal> </Modal>

View File

@ -37,7 +37,7 @@ const Wrapper = styled.div`
width: 6px; width: 6px;
right: -3px; right: -3px;
&:hover div.drag-request-border{ &:hover div.drag-request-border {
width: 2px; width: 2px;
height: 100%; height: 100%;
border-left: solid 1px var(--color-request-dragbar-background-active); border-left: solid 1px var(--color-request-dragbar-background-active);

View File

@ -25,4 +25,4 @@ const StyledWrapper = styled.div`
} }
`; `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -23,11 +23,11 @@ const TitleBar = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const menuDropdownTippyRef = useRef(); const menuDropdownTippyRef = useRef();
const onMenuDropdownCreate = (ref) => menuDropdownTippyRef.current = ref; const onMenuDropdownCreate = (ref) => (menuDropdownTippyRef.current = ref);
const MenuIcon = forwardRef((props, ref) => { const MenuIcon = forwardRef((props, ref) => {
return ( return (
<div ref={ref} className="dropdown-icon cursor-pointer"> <div ref={ref} className="dropdown-icon cursor-pointer">
<IconDots size={22}/> <IconDots size={22} />
</div> </div>
); );
}); });
@ -35,23 +35,22 @@ const TitleBar = () => {
const handleTitleClick = () => dispatch(showHomePage()); const handleTitleClick = () => dispatch(showHomePage());
const handleOpenLocalCollection = () => { const handleOpenLocalCollection = () => {
dispatch(openLocalCollection()) dispatch(openLocalCollection()).catch((err) => console.log(err) && toast.error('An error occured while opening the local collection'));
.catch((err) => console.log(err) && toast.error("An error occured while opening the local collection")); };
}
const handleAddCollectionToWorkspace = (collectionUid) => { const handleAddCollectionToWorkspace = (collectionUid) => {
setAddCollectionToWSModalOpen(false); setAddCollectionToWSModalOpen(false);
dispatch(addCollectionToWorkspace(activeWorkspaceUid, collectionUid)) dispatch(addCollectionToWorkspace(activeWorkspaceUid, collectionUid))
.then(() => { .then(() => {
toast.success("Collection added to workspace"); toast.success('Collection added to workspace');
}) })
.catch(() => toast.error("An error occured while adding collection to workspace")); .catch(() => toast.error('An error occured while adding collection to workspace'));
}; };
const handleImportCollection = () => { const handleImportCollection = () => {
importCollection() importCollection()
.then((collection) => { .then((collection) => {
dispatch(collectionImported({collection: collection})); dispatch(collectionImported({ collection: collection }));
dispatch(addCollectionToWorkspace(activeWorkspaceUid, collection.uid)); dispatch(addCollectionToWorkspace(activeWorkspaceUid, collection.uid));
}) })
.catch((err) => console.log(err)); .catch((err) => console.log(err));
@ -59,20 +58,11 @@ const TitleBar = () => {
return ( return (
<StyledWrapper className="px-2 py-2"> <StyledWrapper className="px-2 py-2">
{createCollectionModalOpen ? ( {createCollectionModalOpen ? <CreateCollection isLocal={createCollectionModalOpen === 'local' ? true : false} onClose={() => setCreateCollectionModalOpen(false)} /> : null}
<CreateCollection
isLocal={createCollectionModalOpen === 'local' ? true : false}
onClose={() => setCreateCollectionModalOpen(false)}
/>
) : null}
{addCollectionToWSModalOpen ? ( {addCollectionToWSModalOpen ? (
<SelectCollection <SelectCollection title="Add Collection to Workspace" onClose={() => setAddCollectionToWSModalOpen(false)} onSelect={handleAddCollectionToWorkspace} />
title='Add Collection to Workspace' ) : null}
onClose={() => setAddCollectionToWSModalOpen(false)}
onSelect={handleAddCollectionToWorkspace}
/>
): 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}>
@ -81,52 +71,65 @@ const TitleBar = () => {
<div <div
onClick={handleTitleClick} onClick={handleTitleClick}
className=" flex items-center font-medium select-none cursor-pointer" className=" flex items-center font-medium select-none cursor-pointer"
style={{fontSize: 14, paddingLeft: 6, position: 'relative', top: -1}} style={{ fontSize: 14, paddingLeft: 6, position: 'relative', top: -1 }}
> >
bruno bruno
</div> </div>
<div className="collection-dropdown flex flex-grow items-center justify-end"> <div className="collection-dropdown flex flex-grow items-center justify-end">
<Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement='bottom-start'> <Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement="bottom-start">
<div className="dropdown-item" onClick={(e) => { <div
menuDropdownTippyRef.current.hide(); className="dropdown-item"
setCreateCollectionModalOpen(true); onClick={(e) => {
}}> menuDropdownTippyRef.current.hide();
setCreateCollectionModalOpen(true);
}}
>
Create Collection Create Collection
</div> </div>
<div className="dropdown-item" onClick={(e) => { <div
menuDropdownTippyRef.current.hide(); className="dropdown-item"
handleImportCollection(); onClick={(e) => {
}}> menuDropdownTippyRef.current.hide();
handleImportCollection();
}}
>
Import Collection Import Collection
</div> </div>
<div className="dropdown-item" onClick={(e) => { <div
menuDropdownTippyRef.current.hide(); className="dropdown-item"
setAddCollectionToWSModalOpen(true); onClick={(e) => {
}}> menuDropdownTippyRef.current.hide();
setAddCollectionToWSModalOpen(true);
}}
>
Add Collection to Workspace Add Collection to Workspace
</div> </div>
{isPlatformElectron ? ( {isPlatformElectron ? (
<> <>
<div className="font-medium label-item font-medium local-collection-label"> <div className="font-medium label-item font-medium local-collection-label">
<div className='flex items-center'> <div className="flex items-center">
<span className='mr-2'> <span className="mr-2">
<IconFolders size={18} strokeWidth={1.5}/> <IconFolders size={18} strokeWidth={1.5} />
</span>
<span>
Local Collections
</span> </span>
<span>Local Collections</span>
</div> </div>
</div> </div>
<div className="dropdown-item" onClick={(e) => { <div
setCreateCollectionModalOpen('local'); className="dropdown-item"
menuDropdownTippyRef.current.hide(); onClick={(e) => {
}}> setCreateCollectionModalOpen('local');
menuDropdownTippyRef.current.hide();
}}
>
Create Local Collection Create Local Collection
</div> </div>
<div className="dropdown-item" onClick={(e) => { <div
handleOpenLocalCollection(); className="dropdown-item"
menuDropdownTippyRef.current.hide(); onClick={(e) => {
}}> handleOpenLocalCollection();
menuDropdownTippyRef.current.hide();
}}
>
Open Local Collection Open Local Collection
</div> </div>
</> </>
@ -135,12 +138,11 @@ const TitleBar = () => {
Note: Local collections are only available on the desktop app. Note: Local collections are only available on the desktop app.
</div> </div>
)} )}
</Dropdown> </Dropdown>
</div> </div>
</div> </div>
</StyledWrapper> </StyledWrapper>
) );
}; };
export default TitleBar; export default TitleBar;

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