Compare commits

..

1 Commits

Author SHA1 Message Date
Anoop M D
5fc32d035f wip: working on async scripting support 2023-03-28 14:49:57 +05:30
346 changed files with 4006 additions and 41600 deletions

View File

@@ -1,12 +0,0 @@
name: Bump Homebrew Cask
on:
release:
types: [published]
jobs:
bump:
runs-on: macos-10.15
steps:
- name: Bump Homebrew Cask
run: brew bump-cask-pr bruno --version "${GITHUB_REF_NAME#v}"

View File

@@ -1,35 +1,29 @@
name: Unit Tests
on:
push:
branches: [main]
branches: [ main ]
pull_request:
branches: [main]
branches: [ main ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Check package-lock.json
run: npm ci
- name: Install dependencies
run: npm i --legacy-peer-deps
- name: Test Package bruno-query
run: npm run test --workspace=packages/bruno-query
- name: Build Package bruno-query
run: npm run build --workspace=packages/bruno-query
- name: Test Package bruno-lang
run: npm run test --workspace=packages/bruno-lang
- name: Test Package bruno-schema
run: npm run test --workspace=packages/bruno-schema
- name: Test Package bruno-app
run: npm run test --workspace=packages/bruno-app
- name: Test Package bruno-js
run: npm run test --workspace=packages/bruno-js
- name: Test Package bruno-cli
run: npm run test --workspace=packages/bruno-cli
- name: Test Package bruno-electron
run: npm run test --workspace=packages/bruno-electron
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install dependencies
run: npm i --legacy-peer-deps
- name: Test Package bruno-query
run: npm run test --workspace=packages/bruno-query
- name: Build Package bruno-query
run: npm run build --workspace=packages/bruno-query
- name: Test Package bruno-lang
run: npm run test --workspace=packages/bruno-lang
- name: Test Package bruno-schema
run: npm run test --workspace=packages/bruno-schema
- name: Test Package bruno-app
run: npm run test --workspace=packages/bruno-app
- name: Test Package bruno-js
run: npm run test --workspace=packages/bruno-js

5
.gitignore vendored
View File

@@ -4,6 +4,7 @@
node_modules
yarn.lock
pnpm-lock.yaml
package-lock.json
.pnp
.pnp.js
@@ -40,7 +41,3 @@ yarn-error.log*
/test-results/
/playwright-report/
/playwright/.cache/
#dev editor
bruno.iml
.idea

View File

@@ -1,4 +0,0 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx pretty-quick --staged

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

View File

@@ -1,5 +1,3 @@
**English** | [Русский](/contributing_ru.md)
## Lets make bruno better, together !!
I am happy that you are looking to improve bruno. Below are the guidelines to get started bringing up bruno on your computer.
@@ -21,7 +19,7 @@ Libraries we use
### Dependencies
You would need [Node v18.x or the latest LTS version](https://nodejs.org/en/) and npm 8.x. We use npm workspaces in the project
You would need [Node v14.x or the latest LTS version](https://nodejs.org/en/) and npm 8.x. We use npm workspaces in the project
### Lets start coding

View File

@@ -1,37 +0,0 @@
[English](/contributing.md) | **Русский**
## Давайте вместе сделаем Бруно лучше!!!
Я рад, что Вы хотите усовершенствовать bruno. Ниже приведены рекомендации по запуску bruno на вашем компьютере.
### Стек
Bruno построен с использованием NextJs и React. Мы также используем electron для поставки десктопной версии ( которая поддерживает локальные коллекции )
Библиотеки, которые мы используем
- CSS - Tailwind
- Редакторы кода - Codemirror
- Управление состоянием - Redux
- Иконки - Tabler Icons
- Формы - formik
- Валидация схем - Yup
- Запросы клиента - axios
- Наблюдатель за файловой системой - chokidar
### Зависимости
Вам потребуется [Node v18.x или последняя версия LTS](https://nodejs.org/en/) и npm 8.x. В проекте мы используем рабочие пространства npm
### Приступим к коду
Пожалуйста, обратитесь к [development_ru.md](docs/development_ru.md) для получения инструкций по запуску локальной среды разработки.
### Создание Pull Request
- Пожалуйста, пусть PR будет небольшим и сфокусированным на одной вещи
- Пожалуйста, соблюдайте формат создания веток
- feature/[название функции]: Эта ветка должна содержать изменения для конкретной функции
- Пример: feature/dark-mode
- bugfix/[название ошибки]: Эта ветка должна содержать только исправления для конкретной ошибки
- Пример bugfix/bug-1

View File

@@ -1,12 +1,9 @@
**English** | [Русский](/docs/development_ru.md)
## Development
Bruno is being developed as a desktop app. You need to load the app by running the nextjs app in one terminal and then run the electron app in another terminal.
Bruno is deing developed as a desktop app. You need to load the app by running the nextjs app in one terminal and then run the electron app in another terminal.
### Dependencies
- NodeJS v18
* NodeJS v18
### Local Development
@@ -18,6 +15,7 @@ nvm use
npm i --legacy-peer-deps
# build graphql docs
# note: you can for now ignore the error thrown while building the graphql docs
npm run build:graphql-docs
# build bruno query

View File

@@ -1,55 +0,0 @@
[English](/docs/development.md) | **Русский**
## Разработка
Bruno разрабатывается как десктопное приложение. Необходимо загрузить приложение, запустив приложение nextjs в одном терминале, а затем запустить приложение electron в другом терминале.
### Зависимости
- NodeJS v18
### Локальная разработка
```bash
# используйте nodejs 18 версии
nvm use
# установите зависимости
npm i --legacy-peer-deps
# билд документации по graphql
npm run build:graphql-docs
# билд bruno query
npm run build:bruno-query
# запустить next приложение ( терминал 1 )
npm run dev:web
# запустить приложение electron ( терминал 2 )
npm run dev:electron
```
### Устранение неисправностей
При запуске `npm install` может возникнуть ошибка `Unsupported platform`. Чтобы исправить это, необходимо удалить `node_modules` и `package-lock.json` и запустить `npm install`. В результате будут установлены все пакеты, необходимые для работы приложения.
```shell
# Удаление node_modules в подкаталогах
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
rm -rf "$dir"
done
# Удаление package-lock в подкаталогах
find . -type f -name "package-lock.json" -delete
```
### Тестирование
```bash
# bruno-schema
npm test --workspace=packages/bruno-schema
# bruno-lang
npm test --workspace=packages/bruno-lang
```

29802
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,14 +13,11 @@
"packages/bruno-testbench",
"packages/bruno-graphql-docs"
],
"homepage": "https://usebruno.com",
"devDependencies": {
"@faker-js/faker": "^7.6.0",
"@jest/globals": "^29.2.0",
"@playwright/test": "^1.27.1",
"husky": "^8.0.3",
"jest": "^29.2.0",
"pretty-quick": "^3.1.3",
"randomstring": "^1.2.2",
"ts-jest": "^29.0.5"
},
@@ -33,10 +30,9 @@
"build:graphql-docs": "npm run build --workspace=packages/bruno-graphql-docs",
"build:electron": "./scripts/build-electron.sh",
"test:e2e": "npx playwright test",
"test:report": "npx playwright show-report",
"prepare": "husky install"
"test:report": "npx playwright show-report"
},
"overrides": {
"rollup": "3.2.5"
}
}
}

View File

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

View File

