mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-04 01:48:33 +00:00
Compare commits
1 Commits
v0.23.0
...
bugfix/col
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41d9bbd2d8 |
12
.github/workflows/bump-homebrew-cask.yml
vendored
12
.github/workflows/bump-homebrew-cask.yml
vendored
@@ -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}"
|
||||
27
.github/workflows/playwright.yml
vendored
27
.github/workflows/playwright.yml
vendored
@@ -1,27 +0,0 @@
|
||||
name: Playwright Tests
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
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: Install dependencies
|
||||
run: npm i --legacy-peer-deps
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps
|
||||
- name: Run Playwright tests
|
||||
run: npm run test:e2e
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
35
.github/workflows/unit-tests.yml
vendored
35
.github/workflows/unit-tests.yml
vendored
@@ -1,35 +0,0 @@
|
||||
name: Unit Tests
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
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
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -4,6 +4,7 @@
|
||||
node_modules
|
||||
yarn.lock
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
.pnp
|
||||
.pnp.js
|
||||
|
||||
@@ -16,7 +17,6 @@ chrome-extension
|
||||
chrome-extension.pem
|
||||
chrome-extension.crx
|
||||
bruno.zip
|
||||
*.zip
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
@@ -37,10 +37,3 @@ yarn-error.log*
|
||||
/renderer
|
||||
/renderer/.next/
|
||||
/renderer/out/
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
|
||||
#dev editor
|
||||
bruno.iml
|
||||
.idea
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx pretty-quick --staged
|
||||
@@ -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: 537 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 474 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 153 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 94 KiB |
@@ -1,37 +1,44 @@
|
||||
**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.
|
||||
I am happy that you are looking to improve bruno. Below are the guidelines to get started bringing up bruno on your computed.
|
||||
|
||||
### Technology Stack
|
||||
|
||||
Bruno is built using NextJs and React. We also use electron to ship a desktop version (that supports local collections)
|
||||
|
||||
Libraries we use
|
||||
Libraries we use
|
||||
* CSS - Tailwind
|
||||
* Code Editors - Codemirror
|
||||
* State Management - Redux
|
||||
* Icons - Tabler Icons
|
||||
* Forms - formik
|
||||
* Schema Validation - Yup
|
||||
* Request Client - axios
|
||||
* Filesystem Watcher - chokidar
|
||||
|
||||
- CSS - Tailwind
|
||||
- Code Editors - Codemirror
|
||||
- State Management - Redux
|
||||
- Icons - Tabler Icons
|
||||
- Forms - formik
|
||||
- Schema Validation - Yup
|
||||
- Request Client - axios
|
||||
- Filesystem Watcher - chokidar
|
||||
|
||||
### 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 and npm 8.x. We use npm workspaces in the project
|
||||
|
||||
### Lets start coding
|
||||
|
||||
Please reference [development.md](docs/development.md) for instructions on running the local development environment.
|
||||
```bash
|
||||
# clone and cd into bruno
|
||||
# use Node 14.x, Npm 8.x
|
||||
|
||||
# Install deps (note that we use npm workspaces)
|
||||
npm i
|
||||
|
||||
# run next app
|
||||
npm run dev:web
|
||||
|
||||
# run electron app
|
||||
# neededonly if you want to test changes related to electron app
|
||||
# please note that both web and electron use the same code
|
||||
# if it works in web, then it should also work in electron
|
||||
npm run dev:electron
|
||||
|
||||
# open in browser
|
||||
open http://localhost:3000
|
||||
```
|
||||
|
||||
### Raising Pull Request
|
||||
|
||||
- Please keep the PR's small and focused on one thing
|
||||
- Please follow the format of creating branches
|
||||
- feature/[feature name]: This branch should contain changes for a specific feature
|
||||
- Example: feature/dark-mode
|
||||
- bugfix/[bug name]: This branch should container only bug fixes for a specific bug
|
||||
- Example bugfix/bug-1
|
||||
* Please keep the PR's small and focused on one thing
|
||||
|
||||
@@ -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
|
||||
@@ -1,55 +1,27 @@
|
||||
**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.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- NodeJS v18
|
||||
|
||||
### Local Development
|
||||
## development
|
||||
|
||||
```bash
|
||||
# use nodejs 18 version
|
||||
nvm use
|
||||
|
||||
# install deps
|
||||
npm i --legacy-peer-deps
|
||||
npm i
|
||||
|
||||
# build graphql docs
|
||||
npm run build:graphql-docs
|
||||
# run next app
|
||||
npm run dev --workspace=packages/bruno-app
|
||||
|
||||
# build bruno query
|
||||
npm run build:bruno-query
|
||||
# run electron app
|
||||
npm run dev --workspace=packages/bruno-electron
|
||||
|
||||
# run next app (terminal 1)
|
||||
npm run dev:web
|
||||
|
||||
# run electron app (terminal 2)
|
||||
npm run dev:electron
|
||||
# build next app
|
||||
npm run build --workspace=packages/bruno-app
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
## fix
|
||||
|
||||
You might encounter a `Unsupported platform` error when you run `npm install`. To fix this, you will need to delete `node_modules` and `package-lock.json` and run `npm install`. This should install all the necessary packages needed to run the app.
|
||||
|
||||
```shell
|
||||
# Delete node_modules in sub-directories
|
||||
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
|
||||
rm -rf "$dir"
|
||||
done
|
||||
|
||||
# Delete package-lock in sub-directories
|
||||
find . -type f -name "package-lock.json" -delete
|
||||
```
|
||||
|
||||
### Testing
|
||||
# testing
|
||||
|
||||
```bash
|
||||
# bruno-schema
|
||||
npm test --workspace=packages/bruno-schema
|
||||
|
||||
# bruno-lang
|
||||
npm test --workspace=packages/bruno-lang
|
||||
```
|
||||
|
||||
@@ -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
|
||||
```
|
||||
55
main/app/menu-template.js
Normal file
55
main/app/menu-template.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const { ipcMain } = require('electron');
|
||||
|
||||
const template = [
|
||||
{
|
||||
label: 'Collection',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Open Collection',
|
||||
click () {
|
||||
ipcMain.emit('main:open-collection');
|
||||
}
|
||||
},
|
||||
{ role: 'quit' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{ role: 'undo'},
|
||||
{ role: 'redo'},
|
||||
{ role: 'separator'},
|
||||
{ role: 'cut'},
|
||||
{ role: 'copy'},
|
||||
{ role: 'paste'}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'View',
|
||||
submenu: [
|
||||
{ role: 'reload'},
|
||||
{ role: 'toggledevtools'},
|
||||
{ role: 'separator'},
|
||||
{ role: 'resetzoom'},
|
||||
{ role: 'zoomin'},
|
||||
{ role: 'zoomout'},
|
||||
{ role: 'separator'},
|
||||
{ role: 'togglefullscreen'}
|
||||
]
|
||||
},
|
||||
{
|
||||
role: 'window',
|
||||
submenu: [
|
||||
{ role: 'minimize'},
|
||||
{ role: 'close'}
|
||||
]
|
||||
},
|
||||
{
|
||||
role: 'help',
|
||||
submenu: [
|
||||
{ label: 'Learn More'}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = template;
|
||||
54
main/index.js
Normal file
54
main/index.js
Normal file
@@ -0,0 +1,54 @@
|
||||
const path = require('path');
|
||||
const { format } = require('url');
|
||||
const { BrowserWindow, app, Menu } = require('electron');
|
||||
const { setContentSecurityPolicy } = require('electron-util');
|
||||
|
||||
const menuTemplate = require('./app/menu-template');
|
||||
const registerIpc = require('./ipc');
|
||||
const isDev = require('electron-is-dev');
|
||||
const prepareNext = require('electron-next');
|
||||
|
||||
setContentSecurityPolicy(`
|
||||
default-src * 'unsafe-inline' 'unsafe-eval';
|
||||
script-src * 'unsafe-inline' 'unsafe-eval';
|
||||
connect-src * 'unsafe-inline';
|
||||
base-uri 'none';
|
||||
form-action 'none';
|
||||
frame-ancestors 'none';
|
||||
`);
|
||||
|
||||
const menu = Menu.buildFromTemplate(menuTemplate);
|
||||
Menu.setApplicationMenu(menu);
|
||||
|
||||
let mainWindow;
|
||||
|
||||
// Prepare the renderer once the app is ready
|
||||
app.on('ready', async () => {
|
||||
await prepareNext('./renderer');
|
||||
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1280,
|
||||
height: 768,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: true,
|
||||
preload: path.join(__dirname, "preload.js")
|
||||
},
|
||||
});
|
||||
|
||||
const url = isDev
|
||||
? 'http://localhost:8000'
|
||||
: format({
|
||||
pathname: path.join(__dirname, '../renderer/out/index.html'),
|
||||
protocol: 'file:',
|
||||
slashes: true
|
||||
});
|
||||
|
||||
mainWindow.loadURL(url);
|
||||
|
||||
// register all ipc handlers
|
||||
registerIpc(mainWindow);
|
||||
});
|
||||
|
||||
// Quit the app once all windows are closed
|
||||
app.on('window-all-closed', app.quit);
|
||||
47
main/ipc.js
Normal file
47
main/ipc.js
Normal file
@@ -0,0 +1,47 @@
|
||||
const axios = require('axios');
|
||||
const FormData = require('form-data');
|
||||
const { ipcMain } = require('electron');
|
||||
const { forOwn, extend } = require('lodash');
|
||||
|
||||
|
||||
const registerIpc = () => {
|
||||
// handler for sending http request
|
||||
ipcMain.handle('send-http-request', async (event, request) => {
|
||||
try {
|
||||
// make axios work in node using form data
|
||||
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
|
||||
if(request.headers && request.headers['content-type'] === 'multipart/form-data') {
|
||||
const form = new FormData();
|
||||
forOwn(request.data, (value, key) => {
|
||||
form.append(key, value);
|
||||
});
|
||||
extend(request.headers, form.getHeaders());
|
||||
request.data = form;
|
||||
}
|
||||
|
||||
const result = await axios(request);
|
||||
|
||||
return {
|
||||
status: result.status,
|
||||
headers: result.headers,
|
||||
data: result.data
|
||||
};
|
||||
} catch (error) {
|
||||
if(error.response) {
|
||||
return {
|
||||
status: error.response.status,
|
||||
headers: error.response.headers,
|
||||
data: error.response.data
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: -1,
|
||||
headers: [],
|
||||
data: null
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = registerIpc;
|
||||
14
main/preload.js
Normal file
14
main/preload.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const { ipcRenderer, contextBridge } = require('electron');
|
||||
|
||||
contextBridge.exposeInMainWorld('ipcRenderer', {
|
||||
invoke: (channel, ...args) => ipcRenderer.invoke(channel, ...args),
|
||||
on: (channel, handler) => {
|
||||
// Deliberately strip event as it includes `sender`
|
||||
const subscription = (event, ...args) => handler(...args);
|
||||
ipcRenderer.on(channel, subscription);
|
||||
|
||||
return () => {
|
||||
ipcRenderer.removeListener(channel, subscription);
|
||||
};
|
||||
}
|
||||
});
|
||||
14
main/utils/common.js
Normal file
14
main/utils/common.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const { customAlphabet } = require('nanoid');
|
||||
|
||||
// a customized version of nanoid without using _ and -
|
||||
const uuid = () => {
|
||||
// https://github.com/ai/nanoid/blob/main/url-alphabet/index.js
|
||||
const urlAlphabet = 'useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict';
|
||||
const customNanoId = customAlphabet (urlAlphabet, 21);
|
||||
|
||||
return customNanoId();
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
uuid
|
||||
};
|
||||
29838
package-lock.json
generated
29838
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
28
package.json
28
package.json
@@ -4,39 +4,19 @@
|
||||
"workspaces": [
|
||||
"packages/bruno-app",
|
||||
"packages/bruno-electron",
|
||||
"packages/bruno-cli",
|
||||
"packages/bruno-tauri",
|
||||
"packages/bruno-schema",
|
||||
"packages/bruno-query",
|
||||
"packages/bruno-js",
|
||||
"packages/bruno-lang",
|
||||
"packages/bruno-testbench",
|
||||
"packages/bruno-graphql-docs"
|
||||
"packages/bruno-testbench"
|
||||
],
|
||||
"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"
|
||||
"randomstring": "^1.2.2"
|
||||
},
|
||||
"scripts": {
|
||||
"dev:web": "npm run dev --workspace=packages/bruno-app",
|
||||
"build:web": "npm run build --workspace=packages/bruno-app",
|
||||
"prettier:web": "npm run prettier --workspace=packages/bruno-app",
|
||||
"dev:electron": "npm run dev --workspace=packages/bruno-electron",
|
||||
"build:bruno-query": "npm run build --workspace=packages/bruno-query",
|
||||
"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"
|
||||
},
|
||||
"overrides": {
|
||||
"rollup": "3.2.5"
|
||||
"build:chrome-extension": "./scripts/build-chrome-extension.sh",
|
||||
"build:electron": "./scripts/build-electron.sh"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120
|
||||
"printWidth": 180
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
"paths": {
|
||||
"assets/*": ["src/assets/*"],
|
||||
"components/*": ["src/components/*"],
|
||||
"hooks/*": ["src/hooks/*"],
|
||||
"themes/*": ["src/themes/*"],
|
||||
"api/*": ["src/api/*"],
|
||||
"pageComponents/*": ["src/pageComponents/*"],
|
||||
"providers/*": ["src/providers/*"],
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
module.exports = {
|
||||
reactStrictMode: false,
|
||||
publicRuntimeConfig: {
|
||||
CI: process.env.CI,
|
||||
PLAYWRIGHT: process.env.PLAYWRIGHT,
|
||||
ENV: process.env.ENV
|
||||
},
|
||||
reactStrictMode: true,
|
||||
webpack: (config, { isServer }) => {
|
||||
// Fixes npm packages that depend on `fs` module
|
||||
if (!isServer) {
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
{
|
||||
"name": "@usebruno/app",
|
||||
"version": "0.3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "cross-env ENV=dev next dev",
|
||||
"dev": "next dev",
|
||||
"build": "next build && next export",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"test": "jest",
|
||||
"prettier": "prettier --write \"./src/**/*.{js,jsx,json,ts,tsx}\""
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -17,8 +15,7 @@
|
||||
"@reduxjs/toolkit": "^1.8.0",
|
||||
"@tabler/icons": "^1.46.0",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@usebruno/graphql-docs": "0.1.0",
|
||||
"@usebruno/schema": "0.5.0",
|
||||
"@usebruno/schema": "0.1.0",
|
||||
"axios": "^0.26.0",
|
||||
"classnames": "^2.3.1",
|
||||
"codemirror": "^5.65.2",
|
||||
@@ -28,35 +25,25 @@
|
||||
"file-saver": "^2.0.5",
|
||||
"formik": "^2.2.9",
|
||||
"graphiql": "^1.5.9",
|
||||
"graphql": "^16.6.0",
|
||||
"graphql": "^16.2.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",
|
||||
"nanoid": "3.3.4",
|
||||
"next": "12.3.3",
|
||||
"next": "12.3.1",
|
||||
"path": "^0.12.7",
|
||||
"platform": "^1.3.6",
|
||||
"posthog-node": "^2.1.0",
|
||||
"qs": "^6.11.0",
|
||||
"react": "18.2.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "18.2.0",
|
||||
"react-github-btn": "^1.4.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-hot-toast": "^2.4.0",
|
||||
"react-inspector": "^6.0.2",
|
||||
"react-redux": "^7.2.6",
|
||||
"react-tooltip": "^5.5.2",
|
||||
"react-tabs": "^3.2.3",
|
||||
"sass": "^1.46.0",
|
||||
"split-on-first": "^3.0.0",
|
||||
"styled-components": "^5.3.3",
|
||||
"tailwindcss": "^2.2.19",
|
||||
"xml-formatter": "^3.5.0",
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -66,7 +53,6 @@
|
||||
"@babel/preset-react": "^7.16.0",
|
||||
"@babel/runtime": "^7.16.3",
|
||||
"babel-loader": "^8.2.3",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.5.1",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-loader": "^3.0.1",
|
||||
|
||||
BIN
packages/bruno-app/public/images/landing.png
Normal file
BIN
packages/bruno-app/public/images/landing.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 436 KiB |
@@ -1,30 +0,0 @@
|
||||
const darkTheme = {
|
||||
brand: '#546de5',
|
||||
text: 'rgb(52 52 52)',
|
||||
'primary-text': '#ffffff',
|
||||
'primary-theme': '#1e1e1e',
|
||||
'secondary-text': '#929292',
|
||||
'sidebar-collection-item-active-indent-border': '#d0d0d0',
|
||||
'sidebar-collection-item-active-background': '#e1e1e1',
|
||||
'sidebar-background': '#252526',
|
||||
'sidebar-bottom-bg': '#68217a',
|
||||
'request-dragbar-background': '#efefef',
|
||||
'request-dragbar-background-active': 'rgb(200, 200, 200)',
|
||||
'tab-inactive': 'rgb(155 155 155)',
|
||||
'tab-active-border': '#546de5',
|
||||
'layout-border': '#dedede',
|
||||
'codemirror-border': '#efefef',
|
||||
'codemirror-background': 'rgb(243, 243, 243)',
|
||||
'text-link': '#1663bb',
|
||||
'text-danger': 'rgb(185, 28, 28)',
|
||||
'background-danger': '#dc3545',
|
||||
'method-get': 'rgb(5, 150, 105)',
|
||||
'method-post': '#8e44ad',
|
||||
'method-delete': 'rgb(185, 28, 28)',
|
||||
'method-patch': 'rgb(52 52 52)',
|
||||
'method-options': 'rgb(52 52 52)',
|
||||
'method-head': 'rgb(52 52 52)',
|
||||
'table-stripe': '#f3f3f3'
|
||||
};
|
||||
|
||||
export default darkTheme;
|
||||
@@ -1,7 +0,0 @@
|
||||
import darkTheme from './dark';
|
||||
import lightTheme from './light';
|
||||
|
||||
export default {
|
||||
Light: lightTheme,
|
||||
Dark: darkTheme
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
const lightTheme = {
|
||||
brand: '#546de5',
|
||||
text: 'rgb(52 52 52)',
|
||||
'primary-text': 'rgb(52 52 52)',
|
||||
'primary-theme': '#ffffff',
|
||||
'secondary-text': '#929292',
|
||||
'sidebar-collection-item-active-indent-border': '#d0d0d0',
|
||||
'sidebar-collection-item-active-background': '#e1e1e1',
|
||||
'sidebar-background': '#f3f3f3',
|
||||
'sidebar-bottom-bg': '#f3f3f3',
|
||||
'request-dragbar-background': '#efefef',
|
||||
'request-dragbar-background-active': 'rgb(200, 200, 200)',
|
||||
'tab-inactive': 'rgb(155 155 155)',
|
||||
'tab-active-border': '#546de5',
|
||||
'layout-border': '#dedede',
|
||||
'codemirror-border': '#efefef',
|
||||
'codemirror-background': 'rgb(243, 243, 243)',
|
||||
'text-link': '#1663bb',
|
||||
'text-danger': 'rgb(185, 28, 28)',
|
||||
'background-danger': '#dc3545',
|
||||
'method-get': 'rgb(5, 150, 105)',
|
||||
'method-post': '#8e44ad',
|
||||
'method-delete': 'rgb(185, 28, 28)',
|
||||
'method-patch': 'rgb(52 52 52)',
|
||||
'method-options': 'rgb(52 52 52)',
|
||||
'method-head': 'rgb(52 52 52)',
|
||||
'table-stripe': '#f3f3f3'
|
||||
};
|
||||
|
||||
export default lightTheme;
|
||||
23
packages/bruno-app/src/api/auth.js
Normal file
23
packages/bruno-app/src/api/auth.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { get, post, put } from './base';
|
||||
|
||||
// not used. kept as a placeholder for reference while implementing license key stuff
|
||||
const AuthApi = {
|
||||
whoami: () => get('auth/v1/user/whoami'),
|
||||
signup: (params) => post('auth/v1/user/signup', params),
|
||||
login: (params) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
ipcRenderer
|
||||
.invoke('bruno-account-request', {
|
||||
data: params,
|
||||
method: 'POST',
|
||||
url: `${process.env.NEXT_PUBLIC_BRUNO_SERVER_API}/auth/v1/user/login`
|
||||
})
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default AuthApi;
|
||||
30
packages/bruno-app/src/api/base.js
Normal file
30
packages/bruno-app/src/api/base.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const apiClient = axios.create({
|
||||
baseURL: process.env.NEXT_PUBLIC_GRAFNODE_SERVER_API
|
||||
});
|
||||
|
||||
apiClient.interceptors.request.use(
|
||||
(config) => {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
return {
|
||||
...config,
|
||||
headers: headers
|
||||
};
|
||||
},
|
||||
(error) => Promise.reject(error)
|
||||
);
|
||||
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error) => {
|
||||
return Promise.reject(error.response ? error.response.data : error);
|
||||
}
|
||||
);
|
||||
|
||||
const { get, post, put, delete: destroy } = apiClient;
|
||||
|
||||
export { get, post, put, destroy };
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
color: ${(props) => props.theme.text};
|
||||
color: var(--color-text);
|
||||
.collection-options {
|
||||
svg {
|
||||
position: relative;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import Modal from 'components/Modal/index';
|
||||
import { IconSpeakerphone, IconBrandTwitter, IconBrandGithub, IconBrandDiscord, IconBook } from '@tabler/icons';
|
||||
import { IconSpeakerphone, IconBrandTwitter } from '@tabler/icons';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import GithubSvg from 'assets/github.svg';
|
||||
|
||||
const BrunoSupport = ({ onClose }) => {
|
||||
return (
|
||||
@@ -9,31 +10,19 @@ const BrunoSupport = ({ onClose }) => {
|
||||
<Modal size="sm" title={'Support'} handleCancel={onClose} hideFooter={true}>
|
||||
<div className="collection-options">
|
||||
<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">
|
||||
<a href="https://github.com/usebruno/bruno/issues" target="_blank" className="flex items-center">
|
||||
<IconSpeakerphone size={18} strokeWidth={2} />
|
||||
<span className="label ml-2">Report Issues</span>
|
||||
</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 href="https://github.com/usebruno/bruno" target="_blank" className="flex items-center">
|
||||
<img src={GithubSvg.src} style={{ width: '18px' }} />
|
||||
<span className="label ml-2">Github</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">
|
||||
<a href="https://twitter.com/use_bruno" target="_blank" className="flex items-center">
|
||||
<IconBrandTwitter size={18} strokeWidth={2} />
|
||||
<span className="label ml-2">Twitter</span>
|
||||
</a>
|
||||
|
||||
@@ -2,51 +2,12 @@ import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
div.CodeMirror {
|
||||
background: ${(props) => props.theme.codemirror.bg};
|
||||
border: solid 1px ${(props) => props.theme.codemirror.border};
|
||||
}
|
||||
|
||||
.CodeMirror-overlayscroll-horizontal div,
|
||||
.CodeMirror-overlayscroll-vertical div {
|
||||
background: #d2d7db;
|
||||
border: solid 1px var(--color-codemirror-border);
|
||||
}
|
||||
|
||||
textarea.cm-editor {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
// Todo: dark mode temporary fix
|
||||
// Clean this
|
||||
.CodeMirror.cm-s-monokai {
|
||||
.CodeMirror-overlayscroll-horizontal div,
|
||||
.CodeMirror-overlayscroll-vertical div {
|
||||
background: #444444;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-s-monokai span.cm-property,
|
||||
.cm-s-monokai span.cm-attribute {
|
||||
color: #9cdcfe !important;
|
||||
}
|
||||
|
||||
.cm-s-monokai span.cm-string {
|
||||
color: #ce9178 !important;
|
||||
}
|
||||
|
||||
.cm-s-monokai span.cm-number {
|
||||
color: #b5cea8 !important;
|
||||
}
|
||||
|
||||
.cm-s-monokai span.cm-atom {
|
||||
color: #569cd6 !important;
|
||||
}
|
||||
|
||||
.cm-variable-valid {
|
||||
color: green;
|
||||
}
|
||||
.cm-variable-invalid {
|
||||
color: red;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -6,9 +6,6 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import { getEnvironmentVariables } from 'utils/collections';
|
||||
import { defineCodeMirrorBrunoVariablesMode } from 'utils/common/codemirror';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
let CodeMirror;
|
||||
@@ -18,7 +15,7 @@ if (!SERVER_RENDERED) {
|
||||
CodeMirror = require('codemirror');
|
||||
}
|
||||
|
||||
export default class CodeEditor extends React.Component {
|
||||
export default class QueryEditor extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@@ -26,7 +23,6 @@ export default class CodeEditor extends React.Component {
|
||||
// editor is updated, which can later be used to protect the editor from
|
||||
// unnecessary updates during the update lifecycle.
|
||||
this.cachedValue = props.value || '';
|
||||
this.variables = {};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -42,9 +38,7 @@ export default class CodeEditor extends React.Component {
|
||||
showCursorWhenSelecting: true,
|
||||
foldGutter: true,
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||
readOnly: this.props.readOnly,
|
||||
scrollbarStyle: 'overlay',
|
||||
theme: this.props.theme === 'dark' ? 'monokai' : 'default',
|
||||
readOnly: this.props.readOnly ? 'nocursor' : false,
|
||||
extraKeys: {
|
||||
'Cmd-Enter': () => {
|
||||
if (this.props.onRun) {
|
||||
@@ -66,8 +60,6 @@ export default class CodeEditor extends React.Component {
|
||||
this.props.onSave();
|
||||
}
|
||||
},
|
||||
'Cmd-F': 'findPersistent',
|
||||
'Ctrl-F': 'findPersistent',
|
||||
Tab: function (cm) {
|
||||
cm.replaceSelection(' ', 'end');
|
||||
}
|
||||
@@ -75,12 +67,11 @@ export default class CodeEditor extends React.Component {
|
||||
}));
|
||||
if (editor) {
|
||||
editor.on('change', this._onEdit);
|
||||
this.addOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -94,17 +85,7 @@ export default class CodeEditor extends React.Component {
|
||||
if (this.props.value !== prevProps.value && this.props.value !== this.cachedValue && this.editor) {
|
||||
this.cachedValue = this.props.value;
|
||||
this.editor.setValue(this.props.value);
|
||||
}
|
||||
|
||||
if (this.editor) {
|
||||
let variables = getEnvironmentVariables(this.props.collection);
|
||||
if (!isEqual(variables, this.variables)) {
|
||||
this.addOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.props.theme !== prevProps.theme && this.editor) {
|
||||
this.editor.setOption('theme', this.props.theme === 'dark' ? 'monokai' : 'default');
|
||||
this.editor.setOption('mode', this.props.mode);
|
||||
}
|
||||
this.ignoreChangeEvent = false;
|
||||
}
|
||||
@@ -119,7 +100,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;
|
||||
@@ -128,15 +109,6 @@ export default class CodeEditor extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
addOverlay = () => {
|
||||
const mode = this.props.mode || 'application/ld+json';
|
||||
let variables = getEnvironmentVariables(this.props.collection);
|
||||
this.variables = variables;
|
||||
|
||||
defineCodeMirrorBrunoVariablesMode(variables, mode);
|
||||
this.editor.setOption('mode', 'brunovariables');
|
||||
};
|
||||
|
||||
_onEdit = () => {
|
||||
if (!this.ignoreChangeEvent && this.editor) {
|
||||
this.cachedValue = this.editor.getValue();
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,5 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div``;
|
||||
|
||||
export default Wrapper;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,5 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div``;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -9,11 +9,11 @@ const Wrapper = styled.div`
|
||||
|
||||
.tippy-box {
|
||||
min-width: 135px;
|
||||
background-color: white;
|
||||
font-size: 0.8125rem;
|
||||
color: ${(props) => props.theme.dropdown.color};
|
||||
background-color: ${(props) => props.theme.dropdown.bg};
|
||||
box-shadow: ${(props) => props.theme.dropdown.shadow};
|
||||
border-radius: 3px;
|
||||
color: rgb(48 48 48);
|
||||
background: #fff;
|
||||
box-shadow: rgb(50 50 93 / 25%) 0px 6px 12px -2px, rgb(0 0 0 / 30%) 0px 3px 7px -3px;
|
||||
|
||||
.tippy-content {
|
||||
padding-left: 0;
|
||||
@@ -25,7 +25,6 @@ const Wrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.35rem 0.6rem;
|
||||
background-color: ${(props) => props.theme.dropdown.labelBg};
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
@@ -34,16 +33,8 @@ const Wrapper = styled.div`
|
||||
padding: 0.35rem 0.6rem;
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
color: ${(props) => props.theme.dropdown.iconColor};
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props) => props.theme.dropdown.hoverBg};
|
||||
}
|
||||
|
||||
&.border-top {
|
||||
border-top: solid 1px ${(props) => props.theme.dropdown.separator};
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -2,7 +2,7 @@ import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
.current-enviroment {
|
||||
background-color: ${(props) => props.theme.sidebar.badge.bg};
|
||||
background: #efefef;
|
||||
border-radius: 15px;
|
||||
|
||||
.caret {
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useRef, forwardRef, useState } from 'react';
|
||||
import find from 'lodash/find';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import { selectEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { IconSettings, IconCaretDown, IconDatabase, IconDatabaseOff } from '@tabler/icons';
|
||||
import { IconSettings, IconCaretDown, IconDatabase } from '@tabler/icons';
|
||||
import EnvironmentSettings from '../EnvironmentSettings';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useDispatch } from 'react-redux';
|
||||
@@ -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 (
|
||||
@@ -63,14 +63,13 @@ const EnvironmentSelector = ({ collection }) => {
|
||||
onSelect(null);
|
||||
}}
|
||||
>
|
||||
<IconDatabaseOff size={18} strokeWidth={1.5} />
|
||||
<span className="ml-2">No Environment</span>
|
||||
<span>No Environment</span>
|
||||
</div>
|
||||
<div className="dropdown-item border-top" onClick={() => setOpenSettingsModal(true)}>
|
||||
<div className="dropdown-item" style={{ borderTop: 'solid 1px #e7e7e7' }} onClick={() => setOpenSettingsModal(true)}>
|
||||
<div className="pr-2 text-gray-600">
|
||||
<IconSettings size={18} strokeWidth={1.5} />
|
||||
</div>
|
||||
<span>Configure</span>
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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%;
|
||||
}
|
||||
border: 1px solid #efefef;
|
||||
}
|
||||
|
||||
thead {
|
||||
color: ${(props) => props.theme.table.thead.color};
|
||||
color: #616161;
|
||||
font-size: 0.8125rem;
|
||||
user-select: none;
|
||||
}
|
||||
thead td {
|
||||
td {
|
||||
padding: 6px 10px;
|
||||
}
|
||||
}
|
||||
@@ -41,7 +29,6 @@ const Wrapper = styled.div`
|
||||
width: 100%;
|
||||
border: solid 1px transparent;
|
||||
outline: none !important;
|
||||
background-color: transparent;
|
||||
|
||||
&:focus {
|
||||
outline: none !important;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,30 +1,18 @@
|
||||
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);
|
||||
console.log(environment);
|
||||
|
||||
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 +20,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>
|
||||
|
||||
@@ -4,11 +4,8 @@ const StyledWrapper = styled.div`
|
||||
margin-inline: -1rem;
|
||||
margin-block: -1.5rem;
|
||||
|
||||
background-color: ${(props) => props.theme.collection.environment.settings.bg};
|
||||
|
||||
.environments-sidebar {
|
||||
background-color: ${(props) => props.theme.collection.environment.settings.sidebar.bg};
|
||||
border-right: solid 1px ${(props) => props.theme.collection.environment.settings.sidebar.borderRight};
|
||||
background-color: #eaeaea;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
@@ -23,15 +20,15 @@ const StyledWrapper = styled.div`
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
background-color: ${(props) => props.theme.collection.environment.settings.item.hoverBg};
|
||||
background-color: #e4e4e4;
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: ${(props) => props.theme.collection.environment.settings.item.active.bg} !important;
|
||||
border-left: solid 2px ${(props) => props.theme.collection.environment.settings.item.border};
|
||||
background-color: #dcdcdc !important;
|
||||
border-left: solid 2px var(--color-brand);
|
||||
&:hover {
|
||||
background-color: ${(props) => props.theme.collection.environment.settings.item.active.hoverBg} !important;
|
||||
background-color: #dcdcdc !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +36,7 @@ const StyledWrapper = styled.div`
|
||||
padding: 8px 10px;
|
||||
cursor: pointer;
|
||||
border-bottom: none;
|
||||
color: ${(props) => props.theme.textLink};
|
||||
color: var(--color-text-link);
|
||||
|
||||
&:hover {
|
||||
span {
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import React, { useEffect, useState, forwardRef, useRef } from 'react';
|
||||
import { findEnvironmentInCollection } from 'utils/collections';
|
||||
import usePrevious from 'hooks/usePrevious';
|
||||
import EnvironmentDetails from './EnvironmentDetails';
|
||||
import CreateEnvironment from '../CreateEnvironment/index';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
@@ -10,36 +8,9 @@ const EnvironmentList = ({ collection }) => {
|
||||
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
|
||||
const [openCreateModal, setOpenCreateModal] = useState(false);
|
||||
|
||||
const envUids = environments ? environments.map((env) => env.uid) : [];
|
||||
const prevEnvUids = usePrevious(envUids);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEnvironment) {
|
||||
return;
|
||||
}
|
||||
|
||||
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
|
||||
if (environment) {
|
||||
setSelectedEnvironment(environment);
|
||||
} else {
|
||||
setSelectedEnvironment(environments && environments.length ? environments[0] : null);
|
||||
}
|
||||
}, [collection, environments, selectedEnvironment]);
|
||||
|
||||
useEffect(() => {
|
||||
// check env add
|
||||
if (prevEnvUids && prevEnvUids.length && envUids.length > prevEnvUids.length) {
|
||||
const newEnv = environments.find((env) => !prevEnvUids.includes(env.uid));
|
||||
if (newEnv) {
|
||||
setSelectedEnvironment(newEnv);
|
||||
}
|
||||
}
|
||||
|
||||
// check env delete
|
||||
if (prevEnvUids && prevEnvUids.length && envUids.length < prevEnvUids.length) {
|
||||
setSelectedEnvironment(environments && environments.length ? environments[0] : null);
|
||||
}
|
||||
}, [envUids, environments, prevEnvUids]);
|
||||
setSelectedEnvironment(environments[0]);
|
||||
}, []);
|
||||
|
||||
if (!selectedEnvironment) {
|
||||
return null;
|
||||
@@ -54,11 +25,7 @@ const EnvironmentList = ({ collection }) => {
|
||||
{environments &&
|
||||
environments.length &&
|
||||
environments.map((env) => (
|
||||
<div
|
||||
key={env.uid}
|
||||
className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'}
|
||||
onClick={() => setSelectedEnvironment(env)}
|
||||
>
|
||||
<div key={env.uid} className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'} onClick={() => setSelectedEnvironment(env)}>
|
||||
<span>{env.name}</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
export default SendIcon;
|
||||
@@ -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;
|
||||
@@ -69,8 +66,8 @@ const Wrapper = styled.div`
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
text-transform: uppercase;
|
||||
color: ${(props) => props.theme.modal.title.color};
|
||||
background-color: ${(props) => props.theme.modal.title.bg};
|
||||
color: rgb(86 86 86);
|
||||
background-color: #f1f1f1;
|
||||
font-size: 0.75rem;
|
||||
padding: 12px;
|
||||
font-weight: 600;
|
||||
@@ -80,7 +77,7 @@ const Wrapper = styled.div`
|
||||
.close {
|
||||
font-size: 1.3rem;
|
||||
line-height: 1;
|
||||
color: ${(props) => props.theme.modal.iconColor};
|
||||
color: #000;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
opacity: 0.5;
|
||||
margin-top: -2px;
|
||||
@@ -93,30 +90,7 @@ const Wrapper = styled.div`
|
||||
|
||||
.bruno-modal-content {
|
||||
flex-grow: 1;
|
||||
background-color: ${(props) => props.theme.modal.body.bg};
|
||||
|
||||
.textbox {
|
||||
line-height: 1.42857143;
|
||||
border: 1px solid #ccc;
|
||||
padding: 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;
|
||||
}
|
||||
}
|
||||
|
||||
.bruno-form {
|
||||
color: ${(props) => props.theme.modal.body.color};
|
||||
}
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.bruno-modal-backdrop {
|
||||
@@ -133,7 +107,7 @@ const Wrapper = styled.div`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
opacity: ${(props) => props.theme.modal.backdrop.opacity};
|
||||
opacity: 0.4;
|
||||
top: 0;
|
||||
background: black;
|
||||
position: fixed;
|
||||
@@ -143,17 +117,10 @@ const Wrapper = styled.div`
|
||||
}
|
||||
|
||||
.bruno-modal-footer {
|
||||
background-color: ${(props) => props.theme.modal.body.bg};
|
||||
background-color: white;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
|
||||
&.modal-footer-none {
|
||||
.bruno-modal-content {
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
|
||||
@@ -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,15 @@ const Modal = ({
|
||||
if (isClosing) {
|
||||
classes += ' modal--animate-out';
|
||||
}
|
||||
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 +81,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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
color: ${(props) => props.theme.text};
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,20 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
color: ${(props) => props.theme.text};
|
||||
.rows {
|
||||
svg {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.label {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -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;
|
||||
@@ -1,7 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
color: var(--color-text);
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,90 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
* Assertion operators
|
||||
*
|
||||
* eq : equal to
|
||||
* neq : not equal to
|
||||
* gt : greater than
|
||||
* gte : greater than or equal to
|
||||
* lt : less than
|
||||
* lte : less than or equal to
|
||||
* in : in
|
||||
* notIn : not in
|
||||
* contains : contains
|
||||
* notContains : not contains
|
||||
* length : length
|
||||
* matches : matches
|
||||
* notMatches : not matches
|
||||
* startsWith : starts with
|
||||
* endsWith : ends with
|
||||
* between : between
|
||||
* isEmpty : is empty
|
||||
* isNull : is null
|
||||
* isUndefined : is undefined
|
||||
* isDefined : is defined
|
||||
* isTruthy : is truthy
|
||||
* isFalsy : is falsy
|
||||
* isJson : is json
|
||||
* isNumber : is number
|
||||
* isString : is string
|
||||
* isBoolean : is boolean
|
||||
*/
|
||||
|
||||
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'
|
||||
];
|
||||
|
||||
const handleChange = (e) => {
|
||||
onChange(e.target.value);
|
||||
};
|
||||
|
||||
const getLabel = (operator) => {
|
||||
switch (operator) {
|
||||
case 'eq':
|
||||
return 'equals';
|
||||
case 'neq':
|
||||
return 'notEquals';
|
||||
default:
|
||||
return operator;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<select value={operator} onChange={handleChange} className="mousetrap">
|
||||
{operators.map((operator) => (
|
||||
<option key={operator} value={operator}>
|
||||
{getLabel(operator)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
};
|
||||
|
||||
export default AssertionOperator;
|
||||
@@ -1,213 +0,0 @@
|
||||
import React from 'react';
|
||||
import { IconTrash } from '@tabler/icons';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import AssertionOperator from '../AssertionOperator';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
|
||||
/**
|
||||
* Assertion operators
|
||||
*
|
||||
* eq : equal to
|
||||
* neq : not equal to
|
||||
* gt : greater than
|
||||
* gte : greater than or equal to
|
||||
* lt : less than
|
||||
* lte : less than or equal to
|
||||
* in : in
|
||||
* notIn : not in
|
||||
* contains : contains
|
||||
* notContains : not contains
|
||||
* length : length
|
||||
* matches : matches
|
||||
* notMatches : not matches
|
||||
* startsWith : starts with
|
||||
* endsWith : ends with
|
||||
* between : between
|
||||
* isEmpty : is empty
|
||||
* isNull : is null
|
||||
* isUndefined : is undefined
|
||||
* isDefined : is defined
|
||||
* isTruthy : is truthy
|
||||
* isFalsy : is falsy
|
||||
* isJson : is json
|
||||
* isNumber : is number
|
||||
* isString : is string
|
||||
* isBoolean : is boolean
|
||||
*/
|
||||
const parseAssertionOperator = (str = '') => {
|
||||
if (!str || typeof str !== 'string' || !str.length) {
|
||||
return {
|
||||
operator: 'eq',
|
||||
value: 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'
|
||||
];
|
||||
|
||||
const unaryOperators = [
|
||||
'isEmpty',
|
||||
'isNull',
|
||||
'isUndefined',
|
||||
'isDefined',
|
||||
'isTruthy',
|
||||
'isFalsy',
|
||||
'isJson',
|
||||
'isNumber',
|
||||
'isString',
|
||||
'isBoolean'
|
||||
];
|
||||
|
||||
const [operator, ...rest] = str.trim().split(' ');
|
||||
const value = rest.join(' ');
|
||||
|
||||
if (unaryOperators.includes(operator)) {
|
||||
return {
|
||||
operator,
|
||||
value: ''
|
||||
};
|
||||
}
|
||||
|
||||
if (operators.includes(operator)) {
|
||||
return {
|
||||
operator,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
operator: 'eq',
|
||||
value: str
|
||||
};
|
||||
};
|
||||
|
||||
const isUnaryOperator = (operator) => {
|
||||
const unaryOperators = [
|
||||
'isEmpty',
|
||||
'isNull',
|
||||
'isUndefined',
|
||||
'isDefined',
|
||||
'isTruthy',
|
||||
'isFalsy',
|
||||
'isJson',
|
||||
'isNumber',
|
||||
'isString',
|
||||
'isBoolean'
|
||||
];
|
||||
|
||||
return unaryOperators.includes(operator);
|
||||
};
|
||||
|
||||
const AssertionRow = ({
|
||||
item,
|
||||
collection,
|
||||
assertion,
|
||||
handleAssertionChange,
|
||||
handleRemoveAssertion,
|
||||
onSave,
|
||||
handleRun
|
||||
}) => {
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const { operator, value } = parseAssertionOperator(assertion.value);
|
||||
|
||||
return (
|
||||
<tr key={assertion.uid}>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={assertion.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleAssertionChange(e, assertion, 'name')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<AssertionOperator
|
||||
operator={operator}
|
||||
onChange={(op) =>
|
||||
handleAssertionChange(
|
||||
{
|
||||
target: {
|
||||
value: `${op} ${value}`
|
||||
}
|
||||
},
|
||||
assertion,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{!isUnaryOperator(operator) ? (
|
||||
<SingleLineEditor
|
||||
value={value}
|
||||
theme={storedTheme}
|
||||
readOnly={true}
|
||||
onSave={onSave}
|
||||
onChange={(newValue) =>
|
||||
handleAssertionChange(
|
||||
{
|
||||
target: {
|
||||
value: `${operator} ${newValue}`
|
||||
}
|
||||
},
|
||||
assertion,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
/>
|
||||
) : (
|
||||
<input type="text" className="cursor-default" disabled />
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={assertion.enabled}
|
||||
tabIndex="-1"
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleAssertionChange(e, assertion, 'enabled')}
|
||||
/>
|
||||
<button tabIndex="-1" onClick={() => handleRemoveAssertion(assertion)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
export default AssertionRow;
|
||||
@@ -1,60 +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(4) {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
select {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-add-assertion {
|
||||
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;
|
||||
@@ -1,96 +0,0 @@
|
||||
import React from 'react';
|
||||
import get from 'lodash/get';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { addAssertion, updateAssertion, deleteAssertion } from 'providers/ReduxStore/slices/collections';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import AssertionRow from './AssertionRow';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const Assertions = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const assertions = item.draft ? get(item, 'draft.request.assertions') : get(item, 'request.assertions');
|
||||
|
||||
const handleAddAssertion = () => {
|
||||
dispatch(
|
||||
addAssertion({
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
const handleRun = () => dispatch(sendRequest(item, collection.uid));
|
||||
const handleAssertionChange = (e, _assertion, type) => {
|
||||
const assertion = cloneDeep(_assertion);
|
||||
switch (type) {
|
||||
case 'name': {
|
||||
assertion.name = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'value': {
|
||||
assertion.value = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'enabled': {
|
||||
assertion.enabled = e.target.checked;
|
||||
break;
|
||||
}
|
||||
}
|
||||
dispatch(
|
||||
updateAssertion({
|
||||
assertion: assertion,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleRemoveAssertion = (assertion) => {
|
||||
dispatch(
|
||||
deleteAssertion({
|
||||
assertUid: assertion.uid,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Expr</td>
|
||||
<td>Operator</td>
|
||||
<td>Value</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{assertions && assertions.length
|
||||
? assertions.map((assertion) => {
|
||||
return (
|
||||
<AssertionRow
|
||||
key={assertion.uid}
|
||||
assertion={assertion}
|
||||
item={item}
|
||||
collection={collection}
|
||||
handleAssertionChange={handleAssertionChange}
|
||||
handleRemoveAssertion={handleRemoveAssertion}
|
||||
onSave={onSave}
|
||||
handleRun={handleRun}
|
||||
/>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
<button className="btn-add-assertion text-link pr-2 py-3 mt-2 select-none" onClick={handleAddAssertion}>
|
||||
+ Add Assertion
|
||||
</button>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
export default Assertions;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,5 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div``;
|
||||
|
||||
export default Wrapper;
|
||||
@@ -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;
|
||||
@@ -5,28 +5,19 @@ const Wrapper = styled.div`
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-weight: 600;
|
||||
table-layout: fixed;
|
||||
|
||||
thead,
|
||||
td {
|
||||
border: 1px solid ${(props) => props.theme.table.border};
|
||||
border: 1px solid #efefef;
|
||||
}
|
||||
|
||||
thead {
|
||||
color: ${(props) => props.theme.table.thead.color};
|
||||
color: #616161;
|
||||
font-size: 0.8125rem;
|
||||
user-select: none;
|
||||
}
|
||||
td {
|
||||
padding: 6px 10px;
|
||||
|
||||
&:nth-child(1) {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
width: 70px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +29,6 @@ const Wrapper = styled.div`
|
||||
width: 100%;
|
||||
border: solid 1px transparent;
|
||||
outline: none !important;
|
||||
color: ${(props) => props.theme.table.input.color};
|
||||
background: transparent;
|
||||
|
||||
&:focus {
|
||||
outline: none !important;
|
||||
|
||||
@@ -3,19 +3,11 @@ 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 {
|
||||
addFormUrlEncodedParam,
|
||||
updateFormUrlEncodedParam,
|
||||
deleteFormUrlEncodedParam
|
||||
} from 'providers/ReduxStore/slices/collections';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { addFormUrlEncodedParam, updateFormUrlEncodedParam, deleteFormUrlEncodedParam } from 'providers/ReduxStore/slices/collections';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const FormUrlEncodedParams = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
const params = item.draft ? get(item, 'draft.request.body.formUrlEncoded') : get(item, 'request.body.formUrlEncoded');
|
||||
|
||||
const addParam = () => {
|
||||
@@ -27,8 +19,6 @@ const FormUrlEncodedParams = ({ item, collection }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
const handleRun = () => dispatch(sendRequest(item, collection.uid));
|
||||
const handleParamChange = (e, _param, type) => {
|
||||
const param = cloneDeep(_param);
|
||||
switch (type) {
|
||||
@@ -40,6 +30,10 @@ const FormUrlEncodedParams = ({ item, collection }) => {
|
||||
param.value = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'description': {
|
||||
param.description = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'enabled': {
|
||||
param.enabled = e.target.checked;
|
||||
break;
|
||||
@@ -71,6 +65,7 @@ const FormUrlEncodedParams = ({ item, collection }) => {
|
||||
<tr>
|
||||
<td>Key</td>
|
||||
<td>Value</td>
|
||||
<td>Description</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -92,35 +87,33 @@ const FormUrlEncodedParams = ({ item, collection }) => {
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<SingleLineEditor
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={param.value}
|
||||
theme={storedTheme}
|
||||
onSave={onSave}
|
||||
onChange={(newValue) =>
|
||||
handleParamChange(
|
||||
{
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
},
|
||||
param,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'value')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={param.description}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'description')}
|
||||
/>
|
||||
</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>
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
div.tabs {
|
||||
div.tab {
|
||||
.react-tabs__tab-list {
|
||||
border-bottom: none !important;
|
||||
padding-top: 0;
|
||||
padding-left: 0 !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
|
||||
.react-tabs__tab {
|
||||
padding: 6px 0px;
|
||||
border: none;
|
||||
user-select: none;
|
||||
border-bottom: solid 2px transparent;
|
||||
margin-right: 1.25rem;
|
||||
color: var(--color-tab-inactive);
|
||||
cursor: pointer;
|
||||
margin-right: 20px;
|
||||
color: rgb(125 125 125);
|
||||
outline: none !important;
|
||||
|
||||
&:focus,
|
||||
&:active,
|
||||
@@ -19,12 +27,36 @@ const StyledWrapper = styled.div`
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: ${(props) => props.theme.tabs.active.color} !important;
|
||||
border-bottom: solid 2px ${(props) => props.theme.tabs.active.border} !important;
|
||||
&:after {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.react-tabs__tab--selected {
|
||||
border: none;
|
||||
color: #322e2c !important;
|
||||
border-bottom: solid 2px var(--color-tab-active-border) !important;
|
||||
border-color: var(--color-tab-active-border) !important;
|
||||
background: inherit;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
|
||||
&:focus,
|
||||
&:active,
|
||||
&:focus-within,
|
||||
&:focus-visible,
|
||||
&:target {
|
||||
border: none;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-bottom: solid 2px var(--color-tab-active-border) !important;
|
||||
border-color: var(--color-tab-active-border) !important;
|
||||
background: inherit;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -1,180 +1,26 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import find from 'lodash/find';
|
||||
import get from 'lodash/get';
|
||||
import classnames from 'classnames';
|
||||
import { IconRefresh, IconLoader2, IconBook, IconDownload } from '@tabler/icons';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { updateRequestPaneTab } from 'providers/ReduxStore/slices/tabs';
|
||||
import React from 'react';
|
||||
import { Tab, TabList, TabPanel, Tabs } from 'react-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';
|
||||
import Assertions from 'components/RequestPane/Assertions';
|
||||
import Script from 'components/RequestPane/Script';
|
||||
import Tests from 'components/RequestPane/Tests';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { updateRequestGraphqlQuery } from 'providers/ReduxStore/slices/collections';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { findEnvironmentInCollection } from 'utils/collections';
|
||||
import useGraphqlSchema from './useGraphqlSchema';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, toggleDocs, handleGqlClickReference }) => {
|
||||
const dispatch = useDispatch();
|
||||
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 url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
|
||||
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);
|
||||
|
||||
const loadGqlSchema = () => {
|
||||
if (!isSchemaLoading) {
|
||||
loadSchema();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (onSchemaLoad) {
|
||||
onSchemaLoad(schema);
|
||||
}
|
||||
}, [schema]);
|
||||
|
||||
const onQueryChange = (value) => {
|
||||
dispatch(
|
||||
updateRequestGraphqlQuery({
|
||||
query: value,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
const onRun = () => dispatch(sendRequest(item, collection.uid));
|
||||
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
|
||||
const selectTab = (tab) => {
|
||||
dispatch(
|
||||
updateRequestPaneTab({
|
||||
uid: item.uid,
|
||||
requestPaneTab: tab
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'variables': {
|
||||
return <GraphQLVariables item={item} variables={variables} collection={collection} />;
|
||||
}
|
||||
case 'headers': {
|
||||
return <RequestHeaders item={item} collection={collection} />;
|
||||
}
|
||||
case 'auth': {
|
||||
return <Auth item={item} collection={collection} />;
|
||||
}
|
||||
case 'vars': {
|
||||
return <Vars item={item} collection={collection} />;
|
||||
}
|
||||
case 'assert': {
|
||||
return <Assertions item={item} collection={collection} />;
|
||||
}
|
||||
case 'script': {
|
||||
return <Script item={item} collection={collection} />;
|
||||
}
|
||||
case 'tests': {
|
||||
return <Tests item={item} collection={collection} />;
|
||||
}
|
||||
default: {
|
||||
return <div className="mt-4">404 | Not found</div>;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!activeTabUid) {
|
||||
return <div>Something went wrong</div>;
|
||||
}
|
||||
|
||||
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>;
|
||||
}
|
||||
|
||||
const getTabClassname = (tabName) => {
|
||||
return classnames(`tab select-none ${tabName}`, {
|
||||
active: tabName === focusedTab.requestPaneTab
|
||||
});
|
||||
};
|
||||
|
||||
const GraphQLRequestPane = ({ onRunQuery, schema, leftPaneWidth, value, onQueryChange }) => {
|
||||
return (
|
||||
<StyledWrapper className="flex flex-col h-full relative">
|
||||
<div className="flex flex-wrap items-center tabs" role="tablist">
|
||||
<div className={getTabClassname('query')} role="tab" onClick={() => selectTab('query')}>
|
||||
Query
|
||||
</div>
|
||||
<div className={getTabClassname('variables')} role="tab" onClick={() => selectTab('variables')}>
|
||||
Variables
|
||||
</div>
|
||||
<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>
|
||||
<div className={getTabClassname('script')} role="tab" onClick={() => selectTab('script')}>
|
||||
Script
|
||||
</div>
|
||||
<div className={getTabClassname('assert')} role="tab" onClick={() => selectTab('assert')}>
|
||||
Assert
|
||||
</div>
|
||||
<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>
|
||||
<StyledWrapper className="h-full">
|
||||
<Tabs className="react-tabs mt-1 flex flex-grow flex-col h-full" forceRenderTabPanel>
|
||||
<TabList>
|
||||
<Tab tabIndex="-1">Query</Tab>
|
||||
<Tab tabIndex="-1">Headers</Tab>
|
||||
</TabList>
|
||||
<TabPanel>
|
||||
<div className="mt-4">
|
||||
<QueryEditor schema={schema} width={leftPaneWidth} value={value} onRunQuery={onRunQuery} onEdit={onQueryChange} />
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
<section className="flex w-full mt-5">{getTabPanel(focusedTab.requestPaneTab)}</section>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<RequestHeaders />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { buildClientSchema } from 'graphql';
|
||||
import { fetchGqlSchema } from 'utils/network';
|
||||
import { simpleHash } from 'utils/common';
|
||||
|
||||
const schemaHashPrefix = 'bruno.graphqlSchema';
|
||||
|
||||
const useGraphqlSchema = (endpoint, environment, request, collection) => {
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
return buildClientSchema(JSON.parse(saved));
|
||||
} catch {
|
||||
localStorage.setItem(localStorageKey, null);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
const loadSchema = () => {
|
||||
setIsLoading(true);
|
||||
fetchGqlSchema(endpoint, environment, request, collection)
|
||||
.then((res) => res.data)
|
||||
.then((s) => {
|
||||
if (s && s.data) {
|
||||
setSchema(buildClientSchema(s.data));
|
||||
setIsLoading(false);
|
||||
localStorage.setItem(localStorageKey, JSON.stringify(s.data));
|
||||
toast.success('GraphQL Schema loaded successfully');
|
||||
} else {
|
||||
return Promise.reject(new Error('An error occurred while introspecting schema'));
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
setIsLoading(false);
|
||||
setError(err);
|
||||
toast.error('Error occurred while loading GraphQL Schema');
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
schema,
|
||||
loadSchema,
|
||||
error
|
||||
};
|
||||
};
|
||||
|
||||
export default useGraphqlSchema;
|
||||
@@ -1,10 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
div.CodeMirror {
|
||||
/* todo: find a better way */
|
||||
height: calc(100vh - 220px);
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -1,42 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import CodeEditor from 'components/CodeEditor';
|
||||
import { updateRequestGraphqlVariables } from 'providers/ReduxStore/slices/collections';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const GraphQLVariables = ({ variables, item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const onEdit = (value) => {
|
||||
dispatch(
|
||||
updateRequestGraphqlVariables({
|
||||
variables: 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">
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
value={variables || ''}
|
||||
theme={storedTheme}
|
||||
onEdit={onEdit}
|
||||
mode="javascript"
|
||||
onRun={onRun}
|
||||
onSave={onSave}
|
||||
/>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default GraphQLVariables;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user