@@ -18,7 +18,7 @@
"@tabler/icons": "^1.46.0",
"@tippyjs/react": "^4.2.6",
"@usebruno/graphql-docs": "0.1.0",
"@usebruno/schema": "0.5.0",
"@usebruno/schema": "0.3.1",
"axios": "^0.26.0",
"classnames": "^2.3.1",
"codemirror": "^5.65.2",
@@ -30,11 +30,8 @@
"graphiql": "^1.5.9",
"graphql": "^16.6.0",
"graphql-request": "^3.7.0",
"handlebars": "^4.7.8",
"httpsnippet": "^3.0.1",
"idb": "^7.0.0",
"immer": "^9.0.15",
"know-your-http-well": "^0.5.0",
"lodash": "^4.17.21",
"markdown-it": "^13.0.1",
"mousetrap": "^1.6.5",
@@ -50,13 +47,11 @@
"react-dom": "18.2.0",
"react-github-btn": "^1.4.0",
"react-hot-toast": "^2.4.0",
"react-inspector": "^6.0.2",
"react-redux": "^7.2.6",
"react-tooltip": "^5.5.2",
"sass": "^1.46.0",
"styled-components": "^5.3.3",
"tailwindcss": "^2.2.19",
"xml-formatter": "^3.5.0",
"yup": "^0.32.11"
},
"devDependencies": {

View File

@@ -14,11 +14,7 @@ const Bruno = ({ width }) => {
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"
/>
<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 id="hair" />
<g id="skin" />
@@ -88,27 +84,8 @@ const Bruno = ({ width }) => {
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"
/>
<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>
</svg>
);

View File

@@ -29,7 +29,7 @@ const BrunoSupport = ({ onClose }) => {
<div className="mt-2">
<a href="https://github.com/usebruno/bruno" target="_blank" className="flex items-end">
<IconBrandGithub size={18} strokeWidth={2} />
<span className="label ml-2">GitHub</span>
<span className="label ml-2">Github</span>
</a>
</div>
<div className="mt-2">

View File

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

View File

@@ -43,7 +43,7 @@ export default class CodeEditor extends React.Component {
foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
readOnly: this.props.readOnly,
scrollbarStyle: 'overlay',
scrollbarStyle: "overlay",
theme: this.props.theme === 'dark' ? 'monokai' : 'default',
extraKeys: {
'Cmd-Enter': () => {
@@ -80,7 +80,7 @@ export default class CodeEditor extends React.Component {
}
componentDidUpdate(prevProps) {
// Ensure the changes caused by this update are not interpreted as
// Ensure the changes caused by this update are not interpretted as
// user-input changes which could otherwise result in an infinite
// event loop.
this.ignoreChangeEvent = true;
@@ -96,7 +96,7 @@ export default class CodeEditor extends React.Component {
this.editor.setValue(this.props.value);
}
if (this.editor) {
if(this.editor) {
let variables = getEnvironmentVariables(this.props.collection);
if (!isEqual(variables, this.variables)) {
this.addOverlay();
@@ -119,7 +119,7 @@ export default class CodeEditor extends React.Component {
render() {
return (
<StyledWrapper
className="h-full w-full"
className="h-full"
aria-label="Code Editor"
ref={(node) => {
this._node = node;
@@ -135,7 +135,7 @@ export default class CodeEditor extends React.Component {
defineCodeMirrorBrunoVariablesMode(variables, mode);
this.editor.setOption('mode', 'brunovariables');
};
}
_onEdit = () => {
if (!this.ignoreChangeEvent && this.editor) {

View File

@@ -1,28 +0,0 @@
import styled from 'styled-components';
const Wrapper = styled.div`
font-size: 0.8125rem;
.auth-mode-selector {
background: transparent;
.auth-mode-label {
color: ${(props) => props.theme.colors.text.yellow};
}
.dropdown-item {
padding: 0.2rem 0.6rem !important;
}
.label-item {
padding: 0.2rem 0.6rem !important;
}
}
.caret {
color: rgb(140, 140, 140);
fill: rgb(140 140 140);
}
`;
export default Wrapper;

View File

@@ -1,69 +0,0 @@
import React, { useRef, forwardRef } from 'react';
import get from 'lodash/get';
import { IconCaretDown } from '@tabler/icons';
import Dropdown from 'components/Dropdown';
import { useDispatch } from 'react-redux';
import { updateCollectionAuthMode } from 'providers/ReduxStore/slices/collections';
import { humanizeRequestAuthMode } from 'utils/collections';
import StyledWrapper from './StyledWrapper';
const AuthMode = ({ collection }) => {
const dispatch = useDispatch();
const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const authMode = get(collection, 'root.request.auth.mode');
const Icon = forwardRef((props, ref) => {
return (
<div ref={ref} className="flex items-center justify-center auth-mode-label select-none">
{humanizeRequestAuthMode(authMode)} <IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
</div>
);
});
const onModeChange = (value) => {
dispatch(
updateCollectionAuthMode({
collectionUid: collection.uid,
mode: value
})
);
};
return (
<StyledWrapper>
<div className="inline-flex items-center cursor-pointer auth-mode-selector">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('basic');
}}
>
Basic Auth
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('bearer');
}}
>
Bearer Token
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('none');
}}
>
No Auth
</div>
</Dropdown>
</div>
</StyledWrapper>
);
};
export default AuthMode;

View File

@@ -1,16 +0,0 @@
import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: 0.8125rem;
}
.single-line-editor-wrapper {
padding: 0.15rem 0.4rem;
border-radius: 3px;
border: solid 1px ${(props) => props.theme.input.border};
background-color: ${(props) => props.theme.input.bg};
}
`;
export default Wrapper;

View File

@@ -1,71 +0,0 @@
import React from 'react';
import get from 'lodash/get';
import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux';
import SingleLineEditor from 'components/SingleLineEditor';
import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
const BasicAuth = ({ collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const basicAuth = get(collection, 'root.request.auth.basic', {});
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
const handleUsernameChange = (username) => {
dispatch(
updateCollectionAuth({
mode: 'basic',
collectionUid: collection.uid,
content: {
username: username,
password: basicAuth.password
}
})
);
};
const handlePasswordChange = (password) => {
dispatch(
updateCollectionAuth({
mode: 'basic',
collectionUid: collection.uid,
content: {
username: basicAuth.username,
password: password
}
})
);
};
return (
<StyledWrapper className="mt-2 w-full">
<label className="block font-medium mb-2">Username</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={basicAuth.username || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleUsernameChange(val)}
collection={collection}
/>
</div>
<label className="block font-medium mb-2">Password</label>
<div className="single-line-editor-wrapper">
<SingleLineEditor
value={basicAuth.password || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handlePasswordChange(val)}
collection={collection}
/>
</div>
</StyledWrapper>
);
};
export default BasicAuth;

View File

@@ -1,16 +0,0 @@
import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: 0.8125rem;
}
.single-line-editor-wrapper {
padding: 0.15rem 0.4rem;
border-radius: 3px;
border: solid 1px ${(props) => props.theme.input.border};
background-color: ${(props) => props.theme.input.bg};
}
`;
export default Wrapper;

View File

@@ -1,46 +0,0 @@
import React from 'react';
import get from 'lodash/get';
import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux';
import SingleLineEditor from 'components/SingleLineEditor';
import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
const BearerAuth = ({ collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const bearerToken = get(collection, 'root.request.auth.bearer.token');
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
const handleTokenChange = (token) => {
dispatch(
updateCollectionAuth({
mode: 'bearer',
collectionUid: collection.uid,
content: {
token: token
}
})
);
};
return (
<StyledWrapper className="mt-2 w-full">
<label className="block font-medium mb-2">Token</label>
<div className="single-line-editor-wrapper">
<SingleLineEditor
value={bearerToken}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleTokenChange(val)}
collection={collection}
/>
</div>
</StyledWrapper>
);
};
export default BearerAuth;

View File

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

View File

@@ -1,42 +0,0 @@
import React from 'react';
import get from 'lodash/get';
import { useDispatch } from 'react-redux';
import AuthMode from './AuthMode';
import BearerAuth from './BearerAuth';
import BasicAuth from './BasicAuth';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
const Auth = ({ collection }) => {
const authMode = get(collection, 'root.request.auth.mode');
const dispatch = useDispatch();
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
const getAuthView = () => {
switch (authMode) {
case 'basic': {
return <BasicAuth collection={collection} />;
}
case 'bearer': {
return <BearerAuth collection={collection} />;
}
}
};
return (
<StyledWrapper className="w-full mt-2">
<div className="flex flex-grow justify-start items-center">
<AuthMode collection={collection} />
</div>
{getAuthView()}
<div className="mt-6">
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
Save
</button>
</div>
</StyledWrapper>
);
};
export default Auth;

View File

@@ -1,56 +0,0 @@
import styled from 'styled-components';
const Wrapper = styled.div`
table {
width: 100%;
border-collapse: collapse;
font-weight: 600;
table-layout: fixed;
thead,
td {
border: 1px solid ${(props) => props.theme.table.border};
}
thead {
color: ${(props) => props.theme.table.thead.color};
font-size: 0.8125rem;
user-select: none;
}
td {
padding: 6px 10px;
&:nth-child(1) {
width: 30%;
}
&:nth-child(3) {
width: 70px;
}
}
}
.btn-add-header {
font-size: 0.8125rem;
}
input[type='text'] {
width: 100%;
border: solid 1px transparent;
outline: none !important;
background-color: inherit;
&:focus {
outline: none !important;
border: solid 1px transparent;
}
}
input[type='checkbox'] {
cursor: pointer;
position: relative;
top: 1px;
}
`;
export default Wrapper;

View File

@@ -1,151 +0,0 @@
import React from 'react';
import get from 'lodash/get';
import cloneDeep from 'lodash/cloneDeep';
import { IconTrash } from '@tabler/icons';
import { useDispatch } from 'react-redux';
import { useTheme } from 'providers/Theme';
import {
addCollectionHeader,
updateCollectionHeader,
deleteCollectionHeader
} from 'providers/ReduxStore/slices/collections';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import SingleLineEditor from 'components/SingleLineEditor';
import StyledWrapper from './StyledWrapper';
import { headers as StandardHTTPHeaders } from 'know-your-http-well';
const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header);
const Headers = ({ collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const headers = get(collection, 'root.request.headers', []);
const addHeader = () => {
dispatch(
addCollectionHeader({
collectionUid: collection.uid
})
);
};
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
const handleHeaderValueChange = (e, _header, type) => {
const header = cloneDeep(_header);
switch (type) {
case 'name': {
header.name = e.target.value;
break;
}
case 'value': {
header.value = e.target.value;
break;
}
case 'enabled': {
header.enabled = e.target.checked;
break;
}
}
dispatch(
updateCollectionHeader({
header: header,
collectionUid: collection.uid
})
);
};
const handleRemoveHeader = (header) => {
dispatch(
deleteCollectionHeader({
headerUid: header.uid,
collectionUid: collection.uid
})
);
};
return (
<StyledWrapper className="w-full">
<table>
<thead>
<tr>
<td>Name</td>
<td>Value</td>
<td></td>
</tr>
</thead>
<tbody>
{headers && headers.length
? headers.map((header) => {
return (
<tr key={header.uid}>
<td>
<SingleLineEditor
value={header.name}
theme={storedTheme}
onSave={handleSave}
onChange={(newValue) =>
handleHeaderValueChange(
{
target: {
value: newValue
}
},
header,
'name'
)
}
autocomplete={headerAutoCompleteList}
collection={collection}
/>
</td>
<td>
<SingleLineEditor
value={header.value}
theme={storedTheme}
onSave={handleSave}
onChange={(newValue) =>
handleHeaderValueChange(
{
target: {
value: newValue
}
},
header,
'value'
)
}
collection={collection}
/>
</td>
<td>
<div className="flex items-center">
<input
type="checkbox"
checked={header.enabled}
tabIndex="-1"
className="mr-3 mousetrap"
onChange={(e) => handleHeaderValueChange(e, header, 'enabled')}
/>
<button tabIndex="-1" onClick={() => handleRemoveHeader(header)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>
</td>
</tr>
);
})
: null}
</tbody>
</table>
<button className="btn-add-header text-link pr-2 py-3 mt-2 select-none" onClick={addHeader}>
+ Add Header
</button>
<div className="mt-6">
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
Save
</button>
</div>
</StyledWrapper>
);
};
export default Headers;

View File

@@ -1,27 +0,0 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
.settings-label {
width: 80px;
}
.textbox {
border: 1px solid #ccc;
padding: 0.15rem 0.45rem;
box-shadow: none;
border-radius: 0px;
outline: none;
box-shadow: none;
transition: border-color ease-in-out 0.1s;
border-radius: 3px;
background-color: ${(props) => props.theme.modal.input.bg};
border: 1px solid ${(props) => props.theme.modal.input.border};
&:focus {
border: solid 1px ${(props) => props.theme.modal.input.focusBorder} !important;
outline: none !important;
}
}
`;
export default StyledWrapper;

View File

@@ -1,200 +0,0 @@
import React, { useEffect } from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import StyledWrapper from './StyledWrapper';
const ProxySettings = ({ proxyConfig, onUpdate }) => {
const formik = useFormik({
initialValues: {
enabled: proxyConfig.enabled || false,
protocol: proxyConfig.protocol || 'http',
hostname: proxyConfig.hostname || '',
port: proxyConfig.port || '',
auth: {
enabled: proxyConfig.auth ? proxyConfig.auth.enabled || false : false,
username: proxyConfig.auth ? proxyConfig.auth.username || '' : '',
password: proxyConfig.auth ? proxyConfig.auth.password || '' : ''
}
},
validationSchema: Yup.object({
enabled: Yup.boolean(),
protocol: Yup.string().oneOf(['http', 'https', 'socks5']),
hostname: Yup.string().max(1024),
port: Yup.number().min(0).max(65535),
auth: Yup.object({
enabled: Yup.boolean(),
username: Yup.string().max(1024),
password: Yup.string().max(1024)
})
}),
onSubmit: (values) => {
onUpdate(values);
}
});
useEffect(() => {
formik.setValues({
enabled: proxyConfig.enabled || false,
protocol: proxyConfig.protocol || 'http',
hostname: proxyConfig.hostname || '',
port: proxyConfig.port || '',
auth: {
enabled: proxyConfig.auth ? proxyConfig.auth.enabled || false : false,
username: proxyConfig.auth ? proxyConfig.auth.username || '' : '',
password: proxyConfig.auth ? proxyConfig.auth.password || '' : ''
}
});
}, [proxyConfig]);
return (
<StyledWrapper>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="enabled">
Enabled
</label>
<input type="checkbox" name="enabled" checked={formik.values.enabled} onChange={formik.handleChange} />
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="protocol">
Protocol
</label>
<div className="flex items-center">
<label className="flex items-center">
<input
type="radio"
name="protocol"
value="http"
checked={formik.values.protocol === 'http'}
onChange={formik.handleChange}
className="mr-1"
/>
http
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="https"
checked={formik.values.protocol === 'https'}
onChange={formik.handleChange}
className="mr-1"
/>
https
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks5"
checked={formik.values.protocol === 'socks5'}
onChange={formik.handleChange}
className="mr-1"
/>
socks5
</label>
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="hostname">
Hostname
</label>
<input
id="hostname"
type="text"
name="hostname"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.hostname || ''}
/>
{formik.touched.hostname && formik.errors.hostname ? (
<div className="text-red-500">{formik.errors.hostname}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="port">
Port
</label>
<input
id="port"
type="number"
name="port"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.port}
/>
{formik.touched.port && formik.errors.port ? <div className="text-red-500">{formik.errors.port}</div> : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.enabled">
Auth
</label>
<input
type="checkbox"
name="auth.enabled"
checked={formik.values.auth.enabled}
onChange={formik.handleChange}
/>
</div>
<div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.username">
Username
</label>
<input
id="auth.username"
type="text"
name="auth.username"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={formik.values.auth.username}
onChange={formik.handleChange}
/>
{formik.touched.auth?.username && formik.errors.auth?.username ? (
<div className="text-red-500">{formik.errors.auth.username}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.password">
Password
</label>
<input
id="auth.password"
type="text"
name="auth.password"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={formik.values.auth.password}
onChange={formik.handleChange}
/>
{formik.touched.auth?.password && formik.errors.auth?.password ? (
<div className="text-red-500">{formik.errors.auth.password}</div>
) : null}
</div>
</div>
<div className="mt-6">
<button type="submit" className="submit btn btn-sm btn-secondary">
Save
</button>
</div>
</form>
</StyledWrapper>
);
};
export default ProxySettings;

View File

@@ -1,13 +0,0 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
div.CodeMirror {
height: inherit;
}
div.title {
color: var(--color-tab-inactive);
}
`;
export default StyledWrapper;

View File

@@ -1,73 +0,0 @@
import React from 'react';
import get from 'lodash/get';
import { useDispatch } from 'react-redux';
import CodeEditor from 'components/CodeEditor';
import { updateCollectionRequestScript, updateCollectionResponseScript } from 'providers/ReduxStore/slices/collections';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import { useTheme } from 'providers/Theme';
import StyledWrapper from './StyledWrapper';
const Script = ({ collection }) => {
const dispatch = useDispatch();
const requestScript = get(collection, 'root.request.script.req', '');
const responseScript = get(collection, 'root.request.script.res', '');
const { storedTheme } = useTheme();
const onRequestScriptEdit = (value) => {
dispatch(
updateCollectionRequestScript({
script: value,
collectionUid: collection.uid
})
);
};
const onResponseScriptEdit = (value) => {
dispatch(
updateCollectionResponseScript({
script: value,
collectionUid: collection.uid
})
);
};
const handleSave = () => {
dispatch(saveCollectionRoot(collection.uid));
};
return (
<StyledWrapper className="w-full flex flex-col">
<div className="flex-1 mt-2">
<div className="mb-1 title text-xs">Pre Request</div>
<CodeEditor
collection={collection}
value={requestScript || ''}
theme={storedTheme}
onEdit={onRequestScriptEdit}
mode="javascript"
onSave={handleSave}
/>
</div>
<div className="flex-1 mt-6">
<div className="mt-1 mb-1 title text-xs">Post Response</div>
<CodeEditor
collection={collection}
value={responseScript || ''}
theme={storedTheme}
onEdit={onResponseScriptEdit}
mode="javascript"
onSave={handleSave}
/>
</div>
<div className="mt-12">
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
Save
</button>
</div>
</StyledWrapper>
);
};
export default Script;

View File

@@ -1,46 +0,0 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
max-width: 800px;
div.tabs {
div.tab {
padding: 6px 0px;
border: none;
border-bottom: solid 2px transparent;
margin-right: 1.25rem;
color: var(--color-tab-inactive);
cursor: pointer;
&:focus,
&:active,
&:focus-within,
&:focus-visible,
&:target {
outline: none !important;
box-shadow: none !important;
}
&.active {
color: ${(props) => props.theme.tabs.active.color} !important;
border-bottom: solid 2px ${(props) => props.theme.tabs.active.border} !important;
}
}
}
table {
thead,
td {
border: 1px solid ${(props) => props.theme.table.border};
li {
background-color: ${(props) => props.theme.bg} !important;
}
}
}
.muted {
color: ${(props) => props.theme.colors.text.muted};
}
`;
export default StyledWrapper;

View File

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

View File

@@ -1,47 +0,0 @@
import React from 'react';
import get from 'lodash/get';
import { useDispatch } from 'react-redux';
import CodeEditor from 'components/CodeEditor';
import { updateCollectionTests } from 'providers/ReduxStore/slices/collections';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import { useTheme } from 'providers/Theme';
import StyledWrapper from './StyledWrapper';
const Tests = ({ collection }) => {
const dispatch = useDispatch();
const tests = get(collection, 'root.request.tests', '');
const { storedTheme } = useTheme();
const onEdit = (value) => {
dispatch(
updateCollectionTests({
tests: value,
collectionUid: collection.uid
})
);
};
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
return (
<StyledWrapper className="w-full flex flex-col h-full">
<CodeEditor
collection={collection}
value={tests || ''}
theme={storedTheme}
onEdit={onEdit}
mode="javascript"
onSave={handleSave}
/>
<div className="mt-6">
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
Save
</button>
</div>
</StyledWrapper>
);
};
export default Tests;

View File

@@ -1,90 +0,0 @@
import React from 'react';
import classnames from 'classnames';
import get from 'lodash/get';
import cloneDeep from 'lodash/cloneDeep';
import toast from 'react-hot-toast';
import { updateBrunoConfig } from 'providers/ReduxStore/slices/collections/actions';
import { updateSettingsSelectedTab } from 'providers/ReduxStore/slices/collections';
import { useDispatch } from 'react-redux';
import ProxySettings from './ProxySettings';
import Headers from './Headers';
import Auth from './Auth';
import Script from './Script';
import Test from './Tests';
import StyledWrapper from './StyledWrapper';
const CollectionSettings = ({ collection }) => {
const dispatch = useDispatch();
const tab = collection.settingsSelectedTab;
const setTab = (tab) => {
dispatch(
updateSettingsSelectedTab({
collectionUid: collection.uid,
tab
})
);
};
const proxyConfig = get(collection, 'brunoConfig.proxy', {});
const onProxySettingsUpdate = (config) => {
const brunoConfig = cloneDeep(collection.brunoConfig);
brunoConfig.proxy = config;
dispatch(updateBrunoConfig(brunoConfig, collection.uid))
.then(() => {
toast.success('Collection settings updated successfully');
})
.catch((err) => console.log(err) && toast.error('Failed to update collection settings'));
};
const getTabPanel = (tab) => {
switch (tab) {
case 'headers': {
return <Headers collection={collection} />;
}
case 'auth': {
return <Auth collection={collection} />;
}
case 'script': {
return <Script collection={collection} />;
}
case 'tests': {
return <Test collection={collection} />;
}
case 'proxy': {
return <ProxySettings proxyConfig={proxyConfig} onUpdate={onProxySettingsUpdate} />;
}
}
};
const getTabClassname = (tabName) => {
return classnames(`tab select-none ${tabName}`, {
active: tabName === tab
});
};
return (
<StyledWrapper className="flex flex-col h-full relative px-4 py-4">
<div className="flex flex-wrap items-center tabs" role="tablist">
<div className={getTabClassname('headers')} role="tab" onClick={() => setTab('headers')}>
Headers
</div>
<div className={getTabClassname('auth')} role="tab" onClick={() => setTab('auth')}>
Auth
</div>
<div className={getTabClassname('script')} role="tab" onClick={() => setTab('script')}>
Script
</div>
<div className={getTabClassname('tests')} role="tab" onClick={() => setTab('tests')}>
Tests
</div>
<div className={getTabClassname('proxy')} role="tab" onClick={() => setTab('proxy')}>
Proxy
</div>
</div>
<section className={`flex ${['auth', 'script'].includes(tab) ? '' : 'mt-4'}`}>{getTabPanel(tab)}</section>
</StyledWrapper>
);
};
export default CollectionSettings;

View File

@@ -43,7 +43,7 @@ const Wrapper = styled.div`
}
&.border-top {
border-top: solid 1px ${(props) => props.theme.dropdown.separator};
border-top: solid 1px ${(props) => props.theme.dropdown.seperator};
}
}
}

View File

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

View File

@@ -2,7 +2,7 @@ import styled from 'styled-components';
const Wrapper = styled.div`
.current-enviroment {
background-color: ${(props) => props.theme.sidebar.badge.bg};
background-color: ${(props) => props.theme.sidebar.badge.bg};
border-radius: 15px;
.caret {

View File

@@ -35,7 +35,7 @@ const EnvironmentSelector = ({ collection }) => {
toast.success(`No Environments are active now`);
}
})
.catch((err) => console.log(err) && toast.error('An error occurred while selecting the environment'));
.catch((err) => console.log(err) && toast.error('An error occured while selecting the environment'));
};
return (
@@ -64,7 +64,7 @@ const EnvironmentSelector = ({ collection }) => {
}}
>
<IconDatabaseOff size={18} strokeWidth={1.5} />
<span className="ml-2">No Environment</span>
<span className='ml-2'>No Environment</span>
</div>
<div className="dropdown-item border-top" onClick={() => setOpenSettingsModal(true)}>
<div className="pr-2 text-gray-600">

View File

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

View File

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

View File

@@ -14,19 +14,13 @@ const DeleteEnvironment = ({ onClose, environment, collection }) => {
toast.success('Environment deleted successfully');
onClose();
})
.catch(() => toast.error('An error occurred while deleting the environment'));
.catch(() => toast.error('An error occured while deleting the environment'));
};
return (
<Portal>
<StyledWrapper>
<Modal
size="sm"
title={'Delete Environment'}
confirmText="Delete"
handleConfirm={onConfirm}
handleCancel={onClose}
>
<Modal size="sm" title={'Delete Environment'} confirmText="Delete" handleConfirm={onConfirm} handleCancel={onClose}>
Are you sure you want to delete <span className="font-semibold">{environment.name}</span> ?
</Modal>
</StyledWrapper>

View File

@@ -5,30 +5,18 @@ const Wrapper = styled.div`
width: 100%;
border-collapse: collapse;
font-weight: 600;
table-layout: fixed;
thead,
td {
border: 1px solid ${(props) => props.theme.collection.environment.settings.gridBorder};
padding: 4px 10px;
&:nth-child(1),
&:nth-child(4),
&:nth-child(5) {
width: 70px;
}
&:nth-child(2) {
width: 25%;
}
}
thead {
color: ${(props) => props.theme.table.thead.color};
color: ${(props) => props.theme.table.thead.color};;
font-size: 0.8125rem;
user-select: none;
}
thead td {
td {
padding: 6px 10px;
}
}

View File

@@ -2,16 +2,13 @@ import React, { useReducer } from 'react';
import toast from 'react-hot-toast';
import cloneDeep from 'lodash/cloneDeep';
import { IconTrash } from '@tabler/icons';
import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux';
import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions';
import reducer from './reducer';
import SingleLineEditor from 'components/SingleLineEditor';
import StyledWrapper from './StyledWrapper';
const EnvironmentVariables = ({ environment, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const [state, reducerDispatch] = useReducer(reducer, { hasChanges: false, variables: environment.variables || [] });
const { variables, hasChanges } = state;
@@ -23,7 +20,7 @@ const EnvironmentVariables = ({ environment, collection }) => {
type: 'CHANGES_SAVED'
});
})
.catch(() => toast.error('An error occurred while saving the changes'));
.catch(() => toast.error('An error occured while saving the changes'));
};
const addVariable = () => {
@@ -47,10 +44,6 @@ const EnvironmentVariables = ({ environment, collection }) => {
variable.enabled = e.target.checked;
break;
}
case 'secret': {
variable.secret = e.target.checked;
break;
}
}
reducerDispatch({
type: 'UPDATE_VAR',
@@ -70,10 +63,8 @@ const EnvironmentVariables = ({ environment, collection }) => {
<table>
<thead>
<tr>
<td>Enabled</td>
<td>Name</td>
<td>Value</td>
<td>Secret</td>
<td></td>
</tr>
</thead>
@@ -82,14 +73,6 @@ const EnvironmentVariables = ({ environment, collection }) => {
? variables.map((variable, index) => {
return (
<tr key={variable.uid}>
<td className="text-center">
<input
type="checkbox"
checked={variable.enabled}
className="mr-3 mousetrap"
onChange={(e) => handleVarChange(e, variable, 'enabled')}
/>
</td>
<td>
<input
type="text"
@@ -102,26 +85,25 @@ const EnvironmentVariables = ({ environment, collection }) => {
onChange={(e) => handleVarChange(e, variable, 'name')}
/>
</td>
<td>
<SingleLineEditor
value={variable.value}
theme={storedTheme}
onChange={(newValue) => handleVarChange({ target: { value: newValue } }, variable, 'value')}
collection={collection}
/>
</td>
<td>
<input
type="checkbox"
checked={variable.secret}
className="mr-3 mousetrap"
onChange={(e) => handleVarChange(e, variable, 'secret')}
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={variable.value}
className="mousetrap"
onChange={(e) => handleVarChange(e, variable, 'value')}
/>
</td>
<td>
<button onClick={() => handleRemoveVars(variable)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
<div className="flex items-center">
<input type="checkbox" checked={variable.enabled} className="mr-3 mousetrap" onChange={(e) => handleVarChange(e, variable, 'enabled')} />
<button onClick={() => handleRemoveVars(variable)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>
</td>
</tr>
);
@@ -137,12 +119,7 @@ const EnvironmentVariables = ({ environment, collection }) => {
</div>
<div>
<button
type="submit"
className="submit btn btn-md btn-secondary mt-2"
disabled={!hasChanges}
onClick={saveChanges}
>
<button type="submit" className="submit btn btn-md btn-secondary mt-2" disabled={!hasChanges} onClick={saveChanges}>
Save
</button>
</div>

View File

@@ -12,7 +12,6 @@ const reducer = (state, action) => {
name: '',
value: '',
type: 'text',
secret: false,
enabled: true
});
draft.hasChanges = true;
@@ -25,7 +24,6 @@ const reducer = (state, action) => {
variable.name = action.variable.name;
variable.value = action.variable.value;
variable.enabled = action.variable.enabled;
variable.secret = action.variable.secret;
draft.hasChanges = true;
});
}

View File

@@ -1,30 +1,17 @@
import { IconCopy, IconDatabase, IconEdit, IconTrash } from '@tabler/icons';
import { useState } from 'react';
import CopyEnvironment from '../../CopyEnvironment';
import DeleteEnvironment from '../../DeleteEnvironment';
import RenameEnvironment from '../../RenameEnvironment';
import React, { useState } from 'react';
import { IconEdit, IconTrash, IconDatabase } from '@tabler/icons';
import EnvironmentVariables from './EnvironmentVariables';
import RenameEnvironment from '../../RenameEnvironment';
import DeleteEnvironment from '../../DeleteEnvironment';
const EnvironmentDetails = ({ environment, collection }) => {
const [openEditModal, setOpenEditModal] = useState(false);
const [openDeleteModal, setOpenDeleteModal] = useState(false);
const [openCopyModal, setOpenCopyModal] = useState(false);
return (
<div className="px-6 flex-grow flex flex-col pt-6" style={{ maxWidth: '700px' }}>
{openEditModal && (
<RenameEnvironment onClose={() => setOpenEditModal(false)} environment={environment} collection={collection} />
)}
{openDeleteModal && (
<DeleteEnvironment
onClose={() => setOpenDeleteModal(false)}
environment={environment}
collection={collection}
/>
)}
{openCopyModal && (
<CopyEnvironment onClose={() => setOpenCopyModal(false)} environment={environment} collection={collection} />
)}
{openEditModal && <RenameEnvironment onClose={() => setOpenEditModal(false)} environment={environment} collection={collection} />}
{openDeleteModal && <DeleteEnvironment onClose={() => setOpenDeleteModal(false)} environment={environment} collection={collection} />}
<div className="flex">
<div className="flex flex-grow items-center">
<IconDatabase className="cursor-pointer" size={20} strokeWidth={1.5} />
@@ -32,7 +19,6 @@ const EnvironmentDetails = ({ environment, collection }) => {
</div>
<div className="flex gap-x-4 pl-4">
<IconEdit className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenEditModal(true)} />
<IconCopy className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenCopyModal(true)} />
<IconTrash className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenDeleteModal(true)} />
</div>
</div>

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,6 @@
import styled from 'styled-components';
const Wrapper = styled.div`
color: ${(props) => props.theme.text};
&.modal--animate-out {
animation: fade-out 0.5s forwards cubic-bezier(0.19, 1, 0.22, 1);
@@ -21,8 +19,7 @@ const Wrapper = styled.div`
align-items: flex-start;
justify-content: center;
overflow-y: auto;
z-index: 10;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1003;
}
.bruno-modal-card {
@@ -31,7 +28,7 @@ const Wrapper = styled.div`
background: var(--color-background-top);
border-radius: var(--border-radius);
position: relative;
z-index: 10;
z-index: 1003;
max-width: calc(100% - var(--spacing-base-unit));
box-shadow: var(--box-shadow-base);
display: flex;
@@ -103,7 +100,7 @@ const Wrapper = styled.div`
border-radius: 0px;
outline: none;
box-shadow: none;
transition: border-color ease-in-out 0.1s;
transition: border-color ease-in-out .1s;
border-radius: 3px;
background-color: ${(props) => props.theme.modal.input.bg};
border: 1px solid ${(props) => props.theme.modal.input.border};

View File

@@ -3,7 +3,7 @@ import StyledWrapper from './StyledWrapper';
const ModalHeader = ({ title, handleCancel }) => (
<div className="bruno-modal-header">
{title ? <div className="bruno-modal-header-title">{title}</div> : null}
{title ? <div className="bruno-modal-heade-title">{title}</div> : null}
{handleCancel ? (
<div className="close cursor-pointer" onClick={handleCancel ? () => handleCancel() : null}>
×
@@ -14,15 +14,7 @@ const ModalHeader = ({ title, handleCancel }) => (
const ModalContent = ({ children }) => <div className="bruno-modal-content px-4 py-6">{children}</div>;
const ModalFooter = ({
confirmText,
cancelText,
handleSubmit,
handleCancel,
confirmDisabled,
hideCancel,
hideFooter
}) => {
const ModalFooter = ({ confirmText, cancelText, handleSubmit, handleCancel, confirmDisabled, hideCancel, hideFooter }) => {
confirmText = confirmText || 'Save';
cancelText = cancelText || 'Cancel';
@@ -38,12 +30,7 @@ const ModalFooter = ({
</button>
</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}
</button>
</span>
@@ -51,30 +38,18 @@ const ModalFooter = ({
);
};
const Modal = ({
size,
title,
confirmText,
cancelText,
handleCancel,
handleConfirm,
children,
confirmDisabled,
hideCancel,
hideFooter,
closeModalFadeTimeout = 500
}) => {
const Modal = ({ size, title, confirmText, cancelText, handleCancel, handleConfirm, children, confirmDisabled, hideCancel, hideFooter }) => {
const [isClosing, setIsClosing] = useState(false);
const escFunction = (event) => {
const escKeyCode = 27;
if (event.keyCode === escKeyCode) {
closeModal({ type: 'esc' });
closeModal();
}
};
const closeModal = (args) => {
const closeModal = () => {
setIsClosing(true);
setTimeout(() => handleCancel(args), closeModalFadeTimeout);
setTimeout(() => handleCancel(), 500);
};
useEffect(() => {
@@ -89,18 +64,18 @@ const Modal = ({
if (isClosing) {
classes += ' modal--animate-out';
}
if (hideFooter) {
if(hideFooter) {
classes += ' modal-footer-none';
}
return (
<StyledWrapper className={classes}>
<div className={`bruno-modal-card modal-${size}`}>
<ModalHeader title={title} handleCancel={() => closeModal({ type: 'icon' })} />
<ModalHeader title={title} handleCancel={() => closeModal()} />
<ModalContent>{children}</ModalContent>
<ModalFooter
confirmText={confirmText}
cancelText={cancelText}
handleCancel={() => closeModal({ type: 'button' })}
handleCancel={() => closeModal()}
handleSubmit={handleConfirm}
confirmDisabled={confirmDisabled}
hideCancel={hideCancel}
@@ -109,12 +84,7 @@ const Modal = ({
</div>
{/* Clicking on backdrop closes the modal */}
<div
className="bruno-modal-backdrop"
onClick={() => {
closeModal({ type: 'backdrop' });
}}
/>
<div className="bruno-modal-backdrop" onClick={() => closeModal()} />
</StyledWrapper>
);
};

View File

@@ -1,7 +0,0 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
color: ${(props) => props.theme.text};
`;
export default StyledWrapper;

View File

@@ -1,38 +0,0 @@
import React, { useState } from 'react';
import { usePreferences } from 'providers/Preferences';
import StyledWrapper from './StyledWrapper';
const General = () => {
const { preferences, setPreferences } = usePreferences();
const [sslVerification, setSslVerification] = useState(preferences.request.sslVerification);
const handleCheckboxChange = () => {
const updatedPreferences = {
...preferences,
request: {
...preferences.request,
sslVerification: !sslVerification
}
};
setPreferences(updatedPreferences)
.then(() => {
setSslVerification(!sslVerification);
})
.catch((err) => {
console.error(err);
});
};
return (
<StyledWrapper>
<div className="flex items-center mt-2">
<input type="checkbox" checked={sslVerification} onChange={handleCheckboxChange} className="mr-3 mousetrap" />
SSL Certificate Verification
</div>
</StyledWrapper>
);
};
export default General;

View File

@@ -1,36 +0,0 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
div.tabs {
margin-top: -0.5rem;
div.tab {
padding: 6px 0px;
border: none;
border-bottom: solid 2px transparent;
margin-right: 1.25rem;
color: var(--color-tab-inactive);
cursor: pointer;
&:focus,
&:active,
&:focus-within,
&:focus-visible,
&:target {
outline: none !important;
box-shadow: none !important;
}
&.active {
color: ${(props) => props.theme.tabs.active.color} !important;
border-bottom: solid 2px ${(props) => props.theme.tabs.active.border} !important;
}
}
}
section.tab-panel {
min-height: 300px;
}
`;
export default StyledWrapper;

View File

@@ -1,44 +0,0 @@
import React from 'react';
import { IconSpeakerphone, IconBrandTwitter, IconBrandGithub, IconBrandDiscord, IconBook } from '@tabler/icons';
import StyledWrapper from './StyledWrapper';
const Support = () => {
return (
<StyledWrapper>
<div className="rows">
<div className="mt-2">
<a href="https://docs.usebruno.com" target="_blank" className="flex items-end">
<IconBook size={18} strokeWidth={2} />
<span className="label ml-2">Documentation</span>
</a>
</div>
<div className="mt-2">
<a href="https://github.com/usebruno/bruno/issues" target="_blank" className="flex items-end">
<IconSpeakerphone size={18} strokeWidth={2} />
<span className="label ml-2">Report Issues</span>
</a>
</div>
<div className="mt-2">
<a href="https://discord.com/invite/KgcZUncpjq" target="_blank" className="flex items-end">
<IconBrandDiscord size={18} strokeWidth={2} />
<span className="label ml-2">Discord</span>
</a>
</div>
<div className="mt-2">
<a href="https://github.com/usebruno/bruno" target="_blank" className="flex items-end">
<IconBrandGithub size={18} strokeWidth={2} />
<span className="label ml-2">GitHub</span>
</a>
</div>
<div className="mt-2">
<a href="https://twitter.com/use_bruno" target="_blank" className="flex items-end">
<IconBrandTwitter size={18} strokeWidth={2} />
<span className="label ml-2">Twitter</span>
</a>
</div>
</div>
</StyledWrapper>
);
};
export default Support;

View File

@@ -1,7 +0,0 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
color: var(--color-text);
`;
export default StyledWrapper;

View File

@@ -1,64 +0,0 @@
import React from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import StyledWrapper from './StyledWrapper';
import { useTheme } from 'providers/Theme';
const Theme = () => {
const { storedTheme, setStoredTheme } = useTheme();
const formik = useFormik({
enableReinitialize: true,
initialValues: {
theme: storedTheme
},
validationSchema: Yup.object({
theme: Yup.string().oneOf(['light', 'dark']).required('theme is required')
}),
onSubmit: (values) => {
setStoredTheme(values.theme);
}
});
return (
<StyledWrapper>
<div className="bruno-form">
<div className="flex items-center mt-2">
<input
id="light-theme"
className="cursor-pointer"
type="radio"
name="theme"
onChange={(e) => {
formik.handleChange(e);
formik.handleSubmit();
}}
value="light"
checked={formik.values.theme === 'light'}
/>
<label htmlFor="light-theme" className="ml-1 cursor-pointer select-none">
Light
</label>
<input
id="dark-theme"
className="ml-4 cursor-pointer"
type="radio"
name="theme"
onChange={(e) => {
formik.handleChange(e);
formik.handleSubmit();
}}
value="dark"
checked={formik.values.theme === 'dark'}
/>
<label htmlFor="dark-theme" className="ml-1 cursor-pointer select-none">
Dark
</label>
</div>
</div>
</StyledWrapper>
);
};
export default Theme;

View File

@@ -1,54 +0,0 @@
import Modal from 'components/Modal/index';
import classnames from 'classnames';
import React, { useState } from 'react';
import Support from './Support';
import General from './General';
import Theme from './Theme';
import StyledWrapper from './StyledWrapper';
const Preferences = ({ onClose }) => {
const [tab, setTab] = useState('general');
const getTabClassname = (tabName) => {
return classnames(`tab select-none ${tabName}`, {
active: tabName === tab
});
};
const getTabPanel = (tab) => {
switch (tab) {
case 'general': {
return <General />;
}
case 'theme': {
return <Theme />;
}
case 'support': {
return <Support />;
}
}
};
return (
<StyledWrapper>
<Modal size="lg" title="Preferences" handleCancel={onClose} hideFooter={true}>
<div className="flex items-center px-2 tabs" role="tablist">
<div className={getTabClassname('general')} role="tab" onClick={() => setTab('general')}>
General
</div>
<div className={getTabClassname('theme')} role="tab" onClick={() => setTab('theme')}>
Theme
</div>
<div className={getTabClassname('support')} role="tab" onClick={() => setTab('support')}>
Support
</div>
</div>
<section className="flex flex-grow px-2 mt-4 tab-panel">{getTabPanel(tab)}</section>
</Modal>
</StyledWrapper>
);
};
export default Preferences;

View File

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

View File

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

View File

@@ -27,10 +27,6 @@ const Wrapper = styled.div`
&:nth-child(4) {
width: 70px;
}
select {
background-color: transparent;
}
}
}

View File

@@ -1,28 +0,0 @@
import styled from 'styled-components';
const Wrapper = styled.div`
font-size: 0.8125rem;
.auth-mode-selector {
background: transparent;
.auth-mode-label {
color: ${(props) => props.theme.colors.text.yellow};
}
.dropdown-item {
padding: 0.2rem 0.6rem !important;
}
.label-item {
padding: 0.2rem 0.6rem !important;
}
}
.caret {
color: rgb(140, 140, 140);
fill: rgb(140 140 140);
}
`;
export default Wrapper;

View File

@@ -1,70 +0,0 @@
import React, { useRef, forwardRef } from 'react';
import get from 'lodash/get';
import { IconCaretDown } from '@tabler/icons';
import Dropdown from 'components/Dropdown';
import { useDispatch } from 'react-redux';
import { updateRequestAuthMode } from 'providers/ReduxStore/slices/collections';
import { humanizeRequestAuthMode } from 'utils/collections';
import StyledWrapper from './StyledWrapper';
const AuthMode = ({ item, collection }) => {
const dispatch = useDispatch();
const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const authMode = item.draft ? get(item, 'draft.request.auth.mode') : get(item, 'request.auth.mode');
const Icon = forwardRef((props, ref) => {
return (
<div ref={ref} className="flex items-center justify-center auth-mode-label select-none">
{humanizeRequestAuthMode(authMode)} <IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
</div>
);
});
const onModeChange = (value) => {
dispatch(
updateRequestAuthMode({
itemUid: item.uid,
collectionUid: collection.uid,
mode: value
})
);
};
return (
<StyledWrapper>
<div className="inline-flex items-center cursor-pointer auth-mode-selector">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('basic');
}}
>
Basic Auth
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('bearer');
}}
>
Bearer Token
</div>
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onModeChange('none');
}}
>
No Auth
</div>
</Dropdown>
</div>
</StyledWrapper>
);
};
export default AuthMode;

View File

@@ -1,16 +0,0 @@
import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: 0.8125rem;
}
.single-line-editor-wrapper {
padding: 0.15rem 0.4rem;
border-radius: 3px;
border: solid 1px ${(props) => props.theme.input.border};
background-color: ${(props) => props.theme.input.bg};
}
`;
export default Wrapper;

View File

@@ -1,76 +0,0 @@
import React from 'react';
import get from 'lodash/get';
import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux';
import SingleLineEditor from 'components/SingleLineEditor';
import { updateAuth } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
const BasicAuth = ({ item, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const basicAuth = item.draft ? get(item, 'draft.request.auth.basic', {}) : get(item, 'request.auth.basic', {});
const handleRun = () => dispatch(sendRequest(item, collection.uid));
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
const handleUsernameChange = (username) => {
dispatch(
updateAuth({
mode: 'basic',
collectionUid: collection.uid,
itemUid: item.uid,
content: {
username: username,
password: basicAuth.password
}
})
);
};
const handlePasswordChange = (password) => {
dispatch(
updateAuth({
mode: 'basic',
collectionUid: collection.uid,
itemUid: item.uid,
content: {
username: basicAuth.username,
password: password
}
})
);
};
return (
<StyledWrapper className="mt-2 w-full">
<label className="block font-medium mb-2">Username</label>
<div className="single-line-editor-wrapper mb-2">
<SingleLineEditor
value={basicAuth.username || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleUsernameChange(val)}
onRun={handleRun}
collection={collection}
/>
</div>
<label className="block font-medium mb-2">Password</label>
<div className="single-line-editor-wrapper">
<SingleLineEditor
value={basicAuth.password || ''}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handlePasswordChange(val)}
onRun={handleRun}
collection={collection}
/>
</div>
</StyledWrapper>
);
};
export default BasicAuth;

View File

@@ -1,16 +0,0 @@
import styled from 'styled-components';
const Wrapper = styled.div`
label {
font-size: 0.8125rem;
}
.single-line-editor-wrapper {
padding: 0.15rem 0.4rem;
border-radius: 3px;
border: solid 1px ${(props) => props.theme.input.border};
background-color: ${(props) => props.theme.input.bg};
}
`;
export default Wrapper;

View File

@@ -1,51 +0,0 @@
import React from 'react';
import get from 'lodash/get';
import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux';
import SingleLineEditor from 'components/SingleLineEditor';
import { updateAuth } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
const BearerAuth = ({ item, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const bearerToken = item.draft
? get(item, 'draft.request.auth.bearer.token')
: get(item, 'request.auth.bearer.token');
const handleRun = () => dispatch(sendRequest(item, collection.uid));
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
const handleTokenChange = (token) => {
dispatch(
updateAuth({
mode: 'bearer',
collectionUid: collection.uid,
itemUid: item.uid,
content: {
token: token
}
})
);
};
return (
<StyledWrapper className="mt-2 w-full">
<label className="block font-medium mb-2">Token</label>
<div className="single-line-editor-wrapper">
<SingleLineEditor
value={bearerToken}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleTokenChange(val)}
onRun={handleRun}
collection={collection}
/>
</div>
</StyledWrapper>
);
};
export default BearerAuth;

View File

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

View File

@@ -1,31 +0,0 @@
import React from 'react';
import get from 'lodash/get';
import AuthMode from './AuthMode';
import BearerAuth from './BearerAuth';
import BasicAuth from './BasicAuth';
import StyledWrapper from './StyledWrapper';
const Auth = ({ item, collection }) => {
const authMode = item.draft ? get(item, 'draft.request.auth.mode') : get(item, 'request.auth.mode');
const getAuthView = () => {
switch (authMode) {
case 'basic': {
return <BasicAuth collection={collection} item={item} />;
}
case 'bearer': {
return <BearerAuth collection={collection} item={item} />;
}
}
};
return (
<StyledWrapper className="w-full mt-1">
<div className="flex flex-grow justify-start items-center">
<AuthMode item={item} collection={collection} />
</div>
{getAuthView()}
</StyledWrapper>
);
};
export default Auth;

View File

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

View File

@@ -6,7 +6,6 @@ import { IconRefresh, IconLoader2, IconBook, IconDownload } from '@tabler/icons'
import { useSelector, useDispatch } from 'react-redux';
import { updateRequestPaneTab } from 'providers/ReduxStore/slices/tabs';
import QueryEditor from 'components/RequestPane/QueryEditor';
import Auth from 'components/RequestPane/Auth';
import GraphQLVariables from 'components/RequestPane/GraphQLVariables';
import RequestHeaders from 'components/RequestPane/RequestHeaders';
import Vars from 'components/RequestPane/Vars';
@@ -25,31 +24,29 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
const tabs = useSelector((state) => state.tabs.tabs);
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
const query = item.draft ? get(item, 'draft.request.body.graphql.query') : get(item, 'request.body.graphql.query');
const variables = item.draft
? get(item, 'draft.request.body.graphql.variables')
: get(item, 'request.body.graphql.variables');
const variables = item.draft ? get(item, 'draft.request.body.graphql.variables') : get(item, 'request.body.graphql.variables');
const url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
const { storedTheme } = useTheme();
const {
storedTheme
} = useTheme();
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
const request = item.draft ? item.draft.request : item.request;
let {
schema,
loadSchema,
isLoading: isSchemaLoading,
error: schemaError
} = useGraphqlSchema(url, environment, request, collection);
} = useGraphqlSchema(url, environment);
const loadGqlSchema = () => {
if (!isSchemaLoading) {
if(!isSchemaLoading) {
loadSchema();
}
};
useEffect(() => {
if (onSchemaLoad) {
if(onSchemaLoad) {
onSchemaLoad(schema);
}
}, [schema]);
@@ -78,19 +75,17 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
const getTabPanel = (tab) => {
switch (tab) {
case 'query': {
return (
<QueryEditor
collection={collection}
theme={storedTheme}
schema={schema}
width={leftPaneWidth}
onSave={onSave}
value={query}
onRun={onRun}
onEdit={onQueryChange}
onClickReference={handleGqlClickReference}
/>
);
return <QueryEditor
collection={collection}
theme={storedTheme}
schema={schema}
width={leftPaneWidth}
onSave={onSave}
value={query}
onRun={onRun}
onEdit={onQueryChange}
onClickReference={handleGqlClickReference}
/>;
}
case 'variables': {
return <GraphQLVariables item={item} variables={variables} collection={collection} />;
@@ -98,9 +93,6 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
case 'headers': {
return <RequestHeaders item={item} collection={collection} />;
}
case 'auth': {
return <Auth item={item} collection={collection} />;
}
case 'vars': {
return <Vars item={item} collection={collection} />;
}
@@ -125,7 +117,7 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
if (!focusedTab || !focusedTab.uid || !focusedTab.requestPaneTab) {
return <div className="pb-4 px-4">An error occurred!</div>;
return <div className="pb-4 px-4">An error occured!</div>;
}
const getTabClassname = (tabName) => {
@@ -136,7 +128,7 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
return (
<StyledWrapper className="flex flex-col h-full relative">
<div className="flex flex-wrap items-center tabs" role="tablist">
<div className="flex items-center tabs" role="tablist">
<div className={getTabClassname('query')} role="tab" onClick={() => selectTab('query')}>
Query
</div>
@@ -146,9 +138,6 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
Headers
</div>
<div className={getTabClassname('auth')} role="tab" onClick={() => selectTab('auth')}>
Auth
</div>
<div className={getTabClassname('vars')} role="tab" onClick={() => selectTab('vars')}>
Vars
</div>
@@ -161,16 +150,20 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
<div className={getTabClassname('tests')} role="tab" onClick={() => selectTab('tests')}>
Tests
</div>
<div className="flex flex-grow justify-end items-center" style={{ fontSize: 13 }}>
<div className="flex items-center cursor-pointer hover:underline" onClick={loadGqlSchema}>
{isSchemaLoading ? <IconLoader2 className="animate-spin" size={18} strokeWidth={1.5} /> : null}
{!isSchemaLoading && !schema ? <IconDownload size={18} strokeWidth={1.5} /> : null}
{!isSchemaLoading && schema ? <IconRefresh size={18} strokeWidth={1.5} /> : null}
<span className="ml-1">Schema</span>
<div className="flex flex-grow justify-end items-center" style={{fontSize: 13}}>
<div className='flex items-center cursor-pointer hover:underline' onClick={loadGqlSchema}>
{isSchemaLoading ? (
<IconLoader2 className="animate-spin" size={18} strokeWidth={1.5}/>
) : null}
{!isSchemaLoading && !schema ? <IconDownload size={18} strokeWidth={1.5}/> : null }
{!isSchemaLoading && schema ? <IconRefresh size={18} strokeWidth={1.5}/> : null }
<span className='ml-1'>Schema</span>
</div>
<div className="flex items-center cursor-pointer hover:underline ml-2" onClick={toggleDocs}>
<IconBook size={18} strokeWidth={1.5} />
<span className="ml-1">Docs</span>
<div
className='flex items-center cursor-pointer hover:underline ml-2'
onClick={toggleDocs}
>
<IconBook size={18} strokeWidth={1.5} /><span className='ml-1'>Docs</span>
</div>
</div>
</div>

View File

@@ -6,14 +6,14 @@ import { simpleHash } from 'utils/common';
const schemaHashPrefix = 'bruno.graphqlSchema';
const useGraphqlSchema = (endpoint, environment, request, collection) => {
const useGraphqlSchema = (endpoint, environment) => {
const localStorageKey = `${schemaHashPrefix}.${simpleHash(endpoint)}`;
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [schema, setSchema] = useState(() => {
try {
const saved = localStorage.getItem(localStorageKey);
if (!saved) {
if(!saved) {
return null;
}
return buildClientSchema(JSON.parse(saved));
@@ -25,7 +25,7 @@ const useGraphqlSchema = (endpoint, environment, request, collection) => {
const loadSchema = () => {
setIsLoading(true);
fetchGqlSchema(endpoint, environment, request, collection)
fetchGqlSchema(endpoint, environment)
.then((res) => res.data)
.then((s) => {
if (s && s.data) {
@@ -40,7 +40,7 @@ const useGraphqlSchema = (endpoint, environment, request, collection) => {
.catch((err) => {
setIsLoading(false);
setError(err);
toast.error('Error occurred while loading GraphQL Schema');
toast.error('Error occured while loading GraphQL Schema');
});
};

View File

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

View File

@@ -7,8 +7,6 @@ import QueryParams from 'components/RequestPane/QueryParams';
import RequestHeaders from 'components/RequestPane/RequestHeaders';
import RequestBody from 'components/RequestPane/RequestBody';
import RequestBodyMode from 'components/RequestPane/RequestBody/RequestBodyMode';
import Auth from 'components/RequestPane/Auth';
import AuthMode from 'components/RequestPane/Auth/AuthMode';
import Vars from 'components/RequestPane/Vars';
import Assertions from 'components/RequestPane/Assertions';
import Script from 'components/RequestPane/Script';
@@ -40,9 +38,6 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
case 'headers': {
return <RequestHeaders item={item} collection={collection} />;
}
case 'auth': {
return <Auth item={item} collection={collection} />;
}
case 'vars': {
return <Vars item={item} collection={collection} />;
}
@@ -67,7 +62,7 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
if (!focusedTab || !focusedTab.uid || !focusedTab.requestPaneTab) {
return <div className="pb-4 px-4">An error occurred!</div>;
return <div className="pb-4 px-4">An error occured!</div>;
}
const getTabClassname = (tabName) => {
@@ -78,7 +73,7 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
return (
<StyledWrapper className="flex flex-col h-full relative">
<div className="flex flex-wrap items-center tabs" role="tablist">
<div className="flex items-center tabs" role="tablist">
<div className={getTabClassname('params')} role="tab" onClick={() => selectTab('params')}>
Query
</div>
@@ -88,9 +83,6 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
Headers
</div>
<div className={getTabClassname('auth')} role="tab" onClick={() => selectTab('auth')}>
Auth
</div>
<div className={getTabClassname('vars')} role="tab" onClick={() => selectTab('vars')}>
Vars
</div>
@@ -103,17 +95,15 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
<div className={getTabClassname('tests')} role="tab" onClick={() => selectTab('tests')}>
Tests
</div>
{/* Moved to post mvp */}
{/* <div className={getTabClassname('auth')} role="tab" onClick={() => selectTab('auth')}>Auth</div> */}
{focusedTab.requestPaneTab === 'body' ? (
<div className="flex flex-grow justify-end items-center">
<RequestBodyMode item={item} collection={collection} />
</div>
) : null}
</div>
<section
className={`flex w-full ${['script', 'vars', 'auth'].includes(focusedTab.requestPaneTab) ? '' : 'mt-5'}`}
>
{getTabPanel(focusedTab.requestPaneTab)}
</section>
<section className={`flex w-full ${['script', 'vars'].includes(focusedTab.requestPaneTab) ? '' : 'mt-5'}`}>{getTabPanel(focusedTab.requestPaneTab)}</section>
</StyledWrapper>
);
};

View File

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

View File

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

View File

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

View File

@@ -43,7 +43,7 @@ export default class QueryEditor extends React.Component {
mode: 'graphql',
// mode: 'brunovariables',
brunoVarInfo: {
variables: getAllVariables(this.props.collection)
variables: getAllVariables(this.props.collection),
},
theme: this.props.editorTheme || 'graphiql',
theme: this.props.theme === 'dark' ? 'monokai' : 'default',
@@ -51,7 +51,7 @@ export default class QueryEditor extends React.Component {
autoCloseBrackets: true,
matchBrackets: true,
showCursorWhenSelecting: true,
scrollbarStyle: 'overlay',
scrollbarStyle: "overlay",
readOnly: this.props.readOnly ? 'nocursor' : false,
foldGutter: {
minFoldSize: 4
@@ -142,7 +142,7 @@ export default class QueryEditor extends React.Component {
}
componentDidUpdate(prevProps) {
// Ensure the changes caused by this update are not interpreted as
// Ensure the changes caused by this update are not interpretted as
// user-input changes which could otherwise result in an infinite
// event loop.
this.ignoreChangeEvent = true;
@@ -179,13 +179,14 @@ export default class QueryEditor extends React.Component {
}
// Todo: Overlay is messing up with schema hint
// Fix this
// Fix this
addOverlay = () => {
// let variables = getAllVariables(this.props.collection);
// this.variables = variables;
// defineCodeMirrorBrunoVariablesMode(variables, 'graphql');
// this.editor.setOption('mode', 'brunovariables');
};
}
render() {
return (

View File

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

View File

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

View File

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

View File

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

View File

@@ -32,50 +32,6 @@ const Wrapper = styled.div`
position: relative;
top: 1px;
}
.tooltip {
position: relative;
display: inline-block;
cursor: pointer;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
.tooltiptext {
visibility: hidden;
width: auto;
background-color: ${(props) => props.theme.requestTabs.active.bg};
color: ${(props) => props.theme.text};
text-align: center;
border-radius: 4px;
padding: 4px 8px;
position: absolute;
z-index: 1;
bottom: 34px;
left: 50%;
transform: translateX(-50%);
opacity: 0;
transition: opacity 0.3s;
white-space: nowrap;
}
.tooltiptext::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
margin-left: -4px;
border-width: 4px;
border-style: solid;
border-color: ${(props) => props.theme.requestTabs.active.bg} transparent transparent transparent;
}
.shortcut {
font-size: 0.625rem;
}
`;
export default Wrapper;

View File

@@ -1,13 +1,12 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect} from 'react';
import get from 'lodash/get';
import { useDispatch } from 'react-redux';
import { requestUrlChanged, updateRequestMethod } from 'providers/ReduxStore/slices/collections';
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import HttpMethodSelector from './HttpMethodSelector';
import { useTheme } from 'providers/Theme';
import { IconDeviceFloppy, IconArrowRight } from '@tabler/icons';
import SendIcon from 'components/Icons/Send';
import SingleLineEditor from 'components/SingleLineEditor';
import { isMacOS } from 'utils/common/platform';
import StyledWrapper from './StyledWrapper';
const QueryUrl = ({ item, collection, handleRun }) => {
@@ -15,20 +14,15 @@ const QueryUrl = ({ item, collection, handleRun }) => {
const dispatch = useDispatch();
const method = item.draft ? get(item, 'draft.request.method') : get(item, 'request.method');
const url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
const isMac = isMacOS();
const saveShortcut = isMac ? 'Cmd + S' : 'Ctrl + S';
const [methodSelectorWidth, setMethodSelectorWidth] = useState(90);
useEffect(() => {
const el = document.querySelector('.method-selector-container');
const el = document.querySelector(".method-selector-container");
setMethodSelectorWidth(el.offsetWidth);
}, [method]);
const onSave = () => {
dispatch(saveRequest(item.uid, collection.uid));
};
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
const onUrlChange = (value) => {
dispatch(
requestUrlChanged({
@@ -62,8 +56,8 @@ const QueryUrl = ({ item, collection, handleRun }) => {
maxWidth: `calc(100% - ${methodSelectorWidth}px)`
}}
>
<SingleLineEditor
value={url}
<SingleLineEditor
value={url}
onSave={onSave}
theme={storedTheme}
onChange={(newValue) => onUrlChange(newValue)}
@@ -71,25 +65,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
collection={collection}
/>
<div className="flex items-center h-full mr-2 cursor-pointer" id="send-request" onClick={handleRun}>
<div
className="tooltip mr-3"
onClick={(e) => {
e.stopPropagation();
if (!item.draft) return;
onSave();
}}
>
<IconDeviceFloppy
color={item.draft ? theme.colors.text.yellow : theme.requestTabs.icon.color}
strokeWidth={1.5}
size={22}
className={`${item.draft ? 'cursor-pointer' : 'cursor-default'}`}
/>
<span className="tooltiptext text-xs">
Save <span className="shortcut">({saveShortcut})</span>
</span>
</div>
<IconArrowRight color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={22} />
<SendIcon color={theme.requestTabPanel.url.icon} width={22}/>
</div>
</div>
</StyledWrapper>

View File

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

View File

@@ -8,8 +8,6 @@ import { addRequestHeader, updateRequestHeader, deleteRequestHeader } from 'prov
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import SingleLineEditor from 'components/SingleLineEditor';
import StyledWrapper from './StyledWrapper';
import { headers as StandardHTTPHeaders } from 'know-your-http-well';
const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header);
const RequestHeaders = ({ item, collection }) => {
const dispatch = useDispatch();
@@ -74,28 +72,19 @@ const RequestHeaders = ({ item, collection }) => {
</thead>
<tbody>
{headers && headers.length
? headers.map((header) => {
? headers.map((header, index) => {
return (
<tr key={header.uid}>
<td>
<SingleLineEditor
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={header.name}
theme={storedTheme}
onSave={onSave}
onChange={(newValue) =>
handleHeaderValueChange(
{
target: {
value: newValue
}
},
header,
'name'
)
}
autocomplete={headerAutoCompleteList}
onRun={handleRun}
collection={collection}
className="mousetrap"
onChange={(e) => handleHeaderValueChange(e, header, 'name')}
/>
</td>
<td>
@@ -103,31 +92,19 @@ const RequestHeaders = ({ item, collection }) => {
value={header.value}
theme={storedTheme}
onSave={onSave}
onChange={(newValue) =>
handleHeaderValueChange(
{
target: {
value: newValue
}
},
header,
'value'
)
}
onChange={(newValue) => handleHeaderValueChange({
target: {
value: newValue
}
}, header, 'value')}
onRun={handleRun}
collection={collection}
/>
</td>
<td>
<div className="flex items-center">
<input
type="checkbox"
checked={header.enabled}
tabIndex="-1"
className="mr-3 mousetrap"
onChange={(e) => handleHeaderValueChange(e, header, 'enabled')}
/>
<button tabIndex="-1" onClick={() => handleRemoveHeader(header)}>
<input type="checkbox" checked={header.enabled} className="mr-3 mousetrap" onChange={(e) => handleHeaderValueChange(e, header, 'enabled')} />
<button onClick={() => handleRemoveHeader(header)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>

View File

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

View File

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

View File

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

View File

@@ -68,18 +68,18 @@ const VarsTable = ({ item, collection, vars, varType }) => {
<thead>
<tr>
<td>Name</td>
{varType === 'request' ? (
{ varType === 'request' ? (
<td>
<div className="flex items-center">
<div className='flex items-center'>
<span>Value</span>
<Tooltip text="You can write any valid JS Template Literal here" tooltipId="request-var" />
<Tooltip text="You can write any valid JS Template Literal here" tooltipId="request-var"/>
</div>
</td>
) : (
) : (
<td>
<div className="flex items-center">
<div className='flex items-center'>
<span>Expr</span>
<Tooltip text="You can write any valid JS expression here" tooltipId="response-var" />
<Tooltip text="You can write any valid JS expression here" tooltipId="response-var"/>
</div>
</td>
)}
@@ -108,17 +108,11 @@ const VarsTable = ({ item, collection, vars, varType }) => {
value={_var.value}
theme={storedTheme}
onSave={onSave}
onChange={(newValue) =>
handleVarChange(
{
target: {
value: newValue
}
},
_var,
'value'
)
}
onChange={(newValue) => handleVarChange({
target: {
value: newValue
}
}, _var, 'value')}
onRun={handleRun}
collection={collection}
/>
@@ -128,11 +122,10 @@ const VarsTable = ({ item, collection, vars, varType }) => {
<input
type="checkbox"
checked={_var.enabled}
tabIndex="-1"
className="mr-3 mousetrap"
onChange={(e) => handleVarChange(e, _var, 'enabled')}
/>
<button tabIndex="-1" onClick={() => handleRemoveVar(_var)}>
<button onClick={() => handleRemoveVar(_var)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>

View File

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

View File

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

View File

@@ -13,8 +13,6 @@ import RequestNotFound from './RequestNotFound';
import QueryUrl from 'components/RequestPane/QueryUrl';
import NetworkError from 'components/ResponsePane/NetworkError';
import RunnerResults from 'components/RunnerResults';
import VariablesEditor from 'components/VariablesEditor';
import CollectionSettings from 'components/CollectionSettings';
import { DocExplorer } from '@usebruno/graphql-docs';
import StyledWrapper from './StyledWrapper';
@@ -35,9 +33,7 @@ const RequestTabPanel = () => {
let asideWidth = useSelector((state) => state.app.leftSidebarWidth);
const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
const [leftPaneWidth, setLeftPaneWidth] = useState(
focusedTab && focusedTab.requestPaneWidth ? focusedTab.requestPaneWidth : (screenWidth - asideWidth) / 2.2
); // 2.2 so that request pane is relatively smaller
const [leftPaneWidth, setLeftPaneWidth] = useState(focusedTab && focusedTab.requestPaneWidth ? focusedTab.requestPaneWidth : (screenWidth - asideWidth) / 2.2); // 2.2 so that request pane is relatively smaller
const [rightPaneWidth, setRightPaneWidth] = useState(screenWidth - asideWidth - leftPaneWidth - DEFAULT_PADDING);
const [dragging, setDragging] = useState(false);
@@ -49,10 +45,10 @@ const RequestTabPanel = () => {
const onSchemaLoad = (schema) => setSchema(schema);
const toggleDocs = () => setShowGqlDocs((showGqlDocs) => !showGqlDocs);
const handleGqlClickReference = (reference) => {
if (docExplorerRef.current) {
if(docExplorerRef.current) {
docExplorerRef.current.showDocForReference(reference);
}
if (!showGqlDocs) {
if(!showGqlDocs) {
setShowGqlDocs(true);
}
};
@@ -70,13 +66,10 @@ const RequestTabPanel = () => {
if (dragging) {
e.preventDefault();
let leftPaneXPosition = e.clientX + 2;
if (
leftPaneXPosition < asideWidth + DEFAULT_PADDING + MIN_LEFT_PANE_WIDTH ||
leftPaneXPosition > screenWidth - MIN_RIGHT_PANE_WIDTH
) {
if (leftPaneXPosition < (asideWidth+ DEFAULT_PADDING + MIN_LEFT_PANE_WIDTH) || leftPaneXPosition > (screenWidth - MIN_RIGHT_PANE_WIDTH )) {
return;
}
setLeftPaneWidth(leftPaneXPosition - asideWidth);
setLeftPaneWidth(leftPaneXPosition- asideWidth);
setRightPaneWidth(screenWidth - e.clientX - DEFAULT_PADDING);
}
};
@@ -112,7 +105,7 @@ const RequestTabPanel = () => {
}
if (!focusedTab || !focusedTab.uid || !focusedTab.collectionUid) {
return <div className="pb-4 px-4">An error occurred!</div>;
return <div className="pb-4 px-4">An error occured!</div>;
}
let collection = find(collections, (c) => c.uid === focusedTab.collectionUid);
@@ -120,16 +113,9 @@ const RequestTabPanel = () => {
return <div className="pb-4 px-4">Collection not found!</div>;
}
if (focusedTab.type === 'collection-runner') {
return <RunnerResults collection={collection} />;
}
if (focusedTab.type === 'variables') {
return <VariablesEditor collection={collection} />;
}
if (focusedTab.type === 'collection-settings') {
return <CollectionSettings collection={collection} />;
const showRunner = collection.showRunner;
if(showRunner) {
return <RunnerResults collection={collection}/>;
}
const item = findItemInCollection(collection, activeTabUid);
@@ -152,13 +138,7 @@ const RequestTabPanel = () => {
</div>
<section className="main flex flex-grow pb-4 relative">
<section className="request-pane">
<div
className="px-4"
style={{
width: `${Math.max(leftPaneWidth, MIN_LEFT_PANE_WIDTH)}px`,
height: `calc(100% - ${DEFAULT_PADDING}px)`
}}
>
<div className="px-4" style={{ width: `${Math.max(leftPaneWidth, MIN_LEFT_PANE_WIDTH)}px`, height: `calc(100% - ${DEFAULT_PADDING}px)` }}>
{item.type === 'graphql-request' ? (
<GraphQLRequestPane
item={item}
@@ -170,9 +150,7 @@ const RequestTabPanel = () => {
/>
) : null}
{item.type === 'http-request' ? (
<HttpRequestPane item={item} collection={collection} leftPaneWidth={leftPaneWidth} />
) : null}
{item.type === 'http-request' ? <HttpRequestPane item={item} collection={collection} leftPaneWidth={leftPaneWidth} /> : null}
</div>
</section>
@@ -187,15 +165,19 @@ const RequestTabPanel = () => {
{item.type === 'graphql-request' ? (
<div className={`graphql-docs-explorer-container ${showGqlDocs ? '' : 'hidden'}`}>
<DocExplorer schema={schema} ref={(r) => (docExplorerRef.current = r)}>
<button className="mr-2" onClick={toggleDocs} aria-label="Close Documentation Explorer">
<DocExplorer schema={schema} ref={(r) => docExplorerRef.current = r}>
<button
className='mr-2'
onClick={toggleDocs}
aria-label="Close Documentation Explorer"
>
{'\u2715'}
</button>
</DocExplorer>
</div>
) : null}
): null}
</StyledWrapper>
);
};
export default RequestTabPanel;
export default RequestTabPanel;

View File

@@ -1,42 +1,18 @@
import React from 'react';
import { uuid } from 'utils/common';
import { IconFiles, IconRun, IconEye, IconSettings } from '@tabler/icons';
import { IconFiles, IconRun } from '@tabler/icons';
import EnvironmentSelector from 'components/Environments/EnvironmentSelector';
import { addTab } from 'providers/ReduxStore/slices/tabs';
import VariablesView from 'components/VariablesView';
import { useDispatch } from 'react-redux';
import { toggleRunnerView } from 'providers/ReduxStore/slices/collections';
import StyledWrapper from './StyledWrapper';
const CollectionToolBar = ({ collection }) => {
const dispatch = useDispatch();
const handleRun = () => {
dispatch(
addTab({
uid: uuid(),
collectionUid: collection.uid,
type: 'collection-runner'
})
);
};
const viewVariables = () => {
dispatch(
addTab({
uid: uuid(),
collectionUid: collection.uid,
type: 'variables'
})
);
};
const viewCollectionSettings = () => {
dispatch(
addTab({
uid: uuid(),
collectionUid: collection.uid,
type: 'collection-settings'
})
);
dispatch(toggleRunnerView({
collectionUid: collection.uid
}));
};
return (
@@ -50,12 +26,7 @@ const CollectionToolBar = ({ collection }) => {
<span className="mr-2">
<IconRun className="cursor-pointer" size={20} strokeWidth={1.5} onClick={handleRun} />
</span>
<span className="mr-3">
<IconEye className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewVariables} />
</span>
<span className="mr-3">
<IconSettings className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewCollectionSettings} />
</span>
<VariablesView collection={collection}/>
<EnvironmentSelector collection={collection} />
</div>
</div>

View File

@@ -1,30 +0,0 @@
import React from 'react';
import Modal from 'components/Modal';
const ConfirmRequestClose = ({ onCancel, onCloseWithoutSave, onSaveAndClose }) => {
const _handleCancel = ({ type }) => {
if (type === 'button') {
return onCloseWithoutSave();
}
return onCancel();
};
return (
<Modal
size="sm"
title="Unsaved changes"
confirmText="Save and Close"
cancelText="Close without saving"
handleConfirm={onSaveAndClose}
handleCancel={_handleCancel}
disableEscapeKey={true}
disableCloseOnOutsideClick={true}
closeModalFadeTimeout={150}
>
<div className="font-normal">You have unsaved changes in you request.</div>
</Modal>
);
};
export default ConfirmRequestClose;

View File

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

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