Compare commits
74 Commits
feature/en
...
bugfix/col
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41d9bbd2d8 | ||
|
|
ac4e3a9f3d | ||
|
|
0ecaba27a6 | ||
|
|
2c8ef7b626 | ||
|
|
ea3a9394c9 | ||
|
|
995c6b3fd0 | ||
|
|
bd6ce6a67b | ||
|
|
3c3c9a6026 | ||
|
|
6b68857b81 | ||
|
|
405f253eef | ||
|
|
4b6439785f | ||
|
|
f9806e69a5 | ||
|
|
ba219d66db | ||
|
|
a41f4fe024 | ||
|
|
93544f8ae6 | ||
|
|
d6e4d07e2c | ||
|
|
503f0b8a17 | ||
|
|
5fc9bbd729 | ||
|
|
eefef27dec | ||
|
|
e98f219448 | ||
|
|
ff87586a1d | ||
|
|
8a96a0ce71 | ||
|
|
9fae7f72d4 | ||
|
|
ad1824e473 | ||
|
|
8045751671 | ||
|
|
c258bc1590 | ||
|
|
075e9162c2 | ||
|
|
d91ee36192 | ||
|
|
579bd424fc | ||
|
|
78645ad52f | ||
|
|
241ee5e788 | ||
|
|
6573df41b0 | ||
|
|
fe900b90c9 | ||
|
|
7078d5cec2 | ||
|
|
46949e48ba | ||
|
|
abc00b810f | ||
|
|
510e549d34 | ||
|
|
42a60a3372 | ||
|
|
f1aaf862ae | ||
|
|
ecc2252e84 | ||
|
|
2efc11ff6b | ||
|
|
6a36313e0e | ||
|
|
6380797f92 | ||
|
|
d8f58aeb0d | ||
|
|
0f5b75ddbf | ||
|
|
7ca6270f2b | ||
|
|
c6ac90a9f8 | ||
|
|
d640dafb06 | ||
|
|
d8cdd2ad8b | ||
|
|
118658822d | ||
|
|
0709666b03 | ||
|
|
fad953a983 | ||
|
|
75f6daec06 | ||
|
|
f2ffca35da | ||
|
|
c95bc8fdf9 | ||
|
|
44aa019754 | ||
|
|
91981a48e4 | ||
|
|
d546709b26 | ||
|
|
6feca9937e | ||
|
|
4ff268712f | ||
|
|
a78bdf87fe | ||
|
|
a84080b482 | ||
|
|
b3bf29d6b2 | ||
|
|
013f9f9e3d | ||
|
|
e46e3d5b22 | ||
|
|
8763ff2ad1 | ||
|
|
7ddfac1ece | ||
|
|
6bb3967379 | ||
|
|
d49eb4df33 | ||
|
|
410bc70318 | ||
|
|
097a6240ad | ||
|
|
6b0ccac1bf | ||
|
|
f8fbc88239 | ||
|
|
819e8c2ccd |
7
.gitignore
vendored
@@ -2,6 +2,9 @@
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
yarn.lock
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
.pnp
|
||||
.pnp.js
|
||||
|
||||
@@ -10,6 +13,10 @@ coverage
|
||||
|
||||
# production
|
||||
build
|
||||
chrome-extension
|
||||
chrome-extension.pem
|
||||
chrome-extension.crx
|
||||
bruno.zip
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
BIN
assets/images/logo-transparent.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
assets/images/logo.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
44
contributing.md
Normal file
@@ -0,0 +1,44 @@
|
||||
## 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 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
|
||||
* 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 v14.x and npm 8.x. We use npm workspaces in the project
|
||||
|
||||
### Lets start coding
|
||||
|
||||
```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
|
||||
@@ -1 +1,27 @@
|
||||
## development
|
||||
|
||||
```bash
|
||||
# install deps
|
||||
npm i
|
||||
|
||||
# run next app
|
||||
npm run dev --workspace=packages/bruno-app
|
||||
|
||||
# run electron app
|
||||
npm run dev --workspace=packages/bruno-electron
|
||||
|
||||
# build next app
|
||||
npm run build --workspace=packages/bruno-app
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
# testing
|
||||
|
||||
```bash
|
||||
# bruno-schema
|
||||
npm test --workspace=packages/bruno-schema
|
||||
|
||||
```
|
||||
|
||||
4652
package-lock.json
generated
60
package.json
@@ -1,60 +1,22 @@
|
||||
{
|
||||
"name": "bruno-workspace",
|
||||
"name": "usebruno",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"packages/bruno-app",
|
||||
"packages/bruno-electron",
|
||||
"packages/bruno-tauri",
|
||||
"packages/bruno-schema",
|
||||
"packages/bruno-testbench"
|
||||
],
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||
"@fortawesome/react-fontawesome": "^0.1.16",
|
||||
"@reduxjs/toolkit": "^1.8.0",
|
||||
"@tabler/icons": "^1.46.0",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"axios": "^0.26.0",
|
||||
"classnames": "^2.3.1",
|
||||
"codemirror": "^5.65.2",
|
||||
"codemirror-graphql": "^1.2.5",
|
||||
"escape-html": "^1.0.3",
|
||||
"formik": "^2.2.9",
|
||||
"graphiql": "^1.5.9",
|
||||
"graphql": "^16.2.0",
|
||||
"graphql-request": "^3.7.0",
|
||||
"idb": "^7.0.0",
|
||||
"immer": "^9.0.12",
|
||||
"lodash": "^4.17.21",
|
||||
"mousetrap": "^1.6.5",
|
||||
"nanoid": "^3.1.30",
|
||||
"next": "^12.1.0",
|
||||
"qs": "^6.11.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-hot-toast": "^2.4.0",
|
||||
"react-redux": "^7.2.6",
|
||||
"react-tabs": "^3.2.3",
|
||||
"sass": "^1.46.0",
|
||||
"split-on-first": "^3.0.0",
|
||||
"styled-components": "^5.3.3",
|
||||
"tailwindcss": "^2.2.19",
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.16.0",
|
||||
"@babel/plugin-transform-spread": "^7.16.7",
|
||||
"@babel/preset-env": "^7.16.4",
|
||||
"@babel/preset-react": "^7.16.0",
|
||||
"@babel/runtime": "^7.16.3",
|
||||
"babel-loader": "^8.2.3",
|
||||
"css-loader": "^6.5.1",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-loader": "^3.0.1",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"mini-css-extract-plugin": "^2.4.5",
|
||||
"style-loader": "^3.3.1",
|
||||
"webpack": "^5.64.4",
|
||||
"webpack-cli": "^4.9.1"
|
||||
"jest": "^29.2.0",
|
||||
"randomstring": "^1.2.2"
|
||||
},
|
||||
"scripts": {
|
||||
"dev:web": "npm run dev --workspace=packages/bruno-app",
|
||||
"build:web": "npm run build --workspace=packages/bruno-app",
|
||||
"dev:electron": "npm run dev --workspace=packages/bruno-electron",
|
||||
"build:chrome-extension": "./scripts/build-chrome-extension.sh",
|
||||
"build:electron": "./scripts/build-electron.sh"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,4 +2,4 @@ ENV=production
|
||||
|
||||
NEXT_PUBLIC_ENV=prod
|
||||
|
||||
NEXT_PUBLIC_GRAFNODE_SERVER_API=https://ada.grafnode.com/api
|
||||
NEXT_PUBLIC_BRUNO_SERVER_API=https://ada.grafnode.com/api
|
||||
3
packages/bruno-app/.gitignore
vendored
@@ -4,6 +4,9 @@
|
||||
node_modules
|
||||
.pnp
|
||||
.pnp.js
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
# testing
|
||||
coverage
|
||||
|
||||
7
packages/bruno-app/.prettierrc.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"printWidth": 180
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
"allowSyntheticDefaultImports": false,
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"assets/*": ["src/assets/*"],
|
||||
"components/*": ["src/components/*"],
|
||||
"api/*": ["src/api/*"],
|
||||
"pageComponents/*": ["src/pageComponents/*"],
|
||||
|
||||
4652
packages/bruno-app/package-lock.json
generated
@@ -2,13 +2,65 @@
|
||||
"name": "@usebruno/app",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "../../node_modules/.bin/next dev",
|
||||
"build": "next build",
|
||||
"dev": "next dev",
|
||||
"build": "next build && next export",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
"lint": "next lint",
|
||||
"prettier": "prettier --write \"./src/**/*.{js,jsx,json,ts,tsx}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||
"@fortawesome/react-fontawesome": "^0.1.16",
|
||||
"@reduxjs/toolkit": "^1.8.0",
|
||||
"@tabler/icons": "^1.46.0",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@usebruno/schema": "0.1.0",
|
||||
"axios": "^0.26.0",
|
||||
"classnames": "^2.3.1",
|
||||
"codemirror": "^5.65.2",
|
||||
"codemirror-graphql": "^1.2.5",
|
||||
"escape-html": "^1.0.3",
|
||||
"file-dialog": "^0.0.8",
|
||||
"file-saver": "^2.0.5",
|
||||
"formik": "^2.2.9",
|
||||
"graphiql": "^1.5.9",
|
||||
"graphql": "^16.2.0",
|
||||
"graphql-request": "^3.7.0",
|
||||
"idb": "^7.0.0",
|
||||
"immer": "^9.0.15",
|
||||
"lodash": "^4.17.21",
|
||||
"mousetrap": "^1.6.5",
|
||||
"nanoid": "3.3.4",
|
||||
"next": "12.3.1",
|
||||
"path": "^0.12.7",
|
||||
"qs": "^6.11.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-hot-toast": "^2.4.0",
|
||||
"react-redux": "^7.2.6",
|
||||
"react-tabs": "^3.2.3",
|
||||
"sass": "^1.46.0",
|
||||
"split-on-first": "^3.0.0",
|
||||
"styled-components": "^5.3.3",
|
||||
"tailwindcss": "^2.2.19",
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.16.0",
|
||||
"@babel/plugin-transform-spread": "^7.16.7",
|
||||
"@babel/preset-env": "^7.16.4",
|
||||
"@babel/preset-react": "^7.16.0",
|
||||
"@babel/runtime": "^7.16.3",
|
||||
"babel-loader": "^8.2.3",
|
||||
"css-loader": "^6.5.1",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-loader": "^3.0.1",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"mini-css-extract-plugin": "^2.4.5",
|
||||
"prettier": "^2.7.1",
|
||||
"style-loader": "^3.3.1",
|
||||
"webpack": "^5.64.4",
|
||||
"webpack-cli": "^4.9.1"
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 15 KiB |
BIN
packages/bruno-app/public/images/landing.png
Normal file
|
After Width: | Height: | Size: 436 KiB |
@@ -1,4 +0,0 @@
|
||||
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -6,17 +6,18 @@ const AuthApi = {
|
||||
signup: (params) => post('auth/v1/user/signup', params),
|
||||
login: (params) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { ipcRenderer } = window.require("electron");
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
ipcRenderer.invoke('grafnode-account-request', {
|
||||
data: params,
|
||||
method: 'POST',
|
||||
url: `${process.env.NEXT_PUBLIC_GRAFNODE_SERVER_API}/auth/v1/user/login`,
|
||||
})
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
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;
|
||||
export default AuthApi;
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
import axios from "axios";
|
||||
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'
|
||||
};
|
||||
apiClient.interceptors.request.use(
|
||||
(config) => {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
return ({
|
||||
...config,
|
||||
headers: headers
|
||||
});
|
||||
}, error => Promise.reject(error));
|
||||
return {
|
||||
...config,
|
||||
headers: headers
|
||||
};
|
||||
},
|
||||
(error) => Promise.reject(error)
|
||||
);
|
||||
|
||||
apiClient.interceptors.response.use((response) =>
|
||||
response,
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error) => {
|
||||
return Promise.reject(error.response ? error.response.data : error);
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 814 B After Width: | Height: | Size: 814 B |
|
Before Width: | Height: | Size: 211 B After Width: | Height: | Size: 211 B |
@@ -1,30 +1,94 @@
|
||||
import React from 'react';
|
||||
|
||||
const Bruno = ({width}) => {
|
||||
const Bruno = ({ width }) => {
|
||||
return (
|
||||
<svg id="emoji" width={width} viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="color">
|
||||
<path fill="#F4AA41" stroke="none" d="M23.5,14.5855l-4.5,1.75l-7.25,8.5l-4.5,10.75l2,5.25c1.2554,3.7911,3.5231,7.1832,7.25,10l2.5-3.3333 c0,0,3.8218,7.7098,10.7384,8.9598c0,0,10.2616,1.936,15.5949-0.8765c3.4203-1.8037,4.4167-4.4167,4.4167-4.4167l3.4167-3.4167 l1.5833,2.3333l2.0833-0.0833l5.4167-7.25L64,37.3355l-0.1667-4.5l-2.3333-5.5l-4.8333-7.4167c0,0-2.6667-4.9167-8.1667-3.9167 c0,0-6.5-4.8333-11.8333-4.0833S32.0833,10.6688,23.5,14.5855z"/>
|
||||
<polygon fill="#EA5A47" stroke="none" points="36,47.2521 32.9167,49.6688 30.4167,49.6688 30.3333,53.5021 31.0833,57.0021 32.1667,58.9188 35,60.4188 39.5833,59.8355 41.1667,58.0855 42.1667,53.8355 41.9167,49.8355 39.9167,50.0855"/>
|
||||
<polygon fill="#3F3F3F" stroke="none" points="32.5,36.9188 30.9167,40.6688 33.0833,41.9188 34.3333,42.4188 38.6667,42.5855 41.5833,40.3355 39.8333,37.0855"/>
|
||||
<path
|
||||
fill="#F4AA41"
|
||||
stroke="none"
|
||||
d="M23.5,14.5855l-4.5,1.75l-7.25,8.5l-4.5,10.75l2,5.25c1.2554,3.7911,3.5231,7.1832,7.25,10l2.5-3.3333 c0,0,3.8218,7.7098,10.7384,8.9598c0,0,10.2616,1.936,15.5949-0.8765c3.4203-1.8037,4.4167-4.4167,4.4167-4.4167l3.4167-3.4167 l1.5833,2.3333l2.0833-0.0833l5.4167-7.25L64,37.3355l-0.1667-4.5l-2.3333-5.5l-4.8333-7.4167c0,0-2.6667-4.9167-8.1667-3.9167 c0,0-6.5-4.8333-11.8333-4.0833S32.0833,10.6688,23.5,14.5855z"
|
||||
/>
|
||||
<polygon
|
||||
fill="#EA5A47"
|
||||
stroke="none"
|
||||
points="36,47.2521 32.9167,49.6688 30.4167,49.6688 30.3333,53.5021 31.0833,57.0021 32.1667,58.9188 35,60.4188 39.5833,59.8355 41.1667,58.0855 42.1667,53.8355 41.9167,49.8355 39.9167,50.0855"
|
||||
/>
|
||||
<polygon fill="#3F3F3F" stroke="none" points="32.5,36.9188 30.9167,40.6688 33.0833,41.9188 34.3333,42.4188 38.6667,42.5855 41.5833,40.3355 39.8333,37.0855" />
|
||||
</g>
|
||||
<g id="hair"/>
|
||||
<g id="skin"/>
|
||||
<g id="skin-shadow"/>
|
||||
<g id="hair" />
|
||||
<g id="skin" />
|
||||
<g id="skin-shadow" />
|
||||
<g id="line">
|
||||
<path fill="#000000" stroke="none" d="M29.5059,30.1088c0,0-1.8051,1.2424-2.7484,0.6679c-0.9434-0.5745-1.2424-1.8051-0.6679-2.7484 s1.805-1.2424,2.7484-0.6679S29.5059,30.1088,29.5059,30.1088z"/>
|
||||
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M33.1089,37.006h6.1457c0.4011,0,0.7634,0.2397,0.9203,0.6089l1.1579,2.7245l-2.1792,1.1456 c-0.6156,0.3236-1.3654-0.0645-1.4567-0.754"/>
|
||||
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M34.7606,40.763c-0.1132,0.6268-0.7757,0.9895-1.3647,0.7471l-2.3132-0.952l1.0899-2.9035 c0.1465-0.3901,0.5195-0.6486,0.9362-0.6486"/>
|
||||
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M30.4364,50.0268c0,0-0.7187,8.7934,3.0072,9.9375c2.6459,0.8125,5.1497,0.5324,6.0625-0.25 c0.875-0.75,2.6323-4.4741,1.8267-9.6875"/>
|
||||
<path fill="#000000" stroke="none" d="M44.2636,30.1088c0,0,1.805,1.2424,2.7484,0.6679c0.9434-0.5745,1.2424-1.8051,0.6679-2.7484 c-0.5745-0.9434-1.805-1.2424-2.7484-0.6679C43.9881,27.9349,44.2636,30.1088,44.2636,30.1088z"/>
|
||||
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M25.6245,42.8393c-0.475,3.6024,2.2343,5.7505,4.2847,6.8414c1.1968,0.6367,2.6508,0.5182,3.7176-0.3181l2.581-2.0233l2.581,2.0233 c1.0669,0.8363,2.5209,0.9548,3.7176,0.3181c2.0504-1.0909,4.7597-3.239,4.2847-6.8414"/>
|
||||
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M19.9509,28.3572c-2.3166,5.1597-0.5084,13.0249,0.119,15.3759c0.122,0.4571,0.0755,0.9355-0.1271,1.3631l-1.9874,4.1937 c-0.623,1.3146-2.3934,1.5533-3.331,0.4409c-3.1921-3.7871-8.5584-11.3899-6.5486-16.686 c7.0625-18.6104,15.8677-18.1429,15.8677-18.1429c2.8453-1.9336,13.1042-6.9375,24.8125,0.875c0,0,8.6323-1.7175,14.9375,16.9375 c1.8036,5.3362-3.4297,12.8668-6.5506,16.6442c-0.9312,1.127-2.7162,0.8939-3.3423-0.4272l-1.9741-4.1656 c-0.2026-0.4275-0.2491-0.906-0.1271-1.3631c0.6275-2.3509,2.4356-10.2161,0.119-15.3759"/>
|
||||
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M52.6309,46.4628c0,0-3.0781,6.7216-7.8049,8.2712"/>
|
||||
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M19.437,46.969c0,0,3.0781,6.0823,7.8049,7.632"/>
|
||||
<line x1="36.2078" x2="36.2078" y1="47.3393" y2="44.3093" fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2"/>
|
||||
<path
|
||||
fill="#000000"
|
||||
stroke="none"
|
||||
d="M29.5059,30.1088c0,0-1.8051,1.2424-2.7484,0.6679c-0.9434-0.5745-1.2424-1.8051-0.6679-2.7484 s1.805-1.2424,2.7484-0.6679S29.5059,30.1088,29.5059,30.1088z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#000000"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit="10"
|
||||
strokeWidth="2"
|
||||
d="M33.1089,37.006h6.1457c0.4011,0,0.7634,0.2397,0.9203,0.6089l1.1579,2.7245l-2.1792,1.1456 c-0.6156,0.3236-1.3654-0.0645-1.4567-0.754"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#000000"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit="10"
|
||||
strokeWidth="2"
|
||||
d="M34.7606,40.763c-0.1132,0.6268-0.7757,0.9895-1.3647,0.7471l-2.3132-0.952l1.0899-2.9035 c0.1465-0.3901,0.5195-0.6486,0.9362-0.6486"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#000000"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit="10"
|
||||
strokeWidth="2"
|
||||
d="M30.4364,50.0268c0,0-0.7187,8.7934,3.0072,9.9375c2.6459,0.8125,5.1497,0.5324,6.0625-0.25 c0.875-0.75,2.6323-4.4741,1.8267-9.6875"
|
||||
/>
|
||||
<path
|
||||
fill="#000000"
|
||||
stroke="none"
|
||||
d="M44.2636,30.1088c0,0,1.805,1.2424,2.7484,0.6679c0.9434-0.5745,1.2424-1.8051,0.6679-2.7484 c-0.5745-0.9434-1.805-1.2424-2.7484-0.6679C43.9881,27.9349,44.2636,30.1088,44.2636,30.1088z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#000000"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit="10"
|
||||
strokeWidth="2"
|
||||
d="M25.6245,42.8393c-0.475,3.6024,2.2343,5.7505,4.2847,6.8414c1.1968,0.6367,2.6508,0.5182,3.7176-0.3181l2.581-2.0233l2.581,2.0233 c1.0669,0.8363,2.5209,0.9548,3.7176,0.3181c2.0504-1.0909,4.7597-3.239,4.2847-6.8414"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#000000"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit="10"
|
||||
strokeWidth="2"
|
||||
d="M19.9509,28.3572c-2.3166,5.1597-0.5084,13.0249,0.119,15.3759c0.122,0.4571,0.0755,0.9355-0.1271,1.3631l-1.9874,4.1937 c-0.623,1.3146-2.3934,1.5533-3.331,0.4409c-3.1921-3.7871-8.5584-11.3899-6.5486-16.686 c7.0625-18.6104,15.8677-18.1429,15.8677-18.1429c2.8453-1.9336,13.1042-6.9375,24.8125,0.875c0,0,8.6323-1.7175,14.9375,16.9375 c1.8036,5.3362-3.4297,12.8668-6.5506,16.6442c-0.9312,1.127-2.7162,0.8939-3.3423-0.4272l-1.9741-4.1656 c-0.2026-0.4275-0.2491-0.906-0.1271-1.3631c0.6275-2.3509,2.4356-10.2161,0.119-15.3759"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#000000"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit="10"
|
||||
strokeWidth="2"
|
||||
d="M52.6309,46.4628c0,0-3.0781,6.7216-7.8049,8.2712"
|
||||
/>
|
||||
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M19.437,46.969c0,0,3.0781,6.0823,7.8049,7.632" />
|
||||
<line x1="36.2078" x2="36.2078" y1="47.3393" y2="44.3093" fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default Bruno;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
color: var(--color-text);
|
||||
.collection-options {
|
||||
svg {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.label {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
36
packages/bruno-app/src/components/BrunoSupport/index.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import Modal from 'components/Modal/index';
|
||||
import { IconSpeakerphone, IconBrandTwitter } from '@tabler/icons';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import GithubSvg from 'assets/github.svg';
|
||||
|
||||
const BrunoSupport = ({ onClose }) => {
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<Modal size="sm" title={'Support'} handleCancel={onClose} hideFooter={true}>
|
||||
<div className="collection-options">
|
||||
<div className="mt-2">
|
||||
<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://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://twitter.com/use_bruno" target="_blank" className="flex items-center">
|
||||
<IconBrandTwitter size={18} strokeWidth={2} />
|
||||
<span className="label ml-2">Twitter</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default BrunoSupport;
|
||||
@@ -11,4 +11,3 @@ const StyledWrapper = styled.div`
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
|
||||
@@ -37,33 +37,33 @@ export default class QueryEditor extends React.Component {
|
||||
matchBrackets: true,
|
||||
showCursorWhenSelecting: true,
|
||||
foldGutter: true,
|
||||
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||
readOnly: this.props.readOnly ? 'nocursor' : false,
|
||||
extraKeys: {
|
||||
'Cmd-Enter': () => {
|
||||
if(this.props.onRun) {
|
||||
if (this.props.onRun) {
|
||||
this.props.onRun();
|
||||
}
|
||||
},
|
||||
'Ctrl-Enter': () => {
|
||||
if(this.props.onRun) {
|
||||
if (this.props.onRun) {
|
||||
this.props.onRun();
|
||||
}
|
||||
},
|
||||
'Cmd-S': () => {
|
||||
if(this.props.onSave) {
|
||||
if (this.props.onSave) {
|
||||
this.props.onSave();
|
||||
}
|
||||
},
|
||||
'Ctrl-S': () => {
|
||||
if(this.props.onSave) {
|
||||
if (this.props.onSave) {
|
||||
this.props.onSave();
|
||||
}
|
||||
},
|
||||
'Tab': function(cm){
|
||||
cm.replaceSelection(" " , "end");
|
||||
Tab: function (cm) {
|
||||
cm.replaceSelection(' ', 'end');
|
||||
}
|
||||
},
|
||||
}
|
||||
}));
|
||||
if (editor) {
|
||||
editor.on('change', this._onEdit);
|
||||
@@ -82,14 +82,10 @@ export default class QueryEditor extends React.Component {
|
||||
this.editor.options.jump.schema = this.props.schema;
|
||||
CodeMirror.signal(this.editor, 'change', this.editor);
|
||||
}
|
||||
if (
|
||||
this.props.value !== prevProps.value &&
|
||||
this.props.value !== this.cachedValue &&
|
||||
this.editor
|
||||
) {
|
||||
if (this.props.value !== prevProps.value && this.props.value !== this.cachedValue && this.editor) {
|
||||
this.cachedValue = this.props.value;
|
||||
this.editor.setValue(this.props.value);
|
||||
this.editor.setOption("mode", this.props.mode);
|
||||
this.editor.setOption('mode', this.props.mode);
|
||||
}
|
||||
this.ignoreChangeEvent = false;
|
||||
}
|
||||
@@ -106,7 +102,7 @@ export default class QueryEditor extends React.Component {
|
||||
<StyledWrapper
|
||||
className="h-full"
|
||||
aria-label="Code Editor"
|
||||
ref={node => {
|
||||
ref={(node) => {
|
||||
this._node = node;
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -24,13 +24,13 @@ const Wrapper = styled.div`
|
||||
.label-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: .35rem .6rem;
|
||||
padding: 0.35rem 0.6rem;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: .35rem .6rem;
|
||||
padding: 0.35rem 0.6rem;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -2,19 +2,10 @@ import React from 'react';
|
||||
import Tippy from '@tippyjs/react';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const Dropdown = ({icon, children, onCreate, placement}) => {
|
||||
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>
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import Portal from "components/Portal/index";
|
||||
import Modal from "components/Modal/index";
|
||||
import { useFormik } from 'formik';
|
||||
import { addWorkspace } from 'providers/ReduxStore/slices/workspaces';
|
||||
import * as Yup from 'yup';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
const CreateEnvironment = ({onClose}) => {
|
||||
const dispatch = useDispatch();
|
||||
const inputRef = useRef();
|
||||
const formik = useFormik({
|
||||
enableReinitialize: true,
|
||||
initialValues: {
|
||||
name: ""
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
name: Yup.string()
|
||||
.min(1, 'must be atleast 1 characters')
|
||||
.max(30, 'must be 30 characters or less')
|
||||
.required('name is required')
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
// dispatch(addWorkspace({name: values.name}));
|
||||
// onClose();
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if(inputRef && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, [inputRef]);
|
||||
|
||||
const onSubmit = () => {
|
||||
formik.handleSubmit();
|
||||
}
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<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">Environment Name</label>
|
||||
<input
|
||||
id="environment-name" type="text" name="name"
|
||||
ref={inputRef}
|
||||
className="block textbox mt-2 w-full"
|
||||
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.name || ''}
|
||||
/>
|
||||
{formik.touched.name && formik.errors.name ? (
|
||||
<div className="text-red-500">{formik.errors.name}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateEnvironment;
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import React from 'react';
|
||||
import Portal from "components/Portal/index";
|
||||
import Modal from "components/Modal/index";
|
||||
// import { deleteWorkspace } from 'providers/ReduxStore/slices/workspaces';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const DeleteEnvironment = ({onClose, environment}) => {
|
||||
const dispatch = useDispatch();
|
||||
const onConfirm = () =>{
|
||||
// dispatch(deleteWorkspace({workspaceUid: workspace.uid}))
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<StyledWrapper>
|
||||
<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>
|
||||
</Portal>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export default DeleteEnvironment;
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { IconEdit, IconTrash } from "@tabler/icons";
|
||||
import RenameEnvironment from "../../RenameEnvironment";
|
||||
import DeleteEnvironment from "../../DeleteEnvironment";
|
||||
// import StyledWrapper from "./StyledWrapper";
|
||||
|
||||
const EnvironmentDetails = ({selected}) => {
|
||||
const [ openEditModal, setOpenEditModal] = useState(false);
|
||||
const [ openDeleteModal, setOpenDeleteModal] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="ml-10 flex-grow flex pt-4" style={{maxWidth: '700px'}}>
|
||||
<span>{selected.name}</span>
|
||||
<div className="flex gap-x-4 pl-4" >
|
||||
<IconEdit className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenEditModal(true)}/>
|
||||
<IconTrash className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenDeleteModal(true)}/>
|
||||
</div>
|
||||
{openEditModal && <RenameEnvironment onClose={() => setOpenEditModal(false)} environment={selected} />}
|
||||
{openDeleteModal && <DeleteEnvironment onClose={() => setOpenDeleteModal(false)} environment={selected} />}
|
||||
</div>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export default EnvironmentDetails;
|
||||
@@ -1,43 +0,0 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
margin-left: -1rem;
|
||||
margin-block: -1.5rem;
|
||||
.environments-sidebar {
|
||||
margin-bottom: 8px;
|
||||
background-color: #ffffff;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.environment-item {
|
||||
min-width: 150px;
|
||||
display: block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
padding: 8px 10px;
|
||||
color: rgb(35, 35, 35);
|
||||
border-bottom: 1px solid #eaecef;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
background-color: #f6f8fa;
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: #e1e4e8;
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
background-color: #e1e4e8;
|
||||
}
|
||||
}
|
||||
|
||||
.create-env {
|
||||
padding: 8px 10px;
|
||||
cursor: pointer;
|
||||
border-bottom: none;
|
||||
color: var(--color-text-link);
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -1,40 +0,0 @@
|
||||
import React, { useEffect, useState, forwardRef, useRef } from "react";
|
||||
import EnvironmentDetails from "./EnvironmentDetails";
|
||||
import CreateEnvironment from "../CreateEnvironment/index";
|
||||
import StyledWrapper from "./StyledWrapper";
|
||||
|
||||
const environments = [
|
||||
{name: "My env", uid: 123},
|
||||
{name: "hjdgfh dj", uid: 3876},
|
||||
{name: "hjdgfer dj", uid: 4678},
|
||||
];
|
||||
|
||||
const Layout = () => {
|
||||
const [selectedEnvironment, setSelectedEnvironment] = useState({});
|
||||
const [openCreateModal, setOpenCreateModal] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedEnvironment(environments[0]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="flex">
|
||||
<div style={{borderRight: "1px solid #ccc"}}>
|
||||
<div className="environments-sidebar">
|
||||
{environments && environments.length && environments.map((env) => (
|
||||
<div className={selectedEnvironment.uid === env.uid ? "environment-item active": "environment-item"} onClick={() => setSelectedEnvironment(env)}>
|
||||
<span>{env.name}</span>
|
||||
</div>
|
||||
))}
|
||||
<p className="create-env" onClick={() => setOpenCreateModal(true)}> + Create</p>
|
||||
</div>
|
||||
</div>
|
||||
<EnvironmentDetails selected={selectedEnvironment}/>
|
||||
</div>
|
||||
{openCreateModal && <CreateEnvironment onClose={() => setOpenCreateModal(false)}/>}
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
@@ -1,70 +0,0 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import Portal from "components/Portal/index";
|
||||
import Modal from "components/Modal/index";
|
||||
import { useFormik } from 'formik';
|
||||
// import { rename } from 'providers/ReduxStore/slices/environments';
|
||||
import * as Yup from 'yup';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
const RenameEnvironment = ({onClose, environment}) => {
|
||||
// const dispatch = useDispatch();
|
||||
const inputRef = useRef();
|
||||
const formik = useFormik({
|
||||
enableReinitialize: true,
|
||||
initialValues: {
|
||||
name: environment.name
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
name: Yup.string()
|
||||
.min(1, 'must be atleast 1 characters')
|
||||
.max(30, 'must be 30 characters or less')
|
||||
.required('name is required')
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
// dispatch(rename({name: values.name, uid: environment.uid}));
|
||||
onClose();
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if(inputRef && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, [inputRef]);
|
||||
|
||||
const onSubmit = () => {
|
||||
formik.handleSubmit();
|
||||
}
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<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">Environment Name</label>
|
||||
<input
|
||||
id="environment-name" type="text" name="name"
|
||||
ref={inputRef}
|
||||
className="block textbox mt-2 w-full"
|
||||
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.name || ''}
|
||||
/>
|
||||
{formik.touched.name && formik.errors.name ? (
|
||||
<div className="text-red-500">{formik.errors.name}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
export default RenameEnvironment;
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import Modal from "components/Modal/index";
|
||||
import React, { useState } from "react";
|
||||
import CreateEnvironment from "./CreateEnvironment";
|
||||
import Layout from "./Layout";
|
||||
|
||||
const EnvironmentSettings = ({onClose}) => {
|
||||
const environments = [
|
||||
{name: "My env", uid: 123},
|
||||
{name: "hjdgfh dj", uid: 3876},
|
||||
];
|
||||
const [openCreateModal, setOpenCreateModal] = useState(false)
|
||||
|
||||
if(!environments.length) {
|
||||
return (
|
||||
<Modal
|
||||
size="lg"
|
||||
title="Environment"
|
||||
confirmText={"Close"}
|
||||
handleConfirm={onClose}
|
||||
hideCancel={true}
|
||||
>
|
||||
<p>No environment found!</p>
|
||||
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={() => setOpenCreateModal(true)}>+ Create Environment</button>
|
||||
{openCreateModal && <CreateEnvironment onClose={() => setOpenCreateModal(false)}/>}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
size="lg"
|
||||
title="Environment"
|
||||
confirmText={"Close"}
|
||||
handleCancel={onClose}
|
||||
hideFooter={true}
|
||||
>
|
||||
<Layout />
|
||||
</Modal>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
export default EnvironmentSettings;
|
||||
@@ -1,54 +0,0 @@
|
||||
import React, { useRef, forwardRef, useState } from 'react';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import { IconAdjustmentsHorizontal, IconCaretDown } from '@tabler/icons';
|
||||
import EnvironmentSettings from "./EnvironmentSettings";
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const EnvironmentSelector = () => {
|
||||
const dropdownTippyRef = useRef();
|
||||
const [openSettingsModal, setOpenSettingsModal] = useState(false);
|
||||
|
||||
const Icon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="current-enviroment flex items-center justify-center pl-3 pr-2 py-1 select-none">
|
||||
No Environment
|
||||
<IconCaretDown className="caret" size={14} strokeWidth={2}/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const onDropdownCreate = (ref) => dropdownTippyRef.current = ref;
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="flex items-center cursor-pointer environment-selector">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement='bottom-end'>
|
||||
<div className="dropdown-item" onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
}}>
|
||||
<span>QA1</span>
|
||||
</div>
|
||||
<div className="dropdown-item" onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
}}>
|
||||
<span>STG</span>
|
||||
</div>
|
||||
<div className="dropdown-item" onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
}}>
|
||||
<span>No Environment</span>
|
||||
</div>
|
||||
<div className="dropdown-item" style={{borderTop: 'solid 1px #e7e7e7'}} onClick={() => setOpenSettingsModal(true)}>
|
||||
<div className="pr-2 text-gray-600">
|
||||
<IconAdjustmentsHorizontal size={18} strokeWidth={1.5}/>
|
||||
</div>
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
{openSettingsModal && <EnvironmentSettings onClose={() => setOpenSettingsModal(false)}/>}
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnvironmentSelector;
|
||||
@@ -0,0 +1,81 @@
|
||||
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 } from '@tabler/icons';
|
||||
import EnvironmentSettings from '../EnvironmentSettings';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const EnvironmentSelector = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const dropdownTippyRef = useRef();
|
||||
const [openSettingsModal, setOpenSettingsModal] = useState(false);
|
||||
const { environments, activeEnvironmentUid } = collection;
|
||||
const activeEnvironment = activeEnvironmentUid ? find(environments, (e) => e.uid === activeEnvironmentUid) : null;
|
||||
|
||||
const Icon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="current-enviroment flex items-center justify-center pl-3 pr-2 py-1 select-none">
|
||||
{activeEnvironment ? activeEnvironment.name : 'No Environment'}
|
||||
<IconCaretDown className="caret" size={14} strokeWidth={2} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||
|
||||
const onSelect = (environment) => {
|
||||
dispatch(selectEnvironment(environment ? environment.uid : null, collection.uid))
|
||||
.then(() => {
|
||||
if (environment) {
|
||||
toast.success(`Environment changed to ${environment.name}`);
|
||||
} else {
|
||||
toast.success(`No Environments are active now`);
|
||||
}
|
||||
})
|
||||
.catch((err) => console.log(err) && toast.error('An error occured while selecting the environment'));
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="flex items-center cursor-pointer environment-selector">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
||||
{environments && environments.length
|
||||
? environments.map((e) => (
|
||||
<div
|
||||
className="dropdown-item"
|
||||
key={e.uid}
|
||||
onClick={() => {
|
||||
onSelect(e);
|
||||
dropdownTippyRef.current.hide();
|
||||
}}
|
||||
>
|
||||
<IconDatabase size={18} strokeWidth={1.5} /> <span className="ml-2">{e.name}</span>
|
||||
</div>
|
||||
))
|
||||
: null}
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onSelect(null);
|
||||
}}
|
||||
>
|
||||
<span>No Environment</span>
|
||||
</div>
|
||||
<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>Settings</span>
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
{openSettingsModal && <EnvironmentSettings collection={collection} onClose={() => setOpenSettingsModal(false)} />}
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnvironmentSelector;
|
||||
@@ -0,0 +1,70 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import Portal from 'components/Portal/index';
|
||||
import Modal from 'components/Modal/index';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useFormik } from 'formik';
|
||||
import { addEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import * as Yup from 'yup';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
const CreateEnvironment = ({ collection, onClose }) => {
|
||||
const dispatch = useDispatch();
|
||||
const inputRef = useRef();
|
||||
const formik = useFormik({
|
||||
enableReinitialize: true,
|
||||
initialValues: {
|
||||
name: ''
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
name: Yup.string().min(1, 'must be atleast 1 characters').max(50, 'must be 50 characters or less').required('name is required')
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
dispatch(addEnvironment(values.name, collection.uid))
|
||||
.then(() => {
|
||||
toast.success('Environment created in collection');
|
||||
onClose();
|
||||
})
|
||||
.catch(() => toast.error('An error occured while created the environment'));
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (inputRef && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, [inputRef]);
|
||||
|
||||
const onSubmit = () => {
|
||||
formik.handleSubmit();
|
||||
};
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<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">
|
||||
Environment Name
|
||||
</label>
|
||||
<input
|
||||
id="environment-name"
|
||||
type="text"
|
||||
name="name"
|
||||
ref={inputRef}
|
||||
className="block textbox mt-2 w-full"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.name || ''}
|
||||
/>
|
||||
{formik.touched.name && formik.errors.name ? <div className="text-red-500">{formik.errors.name}</div> : null}
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateEnvironment;
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import Portal from 'components/Portal/index';
|
||||
import toast from 'react-hot-toast';
|
||||
import Modal from 'components/Modal/index';
|
||||
import { deleteEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const DeleteEnvironment = ({ onClose, environment, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const onConfirm = () => {
|
||||
dispatch(deleteEnvironment(environment.uid, collection.uid))
|
||||
.then(() => {
|
||||
toast.success('Environment deleted successfully');
|
||||
onClose();
|
||||
})
|
||||
.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}>
|
||||
Are you sure you want to delete <span className="font-semibold">{environment.name}</span> ?
|
||||
</Modal>
|
||||
</StyledWrapper>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteEnvironment;
|
||||
@@ -0,0 +1,46 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-weight: 600;
|
||||
|
||||
thead,
|
||||
td {
|
||||
border: 1px solid #efefef;
|
||||
}
|
||||
|
||||
thead {
|
||||
color: #616161;
|
||||
font-size: 0.8125rem;
|
||||
user-select: none;
|
||||
}
|
||||
td {
|
||||
padding: 6px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-add-param {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
width: 100%;
|
||||
border: solid 1px transparent;
|
||||
outline: none !important;
|
||||
|
||||
&:focus {
|
||||
outline: none !important;
|
||||
border: solid 1px transparent;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@@ -0,0 +1,129 @@
|
||||
import React, { useReducer } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { IconTrash } from '@tabler/icons';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import reducer from './reducer';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const EnvironmentVariables = ({ environment, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const [state, reducerDispatch] = useReducer(reducer, { hasChanges: false, variables: environment.variables || [] });
|
||||
const { variables, hasChanges } = state;
|
||||
|
||||
const saveChanges = () => {
|
||||
dispatch(saveEnvironment(cloneDeep(variables), environment.uid, collection.uid))
|
||||
.then(() => {
|
||||
toast.success('Changes saved successfully');
|
||||
reducerDispatch({
|
||||
type: 'CHANGES_SAVED'
|
||||
});
|
||||
})
|
||||
.catch(() => toast.error('An error occured while saving the changes'));
|
||||
};
|
||||
|
||||
const addVariable = () => {
|
||||
reducerDispatch({
|
||||
type: 'ADD_VAR'
|
||||
});
|
||||
};
|
||||
|
||||
const handleVarChange = (e, _variable, type) => {
|
||||
const variable = cloneDeep(_variable);
|
||||
switch (type) {
|
||||
case 'name': {
|
||||
variable.name = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'value': {
|
||||
variable.value = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'enabled': {
|
||||
variable.enabled = e.target.checked;
|
||||
break;
|
||||
}
|
||||
}
|
||||
reducerDispatch({
|
||||
type: 'UPDATE_VAR',
|
||||
variable
|
||||
});
|
||||
};
|
||||
|
||||
const handleRemoveVars = (variable) => {
|
||||
reducerDispatch({
|
||||
type: 'DELETE_VAR',
|
||||
variable
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full mt-6 mb-6">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>Value</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{variables && variables.length
|
||||
? variables.map((variable, index) => {
|
||||
return (
|
||||
<tr key={variable.uid}>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={variable.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleVarChange(e, variable, 'name')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={variable.value}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleVarChange(e, variable, 'value')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<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>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div>
|
||||
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={addVariable}>
|
||||
+ Add Variable
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="submit" className="submit btn btn-md btn-secondary mt-2" disabled={!hasChanges} onClick={saveChanges}>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
export default EnvironmentVariables;
|
||||
@@ -0,0 +1,50 @@
|
||||
import produce from 'immer';
|
||||
import find from 'lodash/find';
|
||||
import filter from 'lodash/filter';
|
||||
import { uuid } from 'utils/common';
|
||||
|
||||
const reducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case 'ADD_VAR': {
|
||||
return produce(state, (draft) => {
|
||||
draft.variables.push({
|
||||
uid: uuid(),
|
||||
name: '',
|
||||
value: '',
|
||||
type: 'text',
|
||||
enabled: true
|
||||
});
|
||||
draft.hasChanges = true;
|
||||
});
|
||||
}
|
||||
|
||||
case 'UPDATE_VAR': {
|
||||
return produce(state, (draft) => {
|
||||
const variable = find(draft.variables, (v) => v.uid === action.variable.uid);
|
||||
variable.name = action.variable.name;
|
||||
variable.value = action.variable.value;
|
||||
variable.enabled = action.variable.enabled;
|
||||
draft.hasChanges = true;
|
||||
});
|
||||
}
|
||||
|
||||
case 'DELETE_VAR': {
|
||||
return produce(state, (draft) => {
|
||||
draft.variables = filter(draft.variables, (v) => v.uid !== action.variable.uid);
|
||||
draft.hasChanges = true;
|
||||
});
|
||||
}
|
||||
|
||||
case 'CHANGES_SAVED': {
|
||||
return produce(state, (draft) => {
|
||||
draft.hasChanges = false;
|
||||
});
|
||||
}
|
||||
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default reducer;
|
||||
@@ -0,0 +1,34 @@
|
||||
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);
|
||||
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} />}
|
||||
<div className="flex">
|
||||
<div className="flex flex-grow items-center">
|
||||
<IconDatabase className="cursor-pointer" size={20} strokeWidth={1.5} />
|
||||
<span className="ml-1 font-semibold">{environment.name}</span>
|
||||
</div>
|
||||
<div className="flex gap-x-4 pl-4">
|
||||
<IconEdit className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenEditModal(true)} />
|
||||
<IconTrash className="cursor-pointer" size={20} strokeWidth={1.5} onClick={() => setOpenDeleteModal(true)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<EnvironmentVariables key={environment.uid} environment={environment} collection={collection} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnvironmentDetails;
|
||||
@@ -0,0 +1,49 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
margin-inline: -1rem;
|
||||
margin-block: -1.5rem;
|
||||
|
||||
.environments-sidebar {
|
||||
background-color: #eaeaea;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.environment-item {
|
||||
min-width: 150px;
|
||||
display: block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
padding: 8px 10px;
|
||||
border-left: solid 2px transparent;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
background-color: #e4e4e4;
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: #dcdcdc !important;
|
||||
border-left: solid 2px var(--color-brand);
|
||||
&:hover {
|
||||
background-color: #dcdcdc !important;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-create-environment {
|
||||
padding: 8px 10px;
|
||||
cursor: pointer;
|
||||
border-bottom: none;
|
||||
color: var(--color-text-link);
|
||||
|
||||
&:hover {
|
||||
span {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -0,0 +1,43 @@
|
||||
import React, { useEffect, useState, forwardRef, useRef } from 'react';
|
||||
import EnvironmentDetails from './EnvironmentDetails';
|
||||
import CreateEnvironment from '../CreateEnvironment/index';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const EnvironmentList = ({ collection }) => {
|
||||
const { environments } = collection;
|
||||
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
|
||||
const [openCreateModal, setOpenCreateModal] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedEnvironment(environments[0]);
|
||||
}, []);
|
||||
|
||||
if (!selectedEnvironment) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
{openCreateModal && <CreateEnvironment collection={collection} onClose={() => setOpenCreateModal(false)} />}
|
||||
<div className="flex">
|
||||
<div>
|
||||
<div className="environments-sidebar">
|
||||
{environments &&
|
||||
environments.length &&
|
||||
environments.map((env) => (
|
||||
<div key={env.uid} className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'} onClick={() => setSelectedEnvironment(env)}>
|
||||
<span>{env.name}</span>
|
||||
</div>
|
||||
))}
|
||||
<div className="btn-create-environment" onClick={() => setOpenCreateModal(true)}>
|
||||
+ <span>Create</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<EnvironmentDetails environment={selectedEnvironment} collection={collection} />
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnvironmentList;
|
||||
@@ -0,0 +1,70 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import Portal from 'components/Portal/index';
|
||||
import Modal from 'components/Modal/index';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useFormik } from 'formik';
|
||||
import { renameEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import * as Yup from 'yup';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
const RenameEnvironment = ({ onClose, environment, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const inputRef = useRef();
|
||||
const formik = useFormik({
|
||||
enableReinitialize: true,
|
||||
initialValues: {
|
||||
name: environment.name
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
name: Yup.string().min(1, 'must be atleast 1 characters').max(50, 'must be 50 characters or less').required('name is required')
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
dispatch(renameEnvironment(values.name, environment.uid, collection.uid))
|
||||
.then(() => {
|
||||
toast.success('Environment renamed successfully');
|
||||
onClose();
|
||||
})
|
||||
.catch(() => toast.error('An error occured while renaming the environment'));
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (inputRef && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, [inputRef]);
|
||||
|
||||
const onSubmit = () => {
|
||||
formik.handleSubmit();
|
||||
};
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<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">
|
||||
Environment Name
|
||||
</label>
|
||||
<input
|
||||
id="environment-name"
|
||||
type="text"
|
||||
name="name"
|
||||
ref={inputRef}
|
||||
className="block textbox mt-2 w-full"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.name || ''}
|
||||
/>
|
||||
{formik.touched.name && formik.errors.name ? <div className="text-red-500">{formik.errors.name}</div> : null}
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
|
||||
export default RenameEnvironment;
|
||||
@@ -0,0 +1,13 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
button.btn-create-environment {
|
||||
&:hover {
|
||||
span {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -0,0 +1,34 @@
|
||||
import Modal from 'components/Modal/index';
|
||||
import React, { useState } from 'react';
|
||||
import CreateEnvironment from './CreateEnvironment';
|
||||
import EnvironmentList from './EnvironmentList';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const EnvironmentSettings = ({ collection, onClose }) => {
|
||||
const { environments } = collection;
|
||||
const [openCreateModal, setOpenCreateModal] = useState(false);
|
||||
|
||||
if (!environments || !environments.length) {
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<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)}>
|
||||
+ <span>Create Environment</span>
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
</StyledWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal size="lg" title="Environments" handleCancel={onClose} hideFooter={true}>
|
||||
<EnvironmentList collection={collection} />
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnvironmentSettings;
|
||||
@@ -1,11 +1,11 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
&.modal--animate-out{
|
||||
animation: fade-out 0.5s forwards cubic-bezier(.19,1,.22,1);
|
||||
&.modal--animate-out {
|
||||
animation: fade-out 0.5s forwards cubic-bezier(0.19, 1, 0.22, 1);
|
||||
|
||||
.bruno-modal-card {
|
||||
animation: fade-and-slide-out-from-top .50s forwards cubic-bezier(.19,1,.22,1);
|
||||
animation: fade-and-slide-out-from-top 0.5s forwards cubic-bezier(0.19, 1, 0.22, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ const Wrapper = styled.div`
|
||||
}
|
||||
|
||||
.bruno-modal-card {
|
||||
animation-duration: .85s;
|
||||
animation-delay: .1s;
|
||||
animation-duration: 0.85s;
|
||||
animation-delay: 0.1s;
|
||||
background: var(--color-background-top);
|
||||
border-radius: var(--border-radius);
|
||||
position: relative;
|
||||
@@ -33,28 +33,32 @@ const Wrapper = styled.div`
|
||||
box-shadow: var(--box-shadow-base);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
will-change: opacity,transform;
|
||||
will-change: opacity, transform;
|
||||
flex-grow: 0;
|
||||
margin: 3vh 10vw;
|
||||
margin-top: 50px;
|
||||
|
||||
&.modal-sm {
|
||||
min-width: 300px;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
&.modal-md {
|
||||
min-width: 500px;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
&.modal-lg {
|
||||
min-width: 800px;
|
||||
max-width: 1140px;
|
||||
}
|
||||
|
||||
&.modal-xl {
|
||||
min-width: 1140px;
|
||||
max-width: calc(100% - 30px);
|
||||
}
|
||||
|
||||
animation: fade-and-slide-in-from-top .50s forwards cubic-bezier(.19,1,.22,1);
|
||||
animation: fade-and-slide-in-from-top 0.5s forwards cubic-bezier(0.19, 1, 0.22, 1);
|
||||
}
|
||||
|
||||
.bruno-modal-header {
|
||||
@@ -98,18 +102,18 @@ const Wrapper = styled.div`
|
||||
will-change: opacity;
|
||||
background: transparent;
|
||||
|
||||
&:before{
|
||||
content: "";
|
||||
&:before {
|
||||
content: '';
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
opacity: .4;
|
||||
opacity: 0.4;
|
||||
top: 0;
|
||||
background: black;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
animation: fade-in .1s forwards cubic-bezier(.19,1,.22,1);
|
||||
animation: fade-in 0.1s forwards cubic-bezier(0.19, 1, 0.22, 1);
|
||||
}
|
||||
|
||||
.bruno-modal-footer {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, {useState, useEffect} from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const ModalHeader = ({title, handleCancel}) => (
|
||||
const ModalHeader = ({ title, handleCancel }) => (
|
||||
<div className="bruno-modal-header">
|
||||
{title ? <div className="bruno-modal-heade-title">{title}</div> : null}
|
||||
{handleCancel ? (
|
||||
@@ -12,13 +12,9 @@ const ModalHeader = ({title, handleCancel}) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const ModalContent = ({children}) => (
|
||||
<div className="bruno-modal-content px-4 py-6">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
const ModalContent = ({ children }) => <div className="bruno-modal-content px-4 py-6">{children}</div>;
|
||||
|
||||
const ModalFooter = ({confirmText, cancelText, handleSubmit, handleCancel, confirmDisabled, hideCancel, hideFooter}) => {
|
||||
const ModalFooter = ({ confirmText, cancelText, handleSubmit, handleCancel, confirmDisabled, hideCancel, hideFooter }) => {
|
||||
confirmText = confirmText || 'Save';
|
||||
cancelText = cancelText || 'Cancel';
|
||||
|
||||
@@ -28,32 +24,21 @@ const ModalFooter = ({confirmText, cancelText, handleSubmit, handleCancel, confi
|
||||
|
||||
return (
|
||||
<div className="flex justify-end p-4 bruno-modal-footer">
|
||||
<span className={hideCancel ? "hidden" : "mr-2"}>
|
||||
<span className={hideCancel ? 'hidden' : 'mr-2'}>
|
||||
<button type="button" onClick={handleCancel} className="btn btn-md btn-close">
|
||||
{cancelText}
|
||||
</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>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const Modal = ({
|
||||
size,
|
||||
title,
|
||||
confirmText,
|
||||
cancelText,
|
||||
handleCancel,
|
||||
handleConfirm,
|
||||
children,
|
||||
confirmDisabled,
|
||||
hideCancel,
|
||||
hideFooter
|
||||
}) => {
|
||||
const Modal = ({ size, title, confirmText, cancelText, handleCancel, handleConfirm, children, confirmDisabled, hideCancel, hideFooter }) => {
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
const escFunction = (event) => {
|
||||
const escKeyCode = 27;
|
||||
@@ -65,14 +50,14 @@ const Modal = ({
|
||||
const closeModal = () => {
|
||||
setIsClosing(true);
|
||||
setTimeout(() => handleCancel(), 500);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('keydown', escFunction, false);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', escFunction, false);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
let classes = 'bruno-modal';
|
||||
@@ -84,16 +69,18 @@ const Modal = ({
|
||||
<div className={`bruno-modal-card modal-${size}`}>
|
||||
<ModalHeader title={title} handleCancel={() => closeModal()} />
|
||||
<ModalContent>{children}</ModalContent>
|
||||
<ModalFooter
|
||||
<ModalFooter
|
||||
confirmText={confirmText}
|
||||
cancelText={cancelText}
|
||||
handleCancel={() => closeModal()}
|
||||
handleSubmit={handleConfirm}
|
||||
handleCancel={() => closeModal()}
|
||||
handleSubmit={handleConfirm}
|
||||
confirmDisabled={confirmDisabled}
|
||||
hideCancel={hideCancel}
|
||||
hideFooter={hideFooter}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Clicking on backdrop closes the modal */}
|
||||
<div className="bruno-modal-backdrop" onClick={() => closeModal()} />
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
@@ -14,7 +14,6 @@ const StyledWrapper = styled.div`
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState, forwardRef, useRef } from 'react';
|
||||
import Dropdown from '../Dropdown';
|
||||
import { faCaretDown } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCaretDown } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { IconBox, IconSearch, IconDots } from '@tabler/icons';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
@@ -9,11 +9,11 @@ const Navbar = () => {
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
|
||||
const menuDropdownTippyRef = useRef();
|
||||
const onMenuDropdownCreate = (ref) => menuDropdownTippyRef.current = ref;
|
||||
const onMenuDropdownCreate = (ref) => (menuDropdownTippyRef.current = ref);
|
||||
const MenuIcon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="dropdown-icon cursor-pointer">
|
||||
<IconDots size={22}/>
|
||||
<IconDots size={22} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -25,27 +25,36 @@ const Navbar = () => {
|
||||
{/* <FontAwesomeIcon className="ml-2" icon={faCaretDown} style={{fontSize: 13}}/> */}
|
||||
</div>
|
||||
<div className="collection-dropdown flex flex-grow items-center justify-end">
|
||||
<Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement='bottom-start'>
|
||||
<div className="dropdown-item" onClick={(e) => {
|
||||
menuDropdownTippyRef.current.hide();
|
||||
setModalOpen(true);
|
||||
}}>
|
||||
<Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement="bottom-start">
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
menuDropdownTippyRef.current.hide();
|
||||
setModalOpen(true);
|
||||
}}
|
||||
>
|
||||
Create Collection
|
||||
</div>
|
||||
<div className="dropdown-item" onClick={(e) => {
|
||||
menuDropdownTippyRef.current.hide();
|
||||
}}>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
menuDropdownTippyRef.current.hide();
|
||||
}}
|
||||
>
|
||||
Import Collection
|
||||
</div>
|
||||
<div className="dropdown-item" onClick={(e) => {
|
||||
menuDropdownTippyRef.current.hide();
|
||||
}}>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
menuDropdownTippyRef.current.hide();
|
||||
}}
|
||||
>
|
||||
Settings
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default Navbar;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
function Portal({ children, wrapperId }) {
|
||||
wrapperId = wrapperId || "bruno-app-body";
|
||||
wrapperId = wrapperId || 'bruno-app-body';
|
||||
|
||||
return createPortal(children, document.getElementById(wrapperId));
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ const Wrapper = styled.div`
|
||||
border-collapse: collapse;
|
||||
font-weight: 600;
|
||||
|
||||
thead, td {
|
||||
thead,
|
||||
td {
|
||||
border: 1px solid #efefef;
|
||||
}
|
||||
|
||||
@@ -24,18 +25,18 @@ const Wrapper = styled.div`
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
input[type='text'] {
|
||||
width: 100%;
|
||||
border: solid 1px transparent;
|
||||
outline: none !important;
|
||||
|
||||
&:focus{
|
||||
&:focus {
|
||||
outline: none !important;
|
||||
border: solid 1px transparent;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
input[type='checkbox'] {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
|
||||
@@ -6,52 +6,58 @@ import { useDispatch } from 'react-redux';
|
||||
import { addFormUrlEncodedParam, updateFormUrlEncodedParam, deleteFormUrlEncodedParam } from 'providers/ReduxStore/slices/collections';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const FormUrlEncodedParams = ({item, collection}) => {
|
||||
const FormUrlEncodedParams = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const params = item.draft ? get(item, 'draft.request.body.formUrlEncoded') : get(item, 'request.body.formUrlEncoded');
|
||||
|
||||
const addParam = () => {
|
||||
dispatch(addFormUrlEncodedParam({
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid,
|
||||
}));
|
||||
dispatch(
|
||||
addFormUrlEncodedParam({
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleParamChange = (e, _param, type) => {
|
||||
const param = cloneDeep(_param);
|
||||
switch(type) {
|
||||
case 'name' : {
|
||||
switch (type) {
|
||||
case 'name': {
|
||||
param.name = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'value' : {
|
||||
case 'value': {
|
||||
param.value = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'description' : {
|
||||
case 'description': {
|
||||
param.description = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'enabled' : {
|
||||
case 'enabled': {
|
||||
param.enabled = e.target.checked;
|
||||
break;
|
||||
}
|
||||
}
|
||||
dispatch(updateFormUrlEncodedParam({
|
||||
param: param,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
}));
|
||||
dispatch(
|
||||
updateFormUrlEncodedParam({
|
||||
param: param,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const handleRemoveParams = (param) => {
|
||||
dispatch(deleteFormUrlEncodedParam({
|
||||
paramUid: param.uid,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
}));
|
||||
dispatch(
|
||||
deleteFormUrlEncodedParam({
|
||||
paramUid: param.uid,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<table>
|
||||
@@ -64,56 +70,64 @@ const FormUrlEncodedParams = ({item, collection}) => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{params && params.length ? params.map((param, index) => {
|
||||
return (
|
||||
<tr key={param.uid}>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false"
|
||||
value={param.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'name')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false"
|
||||
value={param.value}
|
||||
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}
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'enabled')}
|
||||
/>
|
||||
<button onClick={() => handleRemoveParams(param)}>
|
||||
<IconTrash strokeWidth={1.5} size={20}/>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}) : null}
|
||||
{params && params.length
|
||||
? params.map((param, index) => {
|
||||
return (
|
||||
<tr key={param.uid}>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={param.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'name')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={param.value}
|
||||
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} className="mr-3 mousetrap" onChange={(e) => handleParamChange(e, param, 'enabled')} />
|
||||
<button onClick={() => handleRemoveParams(param)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={addParam}>+ Add Param</button>
|
||||
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={addParam}>
|
||||
+ Add Param
|
||||
</button>
|
||||
</StyledWrapper>
|
||||
)
|
||||
);
|
||||
};
|
||||
export default FormUrlEncodedParams;
|
||||
export default FormUrlEncodedParams;
|
||||
|
||||
@@ -18,7 +18,11 @@ const StyledWrapper = styled.div`
|
||||
color: rgb(125 125 125);
|
||||
outline: none !important;
|
||||
|
||||
&:focus, &:active, &:focus-within, &:focus-visible, &:target {
|
||||
&:focus,
|
||||
&:active,
|
||||
&:focus-within,
|
||||
&:focus-visible,
|
||||
&:target {
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
@@ -38,7 +42,11 @@ const StyledWrapper = styled.div`
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
|
||||
&:focus, &:active, &:focus-within, &:focus-visible, &:target {
|
||||
&:focus,
|
||||
&:active,
|
||||
&:focus-within,
|
||||
&:focus-visible,
|
||||
&:target {
|
||||
border: none;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
@@ -49,7 +57,6 @@ const StyledWrapper = styled.div`
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -4,23 +4,17 @@ import QueryEditor from 'components/RequestPane/QueryEditor';
|
||||
import RequestHeaders from 'components/RequestPane/RequestHeaders';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const GraphQLRequestPane = ({onRunQuery, schema, leftPaneWidth, value, onQueryChange}) => {
|
||||
const GraphQLRequestPane = ({ onRunQuery, schema, leftPaneWidth, value, onQueryChange }) => {
|
||||
return (
|
||||
<StyledWrapper className="h-full">
|
||||
<Tabs className='react-tabs mt-1 flex flex-grow flex-col h-full' forceRenderTabPanel>
|
||||
<Tabs className="react-tabs mt-1 flex flex-grow flex-col h-full" forceRenderTabPanel>
|
||||
<TabList>
|
||||
<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}
|
||||
/>
|
||||
<QueryEditor schema={schema} width={leftPaneWidth} value={value} onRunQuery={onRunQuery} onEdit={onQueryChange} />
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
@@ -28,7 +22,7 @@ const GraphQLRequestPane = ({onRunQuery, schema, leftPaneWidth, value, onQueryCh
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</StyledWrapper>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default GraphQLRequestPane;
|
||||
|
||||
@@ -10,7 +10,11 @@ const StyledWrapper = styled.div`
|
||||
color: var(--color-tab-inactive);
|
||||
cursor: pointer;
|
||||
|
||||
&:focus, &:active, &:focus-within, &:focus-visible, &:target {
|
||||
&:focus,
|
||||
&:active,
|
||||
&:focus-within,
|
||||
&:focus-visible,
|
||||
&:target {
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
@@ -23,4 +27,4 @@ const StyledWrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -7,86 +7,77 @@ import QueryParams from 'components/RequestPane/QueryParams';
|
||||
import RequestHeaders from 'components/RequestPane/RequestHeaders';
|
||||
import RequestBody from 'components/RequestPane/RequestBody';
|
||||
import RequestBodyMode from 'components/RequestPane/RequestBody/RequestBodyMode';
|
||||
import QueryUrl from 'components/RequestPane/QueryUrl';
|
||||
import { sendRequest } from 'providers/ReduxStore/slices/collections';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const HttpRequestPane = ({item, collection, leftPaneWidth}) => {
|
||||
const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
|
||||
const dispatch = useDispatch();
|
||||
const tabs = useSelector((state) => state.tabs.tabs);
|
||||
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
|
||||
|
||||
const sendNetworkRequest = async () => dispatch(sendRequest(item, collection.uid));
|
||||
|
||||
const selectTab = (tab) => {
|
||||
dispatch(updateRequestPaneTab({
|
||||
uid: item.uid,
|
||||
requestPaneTab: tab
|
||||
}))
|
||||
dispatch(
|
||||
updateRequestPaneTab({
|
||||
uid: item.uid,
|
||||
requestPaneTab: tab
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const getTabPanel = (tab) => {
|
||||
switch(tab) {
|
||||
switch (tab) {
|
||||
case 'params': {
|
||||
return <QueryParams item={item} collection={collection}/>;
|
||||
return <QueryParams item={item} collection={collection} />;
|
||||
}
|
||||
case 'body': {
|
||||
return <RequestBody item={item} collection={collection}/>;
|
||||
return <RequestBody item={item} collection={collection} />;
|
||||
}
|
||||
case 'headers': {
|
||||
return <RequestHeaders item={item} collection={collection}/>;
|
||||
return <RequestHeaders item={item} collection={collection} />;
|
||||
}
|
||||
default: {
|
||||
return <div className="mt-4">404 | Not found</div>;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if(!activeTabUid) {
|
||||
return (
|
||||
<div>Something went wrong</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 occured!</div>
|
||||
);
|
||||
if (!focusedTab || !focusedTab.uid || !focusedTab.requestPaneTab) {
|
||||
return <div className="pb-4 px-4">An error occured!</div>;
|
||||
}
|
||||
|
||||
const getTabClassname = (tabName) => {
|
||||
return classnames(`tab select-none ${tabName}`, {
|
||||
'active': tabName === focusedTab.requestPaneTab
|
||||
active: tabName === focusedTab.requestPaneTab
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="flex flex-col h-full relativ">
|
||||
<div className="pt-2 pb-3">
|
||||
<QueryUrl
|
||||
item = {item}
|
||||
collection={collection}
|
||||
handleRun={sendNetworkRequest}
|
||||
/>
|
||||
</div>
|
||||
<StyledWrapper className="flex flex-col h-full relative">
|
||||
<div className="flex items-center tabs" role="tablist">
|
||||
<div className={getTabClassname('params')} role="tab" onClick={() => selectTab('params')}>Params</div>
|
||||
<div className={getTabClassname('body')} role="tab" onClick={() => selectTab('body')}>Body</div>
|
||||
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>Headers</div>
|
||||
<div className={getTabClassname('params')} role="tab" onClick={() => selectTab('params')}>
|
||||
Params
|
||||
</div>
|
||||
<div className={getTabClassname('body')} role="tab" onClick={() => selectTab('body')}>
|
||||
Body
|
||||
</div>
|
||||
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
|
||||
Headers
|
||||
</div>
|
||||
{/* Moved to post mvp */}
|
||||
{/* <div className={getTabClassname('auth')} role="tab" onClick={() => selectTab('auth')}>Auth</div> */}
|
||||
{focusedTab.requestPaneTab === 'body' ? (
|
||||
<div className="flex flex-grow justify-end items-center">
|
||||
<RequestBodyMode item={item} collection={collection}/>
|
||||
<RequestBodyMode item={item} collection={collection} />
|
||||
</div>
|
||||
) : null }
|
||||
) : null}
|
||||
</div>
|
||||
<section className="flex w-full mt-5">
|
||||
{getTabPanel(focusedTab.requestPaneTab)}
|
||||
</section>
|
||||
<section className="flex w-full mt-5">{getTabPanel(focusedTab.requestPaneTab)}</section>
|
||||
</StyledWrapper>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default HttpRequestPane;
|
||||
|
||||
@@ -6,7 +6,8 @@ const Wrapper = styled.div`
|
||||
border-collapse: collapse;
|
||||
font-weight: 600;
|
||||
|
||||
thead, td {
|
||||
thead,
|
||||
td {
|
||||
border: 1px solid #efefef;
|
||||
}
|
||||
|
||||
@@ -24,18 +25,18 @@ const Wrapper = styled.div`
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
input[type='text'] {
|
||||
width: 100%;
|
||||
border: solid 1px transparent;
|
||||
outline: none !important;
|
||||
|
||||
&:focus{
|
||||
&:focus {
|
||||
outline: none !important;
|
||||
border: solid 1px transparent;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
input[type='checkbox'] {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
|
||||
@@ -6,52 +6,58 @@ import { useDispatch } from 'react-redux';
|
||||
import { addMultipartFormParam, updateMultipartFormParam, deleteMultipartFormParam } from 'providers/ReduxStore/slices/collections';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const MultipartFormParams = ({item, collection}) => {
|
||||
const MultipartFormParams = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const params = item.draft ? get(item, 'draft.request.body.multipartForm') : get(item, 'request.body.multipartForm');
|
||||
|
||||
const addParam = () => {
|
||||
dispatch(addMultipartFormParam({
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid,
|
||||
}));
|
||||
dispatch(
|
||||
addMultipartFormParam({
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleParamChange = (e, _param, type) => {
|
||||
const param = cloneDeep(_param);
|
||||
switch(type) {
|
||||
case 'name' : {
|
||||
switch (type) {
|
||||
case 'name': {
|
||||
param.name = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'value' : {
|
||||
case 'value': {
|
||||
param.value = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'description' : {
|
||||
case 'description': {
|
||||
param.description = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'enabled' : {
|
||||
case 'enabled': {
|
||||
param.enabled = e.target.checked;
|
||||
break;
|
||||
}
|
||||
}
|
||||
dispatch(updateMultipartFormParam({
|
||||
param: param,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
}));
|
||||
dispatch(
|
||||
updateMultipartFormParam({
|
||||
param: param,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const handleRemoveParams = (param) => {
|
||||
dispatch(deleteMultipartFormParam({
|
||||
paramUid: param.uid,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
}));
|
||||
dispatch(
|
||||
deleteMultipartFormParam({
|
||||
paramUid: param.uid,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<table>
|
||||
@@ -64,56 +70,64 @@ const MultipartFormParams = ({item, collection}) => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{params && params.length ? params.map((param, index) => {
|
||||
return (
|
||||
<tr key={param.uid}>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false"
|
||||
value={param.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'name')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false"
|
||||
value={param.value}
|
||||
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}
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'enabled')}
|
||||
/>
|
||||
<button onClick={() => handleRemoveParams(param)}>
|
||||
<IconTrash strokeWidth={1.5} size={20}/>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}) : null}
|
||||
{params && params.length
|
||||
? params.map((param, index) => {
|
||||
return (
|
||||
<tr key={param.uid}>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={param.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'name')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={param.value}
|
||||
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} className="mr-3 mousetrap" onChange={(e) => handleParamChange(e, param, 'enabled')} />
|
||||
<button onClick={() => handleRemoveParams(param)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={addParam}>+ Add Param</button>
|
||||
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={addParam}>
|
||||
+ Add Param
|
||||
</button>
|
||||
</StyledWrapper>
|
||||
)
|
||||
);
|
||||
};
|
||||
export default MultipartFormParams;
|
||||
export default MultipartFormParams;
|
||||
|
||||
@@ -13,4 +13,3 @@ const StyledWrapper = styled.div`
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
|
||||
@@ -44,44 +44,37 @@ export default class QueryEditor extends React.Component {
|
||||
showCursorWhenSelecting: true,
|
||||
readOnly: this.props.readOnly ? 'nocursor' : false,
|
||||
foldGutter: {
|
||||
minFoldSize: 4,
|
||||
minFoldSize: 4
|
||||
},
|
||||
lint: {
|
||||
schema: this.props.schema,
|
||||
validationRules: this.props.validationRules ?? null,
|
||||
// linting accepts string or FragmentDefinitionNode[]
|
||||
externalFragments: this.props?.externalFragments,
|
||||
externalFragments: this.props?.externalFragments
|
||||
},
|
||||
hintOptions: {
|
||||
schema: this.props.schema,
|
||||
closeOnUnfocus: false,
|
||||
completeSingle: false,
|
||||
container: this._node,
|
||||
externalFragments: this.props?.externalFragments,
|
||||
externalFragments: this.props?.externalFragments
|
||||
},
|
||||
info: {
|
||||
schema: this.props.schema,
|
||||
renderDescription: (text) => md.render(text),
|
||||
onClick: (reference) =>
|
||||
this.props.onClickReference && this.props.onClickReference(reference),
|
||||
onClick: (reference) => this.props.onClickReference && this.props.onClickReference(reference)
|
||||
},
|
||||
jump: {
|
||||
schema: this.props.schema,
|
||||
onClick: (reference) =>
|
||||
this.props.onClickReference && this.props.onClickReference(reference)
|
||||
onClick: (reference) => this.props.onClickReference && this.props.onClickReference(reference)
|
||||
},
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||
extraKeys: {
|
||||
'Cmd-Space': () =>
|
||||
editor.showHint({ completeSingle: true, container: this._node }),
|
||||
'Ctrl-Space': () =>
|
||||
editor.showHint({ completeSingle: true, container: this._node }),
|
||||
'Alt-Space': () =>
|
||||
editor.showHint({ completeSingle: true, container: this._node }),
|
||||
'Shift-Space': () =>
|
||||
editor.showHint({ completeSingle: true, container: this._node }),
|
||||
'Shift-Alt-Space': () =>
|
||||
editor.showHint({ completeSingle: true, container: this._node }),
|
||||
'Cmd-Space': () => editor.showHint({ completeSingle: true, container: this._node }),
|
||||
'Ctrl-Space': () => editor.showHint({ completeSingle: true, container: this._node }),
|
||||
'Alt-Space': () => editor.showHint({ completeSingle: true, container: this._node }),
|
||||
'Shift-Space': () => editor.showHint({ completeSingle: true, container: this._node }),
|
||||
'Shift-Alt-Space': () => editor.showHint({ completeSingle: true, container: this._node }),
|
||||
|
||||
'Cmd-Enter': () => {
|
||||
if (this.props.onRunQuery) {
|
||||
@@ -129,8 +122,8 @@ export default class QueryEditor extends React.Component {
|
||||
if (this.props.onRunQuery) {
|
||||
// empty
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}));
|
||||
if (editor) {
|
||||
editor.on('change', this._onEdit);
|
||||
@@ -152,11 +145,7 @@ export default class QueryEditor extends React.Component {
|
||||
this.editor.options.jump.schema = this.props.schema;
|
||||
CodeMirror.signal(this.editor, 'change', this.editor);
|
||||
}
|
||||
if (
|
||||
this.props.value !== prevProps.value &&
|
||||
this.props.value !== this.cachedValue &&
|
||||
this.editor
|
||||
) {
|
||||
if (this.props.value !== prevProps.value && this.props.value !== this.cachedValue && this.editor) {
|
||||
this.cachedValue = this.props.value;
|
||||
this.editor.setValue(this.props.value);
|
||||
}
|
||||
@@ -177,7 +166,7 @@ export default class QueryEditor extends React.Component {
|
||||
<StyledWrapper
|
||||
className="h-full"
|
||||
aria-label="Query Editor"
|
||||
ref={node => {
|
||||
ref={(node) => {
|
||||
this._node = node;
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -7,10 +7,7 @@
|
||||
import escapeHTML from 'escape-html';
|
||||
import MD from 'markdown-it';
|
||||
|
||||
import {
|
||||
GraphQLNonNull,
|
||||
GraphQLList
|
||||
} from 'graphql';
|
||||
import { GraphQLNonNull, GraphQLList } from 'graphql';
|
||||
|
||||
const md = new MD();
|
||||
|
||||
@@ -18,85 +15,65 @@ const md = new MD();
|
||||
* Render a custom UI for CodeMirror's hint which includes additional info
|
||||
* about the type and description for the selected context.
|
||||
*/
|
||||
export default function onHasCompletion(
|
||||
_cm,
|
||||
data,
|
||||
onHintInformationRender,
|
||||
) {
|
||||
export default function onHasCompletion(_cm, data, onHintInformationRender) {
|
||||
const CodeMirror = require('codemirror');
|
||||
|
||||
let information;
|
||||
let deprecation;
|
||||
|
||||
// When a hint result is selected, we augment the UI with information.
|
||||
CodeMirror.on(
|
||||
data,
|
||||
'select',
|
||||
(ctx, el) => {
|
||||
// Only the first time (usually when the hint UI is first displayed)
|
||||
// do we create the information nodes.
|
||||
if (!information) {
|
||||
const hintsUl = el.parentNode;
|
||||
CodeMirror.on(data, 'select', (ctx, el) => {
|
||||
// Only the first time (usually when the hint UI is first displayed)
|
||||
// do we create the information nodes.
|
||||
if (!information) {
|
||||
const hintsUl = el.parentNode;
|
||||
|
||||
// This "information" node will contain the additional info about the
|
||||
// highlighted typeahead option.
|
||||
information = document.createElement('div');
|
||||
information.className = 'CodeMirror-hint-information';
|
||||
hintsUl.appendChild(information);
|
||||
// This "information" node will contain the additional info about the
|
||||
// highlighted typeahead option.
|
||||
information = document.createElement('div');
|
||||
information.className = 'CodeMirror-hint-information';
|
||||
hintsUl.appendChild(information);
|
||||
|
||||
// This "deprecation" node will contain info about deprecated usage.
|
||||
deprecation = document.createElement('div');
|
||||
deprecation.className = 'CodeMirror-hint-deprecation';
|
||||
hintsUl.appendChild(deprecation);
|
||||
// This "deprecation" node will contain info about deprecated usage.
|
||||
deprecation = document.createElement('div');
|
||||
deprecation.className = 'CodeMirror-hint-deprecation';
|
||||
hintsUl.appendChild(deprecation);
|
||||
|
||||
// When CodeMirror attempts to remove the hint UI, we detect that it was
|
||||
// removed and in turn remove the information nodes.
|
||||
let onRemoveFn;
|
||||
hintsUl.addEventListener(
|
||||
'DOMNodeRemoved',
|
||||
(onRemoveFn = (event) => {
|
||||
if (event.target === hintsUl) {
|
||||
hintsUl.removeEventListener('DOMNodeRemoved', onRemoveFn);
|
||||
information = null;
|
||||
deprecation = null;
|
||||
onRemoveFn = null;
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
// When CodeMirror attempts to remove the hint UI, we detect that it was
|
||||
// removed and in turn remove the information nodes.
|
||||
let onRemoveFn;
|
||||
hintsUl.addEventListener(
|
||||
'DOMNodeRemoved',
|
||||
(onRemoveFn = (event) => {
|
||||
if (event.target === hintsUl) {
|
||||
hintsUl.removeEventListener('DOMNodeRemoved', onRemoveFn);
|
||||
information = null;
|
||||
deprecation = null;
|
||||
onRemoveFn = null;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Now that the UI has been set up, add info to information.
|
||||
const description = ctx.description
|
||||
? md.render(ctx.description)
|
||||
: 'Self descriptive.';
|
||||
const type = ctx.type
|
||||
? '<span className="infoType">' + renderType(ctx.type) + '</span>'
|
||||
: '';
|
||||
// Now that the UI has been set up, add info to information.
|
||||
const description = ctx.description ? md.render(ctx.description) : 'Self descriptive.';
|
||||
const type = ctx.type ? '<span className="infoType">' + renderType(ctx.type) + '</span>' : '';
|
||||
|
||||
information.innerHTML =
|
||||
'<div className="content">' +
|
||||
(description.slice(0, 3) === '<p>'
|
||||
? '<p>' + type + description.slice(3)
|
||||
: type + description) +
|
||||
'</div>';
|
||||
information.innerHTML = '<div className="content">' + (description.slice(0, 3) === '<p>' ? '<p>' + type + description.slice(3) : type + description) + '</div>';
|
||||
|
||||
if (ctx && deprecation && ctx.deprecationReason) {
|
||||
const reason = ctx.deprecationReason
|
||||
? md.render(ctx.deprecationReason)
|
||||
: '';
|
||||
deprecation.innerHTML =
|
||||
'<span className="deprecation-label">Deprecated</span>' + reason;
|
||||
deprecation.style.display = 'block';
|
||||
} else if (deprecation) {
|
||||
deprecation.style.display = 'none';
|
||||
}
|
||||
if (ctx && deprecation && ctx.deprecationReason) {
|
||||
const reason = ctx.deprecationReason ? md.render(ctx.deprecationReason) : '';
|
||||
deprecation.innerHTML = '<span className="deprecation-label">Deprecated</span>' + reason;
|
||||
deprecation.style.display = 'block';
|
||||
} else if (deprecation) {
|
||||
deprecation.style.display = 'none';
|
||||
}
|
||||
|
||||
// Additional rendering?
|
||||
if (onHintInformationRender) {
|
||||
onHintInformationRender(information);
|
||||
}
|
||||
},
|
||||
);
|
||||
// Additional rendering?
|
||||
if (onHintInformationRender) {
|
||||
onHintInformationRender(information);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderType(type) {
|
||||
|
||||
@@ -6,7 +6,8 @@ const Wrapper = styled.div`
|
||||
border-collapse: collapse;
|
||||
font-weight: 600;
|
||||
|
||||
thead, td {
|
||||
thead,
|
||||
td {
|
||||
border: 1px solid #efefef;
|
||||
}
|
||||
|
||||
@@ -27,18 +28,18 @@ const Wrapper = styled.div`
|
||||
}
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
input[type='text'] {
|
||||
width: 100%;
|
||||
border: solid 1px transparent;
|
||||
outline: none !important;
|
||||
|
||||
&:focus{
|
||||
&:focus {
|
||||
outline: none !important;
|
||||
border: solid 1px transparent;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
input[type='checkbox'] {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
|
||||
@@ -7,52 +7,58 @@ import { addQueryParam, updateQueryParam, deleteQueryParam } from 'providers/Red
|
||||
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const QueryParams = ({item, collection}) => {
|
||||
const QueryParams = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const params = item.draft ? get(item, 'draft.request.params') : get(item, 'request.params');
|
||||
|
||||
const handleAddParam = () => {
|
||||
dispatch(addQueryParam({
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid,
|
||||
}));
|
||||
dispatch(
|
||||
addQueryParam({
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleParamChange = (e, _param, type) => {
|
||||
const param = cloneDeep(_param);
|
||||
|
||||
switch(type) {
|
||||
case 'name' : {
|
||||
switch (type) {
|
||||
case 'name': {
|
||||
param.name = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'value' : {
|
||||
case 'value': {
|
||||
param.value = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'description' : {
|
||||
case 'description': {
|
||||
param.description = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'enabled' : {
|
||||
case 'enabled': {
|
||||
param.enabled = e.target.checked;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(updateQueryParam({
|
||||
param,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
}));
|
||||
dispatch(
|
||||
updateQueryParam({
|
||||
param,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const handleRemoveParam = (param) => {
|
||||
dispatch(deleteQueryParam({
|
||||
paramUid: param.uid,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
}));
|
||||
dispatch(
|
||||
deleteQueryParam({
|
||||
paramUid: param.uid,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -67,58 +73,64 @@ const QueryParams = ({item, collection}) => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{params && params.length ? params.map((param, index) => {
|
||||
return (
|
||||
<tr key={param.uid}>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false"
|
||||
value={param.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'name')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false"
|
||||
value={param.value}
|
||||
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}
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'enabled')}
|
||||
/>
|
||||
<button onClick={() => handleRemoveParam(param)}>
|
||||
<IconTrash strokeWidth={1.5} size={20}/>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}) : null}
|
||||
{params && params.length
|
||||
? params.map((param, index) => {
|
||||
return (
|
||||
<tr key={param.uid}>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={param.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'name')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={param.value}
|
||||
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} className="mr-3 mousetrap" onChange={(e) => handleParamChange(e, param, 'enabled')} />
|
||||
<button onClick={() => handleRemoveParam(param)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={handleAddParam}>
|
||||
+ <span>Add Param</span>
|
||||
</button>
|
||||
</StyledWrapper>
|
||||
)
|
||||
);
|
||||
};
|
||||
export default QueryParams;
|
||||
export default QueryParams;
|
||||
|
||||
@@ -17,7 +17,7 @@ const Wrapper = styled.div`
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: .25rem .6rem !important;
|
||||
padding: 0.25rem 0.6rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,43 +3,48 @@ import { IconCaretDown } from '@tabler/icons';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const HttpMethodSelector = ({method, onMethodSelect}) => {
|
||||
const HttpMethodSelector = ({ method, onMethodSelect }) => {
|
||||
const dropdownTippyRef = useRef();
|
||||
const onDropdownCreate = (ref) => dropdownTippyRef.current = ref;
|
||||
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||
|
||||
const Icon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="flex w-full items-center pl-3 py-1 select-none uppercase">
|
||||
<div className="flex-grow font-medium">{method}</div>
|
||||
<div><IconCaretDown className="caret ml-2 mr-2" size={14} strokeWidth={2}/></div>
|
||||
<div>
|
||||
<IconCaretDown className="caret ml-2 mr-2" size={14} strokeWidth={2} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const handleMethodSelect = (verb) => onMethodSelect(verb);
|
||||
|
||||
const Verb = ({verb}) => {
|
||||
const Verb = ({ verb }) => {
|
||||
return (
|
||||
<div className="dropdown-item" onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
handleMethodSelect(verb);
|
||||
}}>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
handleMethodSelect(verb);
|
||||
}}
|
||||
>
|
||||
{verb}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return(
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="flex items-center cursor-pointer method-selector">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement='bottom-start'>
|
||||
<Verb verb='GET' />
|
||||
<Verb verb='POST' />
|
||||
<Verb verb='PUT' />
|
||||
<Verb verb='DELETE' />
|
||||
<Verb verb='PATCH' />
|
||||
<Verb verb='OPTIONS' />
|
||||
<Verb verb='HEAD' />
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-start">
|
||||
<Verb verb="GET" />
|
||||
<Verb verb="POST" />
|
||||
<Verb verb="PUT" />
|
||||
<Verb verb="DELETE" />
|
||||
<Verb verb="PATCH" />
|
||||
<Verb verb="OPTIONS" />
|
||||
<Verb verb="HEAD" />
|
||||
</Dropdown>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
|
||||
@@ -4,46 +4,55 @@ import { useDispatch } from 'react-redux';
|
||||
import { requestUrlChanged, updateRequestMethod } from 'providers/ReduxStore/slices/collections';
|
||||
import HttpMethodSelector from './HttpMethodSelector';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import SendSvg from 'assets/send.svg';
|
||||
|
||||
const QueryUrl = ({item, collection, handleRun}) => {
|
||||
const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
const dispatch = useDispatch();
|
||||
const method = item.draft ? get(item, 'draft.request.method') : get(item, 'request.method');
|
||||
let url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
|
||||
|
||||
const onUrlChange = (value) => {
|
||||
dispatch(requestUrlChanged({
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid,
|
||||
url: value
|
||||
}));
|
||||
dispatch(
|
||||
requestUrlChanged({
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid,
|
||||
url: value
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const onMethodSelect = (verb) => {
|
||||
dispatch(updateRequestMethod({
|
||||
method: verb,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
}));
|
||||
dispatch(
|
||||
updateRequestMethod({
|
||||
method: verb,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="flex items-center">
|
||||
<div className="flex items-center h-full method-selector-container">
|
||||
<HttpMethodSelector method={method} onMethodSelect={onMethodSelect}/>
|
||||
<HttpMethodSelector method={method} onMethodSelect={onMethodSelect} />
|
||||
</div>
|
||||
<div className="flex items-center flex-grow input-container h-full">
|
||||
<input
|
||||
className="px-3 w-full mousetrap"
|
||||
type="text" value={url}
|
||||
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false"
|
||||
type="text"
|
||||
value={url}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={(event) => onUrlChange(event.target.value)}
|
||||
/>
|
||||
<div className="flex items-center h-full mr-2 cursor-pointer" onClick={handleRun}>
|
||||
<img src='/send.svg' style={{width: '22px'}}/>
|
||||
<img src={SendSvg.src} style={{ width: '22px' }} />
|
||||
</div>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default QueryUrl;
|
||||
|
||||
@@ -8,12 +8,12 @@ const Wrapper = styled.div`
|
||||
border-radius: 3px;
|
||||
|
||||
.dropdown-item {
|
||||
padding: .2rem .6rem !important;
|
||||
padding: 0.2rem 0.6rem !important;
|
||||
padding-left: 1.5rem !important;
|
||||
}
|
||||
|
||||
.label-item {
|
||||
padding: .2rem .6rem !important;
|
||||
.label-item {
|
||||
padding: 0.2rem 0.6rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,76 +7,89 @@ import { updateRequestBodyMode } from 'providers/ReduxStore/slices/collections';
|
||||
import { humanizeRequestBodyMode } from 'utils/collections';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const RequestBodyMode = ({item, collection}) => {
|
||||
const RequestBodyMode = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const dropdownTippyRef = useRef();
|
||||
const onDropdownCreate = (ref) => dropdownTippyRef.current = ref;
|
||||
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||
const bodyMode = item.draft ? get(item, 'draft.request.body.mode') : get(item, 'request.body.mode');
|
||||
|
||||
|
||||
const Icon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="flex items-center justify-center pl-3 py-1 select-none">
|
||||
{humanizeRequestBodyMode(bodyMode)} <IconCaretDown className="caret ml-2 mr-2" size={14} strokeWidth={2}/>
|
||||
{humanizeRequestBodyMode(bodyMode)} <IconCaretDown className="caret ml-2 mr-2" size={14} strokeWidth={2} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const onModeChange = (value) => {
|
||||
dispatch(updateRequestBodyMode({
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid,
|
||||
mode: value
|
||||
}));
|
||||
dispatch(
|
||||
updateRequestBodyMode({
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid,
|
||||
mode: value
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return(
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="inline-flex items-center cursor-pointer body-mode-selector">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement='bottom-end'>
|
||||
<div className="label-item font-medium">
|
||||
Form
|
||||
</div>
|
||||
<div className="dropdown-item" onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('multipartForm');
|
||||
}}>
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
||||
<div className="label-item font-medium">Form</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('multipartForm');
|
||||
}}
|
||||
>
|
||||
Multipart Form
|
||||
</div>
|
||||
<div className="dropdown-item" onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('formUrlEncoded');
|
||||
}}>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('formUrlEncoded');
|
||||
}}
|
||||
>
|
||||
Form Url Encoded
|
||||
</div>
|
||||
<div className="label-item font-medium">
|
||||
Raw
|
||||
</div>
|
||||
<div className="dropdown-item" onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('json');
|
||||
}}>
|
||||
<div className="label-item font-medium">Raw</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('json');
|
||||
}}
|
||||
>
|
||||
JSON
|
||||
</div>
|
||||
<div className="dropdown-item" onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('xml');
|
||||
}}>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('xml');
|
||||
}}
|
||||
>
|
||||
XML
|
||||
</div>
|
||||
<div className="dropdown-item" onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('text');
|
||||
}}>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('text');
|
||||
}}
|
||||
>
|
||||
TEXT
|
||||
</div>
|
||||
<div className="label-item font-medium">
|
||||
Other
|
||||
</div>
|
||||
<div className="dropdown-item" onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('none');
|
||||
}}>
|
||||
<div className="label-item font-medium">Other</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('none');
|
||||
}}
|
||||
>
|
||||
No Body
|
||||
</div>
|
||||
</Dropdown>
|
||||
|
||||
@@ -3,7 +3,7 @@ import styled from 'styled-components';
|
||||
const Wrapper = styled.div`
|
||||
div.CodeMirror {
|
||||
/* todo: find a better way */
|
||||
height: calc(100vh - 240px);
|
||||
height: calc(100vh - 220px);
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -4,26 +4,29 @@ import CodeEditor from 'components/CodeEditor';
|
||||
import FormUrlEncodedParams from 'components/RequestPane/FormUrlEncodedParams';
|
||||
import MultipartFormParams from 'components/RequestPane/MultipartFormParams';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { updateRequestBody, sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections';
|
||||
import { updateRequestBody } from 'providers/ReduxStore/slices/collections';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const RequestBody = ({item, collection}) => {
|
||||
const RequestBody = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const body = item.draft ? get(item, 'draft.request.body') : get(item, 'request.body');
|
||||
const bodyMode = item.draft ? get(item, 'draft.request.body.mode') : get(item, 'request.body.mode');
|
||||
|
||||
const onEdit = (value) => {
|
||||
dispatch(updateRequestBody({
|
||||
content: value,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid,
|
||||
}));
|
||||
dispatch(
|
||||
updateRequestBody({
|
||||
content: value,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const onRun = () => dispatch(sendRequest(item, collection.uid));;
|
||||
const onRun = () => dispatch(sendRequest(item, collection.uid));
|
||||
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
|
||||
if(['json', 'xml', 'text'].includes(bodyMode)) {
|
||||
if (['json', 'xml', 'text'].includes(bodyMode)) {
|
||||
let codeMirrorMode = {
|
||||
json: 'application/ld+json',
|
||||
text: 'application/text',
|
||||
@@ -36,31 +39,21 @@ const RequestBody = ({item, collection}) => {
|
||||
xml: body.xml
|
||||
};
|
||||
|
||||
return(
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<CodeEditor
|
||||
value={bodyContent[bodyMode] || ''}
|
||||
onEdit={onEdit}
|
||||
onRun={onRun}
|
||||
onSave={onSave}
|
||||
mode={codeMirrorMode[bodyMode]}
|
||||
/>
|
||||
<CodeEditor value={bodyContent[bodyMode] || ''} onEdit={onEdit} onRun={onRun} onSave={onSave} mode={codeMirrorMode[bodyMode]} />
|
||||
</StyledWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
if(bodyMode === 'formUrlEncoded') {
|
||||
return <FormUrlEncodedParams item={item} collection={collection}/>;
|
||||
if (bodyMode === 'formUrlEncoded') {
|
||||
return <FormUrlEncodedParams item={item} collection={collection} />;
|
||||
}
|
||||
|
||||
if(bodyMode === 'multipartForm') {
|
||||
return <MultipartFormParams item={item} collection={collection}/>;
|
||||
if (bodyMode === 'multipartForm') {
|
||||
return <MultipartFormParams item={item} collection={collection} />;
|
||||
}
|
||||
|
||||
return(
|
||||
<StyledWrapper className="w-full">
|
||||
No Body
|
||||
</StyledWrapper>
|
||||
);
|
||||
return <StyledWrapper className="w-full">No Body</StyledWrapper>;
|
||||
};
|
||||
export default RequestBody;
|
||||
|
||||
@@ -6,7 +6,8 @@ const Wrapper = styled.div`
|
||||
border-collapse: collapse;
|
||||
font-weight: 600;
|
||||
|
||||
thead, td {
|
||||
thead,
|
||||
td {
|
||||
border: 1px solid #efefef;
|
||||
}
|
||||
|
||||
@@ -26,18 +27,18 @@ const Wrapper = styled.div`
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
input[type='text'] {
|
||||
width: 100%;
|
||||
border: solid 1px transparent;
|
||||
outline: none !important;
|
||||
|
||||
&:focus{
|
||||
&:focus {
|
||||
outline: none !important;
|
||||
border: solid 1px transparent;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
input[type='checkbox'] {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
|
||||
@@ -6,52 +6,58 @@ import { useDispatch } from 'react-redux';
|
||||
import { addRequestHeader, updateRequestHeader, deleteRequestHeader } from 'providers/ReduxStore/slices/collections';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const RequestHeaders = ({item, collection}) => {
|
||||
const RequestHeaders = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const headers = item.draft ? get(item, 'draft.request.headers') : get(item, 'request.headers');
|
||||
|
||||
const addHeader = () => {
|
||||
dispatch(addRequestHeader({
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid,
|
||||
}));
|
||||
dispatch(
|
||||
addRequestHeader({
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleHeaderValueChange = (e, _header, type) => {
|
||||
const header = cloneDeep(_header);
|
||||
switch(type) {
|
||||
case 'name' : {
|
||||
switch (type) {
|
||||
case 'name': {
|
||||
header.name = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'value' : {
|
||||
case 'value': {
|
||||
header.value = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'description' : {
|
||||
case 'description': {
|
||||
header.description = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'enabled' : {
|
||||
case 'enabled': {
|
||||
header.enabled = e.target.checked;
|
||||
break;
|
||||
}
|
||||
}
|
||||
dispatch(updateRequestHeader({
|
||||
header: header,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
}));
|
||||
dispatch(
|
||||
updateRequestHeader({
|
||||
header: header,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const handleRemoveHeader = (header) => {
|
||||
dispatch(deleteRequestHeader({
|
||||
headerUid: header.uid,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
}));
|
||||
dispatch(
|
||||
deleteRequestHeader({
|
||||
headerUid: header.uid,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<table>
|
||||
@@ -64,56 +70,64 @@ const RequestHeaders = ({item, collection}) => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{headers && headers.length ? headers.map((header, index) => {
|
||||
return (
|
||||
<tr key={header.uid}>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false"
|
||||
value={header.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleHeaderValueChange(e, header, 'name')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false"
|
||||
value={header.value}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleHeaderValueChange(e, header, 'value')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false"
|
||||
value={header.description}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleHeaderValueChange(e, header, 'description')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={header.enabled}
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleHeaderValueChange(e, header, 'enabled')}
|
||||
/>
|
||||
<button onClick={() => handleRemoveHeader(header)}>
|
||||
<IconTrash strokeWidth={1.5} size={20}/>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}) : null}
|
||||
{headers && headers.length
|
||||
? headers.map((header, index) => {
|
||||
return (
|
||||
<tr key={header.uid}>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={header.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleHeaderValueChange(e, header, 'name')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={header.value}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleHeaderValueChange(e, header, 'value')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={header.description}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleHeaderValueChange(e, header, 'description')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<input type="checkbox" checked={header.enabled} className="mr-3 mousetrap" onChange={(e) => handleHeaderValueChange(e, header, 'enabled')} />
|
||||
<button onClick={() => handleRemoveHeader(header)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
<button className="btn-add-header select-none" onClick={addHeader}>+ Add Header</button>
|
||||
<button className="btn-add-header select-none" onClick={addHeader}>
|
||||
+ Add Header
|
||||
</button>
|
||||
</StyledWrapper>
|
||||
)
|
||||
);
|
||||
};
|
||||
export default RequestHeaders;
|
||||
export default RequestHeaders;
|
||||
|
||||
@@ -1,53 +1,44 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { faFolder } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faFolder } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import Modal from 'components//Modal';
|
||||
|
||||
const SaveRequest = ({items, onClose}) => {
|
||||
const SaveRequest = ({ items, onClose }) => {
|
||||
const [showFolders, setShowFolders] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
setShowFolders(items || []);
|
||||
}, [items])
|
||||
}, [items]);
|
||||
|
||||
const handleFolderClick = (folder) => {
|
||||
let subFolders = [];
|
||||
if(folder.items && folder.items.length) {
|
||||
if (folder.items && folder.items.length) {
|
||||
for (let item of folder.items) {
|
||||
if (item.items) {
|
||||
subFolders.push(item)
|
||||
subFolders.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
if(subFolders.length) {
|
||||
|
||||
if (subFolders.length) {
|
||||
setShowFolders(subFolders);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<Modal
|
||||
size ="md"
|
||||
title ="Save Request"
|
||||
confirmText ="Save"
|
||||
cancelText ="Cancel"
|
||||
handleCancel = {onClose}
|
||||
handleConfirm = {onClose}
|
||||
>
|
||||
<Modal size="md" title="Save Request" confirmText="Save" cancelText="Cancel" handleCancel={onClose} handleConfirm={onClose}>
|
||||
<p className="mb-2">Select a folder to save request:</p>
|
||||
<div className="folder-list">
|
||||
{showFolders && showFolders.length ? showFolders.map((folder) => (
|
||||
<div
|
||||
key={folder.uid}
|
||||
className="folder-name"
|
||||
onClick={() => handleFolderClick(folder)}
|
||||
>
|
||||
<FontAwesomeIcon className="mr-3 text-gray-500" icon={faFolder} style={{fontSize: 20}}/>
|
||||
{folder.name}
|
||||
</div>
|
||||
)): null}
|
||||
{showFolders && showFolders.length
|
||||
? showFolders.map((folder) => (
|
||||
<div key={folder.uid} className="folder-name" onClick={() => handleFolderClick(folder)}>
|
||||
<FontAwesomeIcon className="mr-3 text-gray-500" icon={faFolder} style={{ fontSize: 20 }} />
|
||||
{folder.name}
|
||||
</div>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
</Modal>
|
||||
</StyledWrapper>
|
||||
|
||||
@@ -2,22 +2,22 @@ import React from 'react';
|
||||
import { closeTabs } from 'providers/ReduxStore/slices/tabs';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
const RequestNotFound = ({itemUid}) => {
|
||||
const RequestNotFound = ({ itemUid }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const closeTab = () => {
|
||||
dispatch(closeTabs({
|
||||
tabUids: [itemUid]
|
||||
}));
|
||||
dispatch(
|
||||
closeTabs({
|
||||
tabUids: [itemUid]
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-6 px-6">
|
||||
<div className="p-4 bg-orange-100 border-l-4 border-yellow-500 text-yellow-700 bg-yellow-100 p-4">
|
||||
<div>Request no longer exists.</div>
|
||||
<div className="mt-2">
|
||||
This can happen when the yml file associated with this request was deleted on your filesystem.
|
||||
</div>
|
||||
<div className="mt-2">This can happen when the yml file associated with this request was deleted on your filesystem.</div>
|
||||
</div>
|
||||
<button className="btn btn-md btn-secondary mt-6" onClick={closeTab}>
|
||||
Close Tab
|
||||
|
||||
@@ -27,4 +27,4 @@ const StyledWrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import find from 'lodash/find';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import GraphQLRequestPane from 'components/RequestPane/GraphQLRequestPane';
|
||||
import HttpRequestPane from 'components/RequestPane/HttpRequestPane';
|
||||
@@ -7,13 +8,16 @@ import ResponsePane from 'components/ResponsePane';
|
||||
import Welcome from 'components/Welcome';
|
||||
import { findItemInCollection } from 'utils/collections';
|
||||
import { updateRequestPaneTabWidth } from 'providers/ReduxStore/slices/tabs';
|
||||
import { sendRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import RequestNotFound from './RequestNotFound';
|
||||
import QueryUrl from 'components/RequestPane/QueryUrl';
|
||||
import NetworkError from 'components/ResponsePane/NetworkError';
|
||||
import useGraphqlSchema from '../../hooks/useGraphqlSchema';
|
||||
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const RequestTabPanel = () => {
|
||||
if(typeof window == 'undefined') {
|
||||
if (typeof window == 'undefined') {
|
||||
return <div></div>;
|
||||
}
|
||||
const dispatch = useDispatch();
|
||||
@@ -24,12 +28,12 @@ const RequestTabPanel = () => {
|
||||
|
||||
let asideWidth = useSelector((state) => state.app.leftSidebarWidth);
|
||||
const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
|
||||
const [leftPaneWidth, setLeftPaneWidth] = useState(focusedTab && focusedTab.requestPaneWidth ? focusedTab.requestPaneWidth : ((screenWidth - asideWidth)/2.2)); // 2.2 so that request pane is relatively smaller
|
||||
const [leftPaneWidth, setLeftPaneWidth] = useState(focusedTab && focusedTab.requestPaneWidth ? focusedTab.requestPaneWidth : (screenWidth - asideWidth) / 2.2); // 2.2 so that request pane is relatively smaller
|
||||
const [rightPaneWidth, setRightPaneWidth] = useState(screenWidth - asideWidth - leftPaneWidth - 5);
|
||||
const [dragging, setDragging] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const leftPaneWidth = (screenWidth - asideWidth)/2.2;
|
||||
const leftPaneWidth = (screenWidth - asideWidth) / 2.2;
|
||||
setLeftPaneWidth(leftPaneWidth);
|
||||
}, [screenWidth]);
|
||||
|
||||
@@ -38,20 +42,22 @@ const RequestTabPanel = () => {
|
||||
}, [screenWidth, asideWidth, leftPaneWidth]);
|
||||
|
||||
const handleMouseMove = (e) => {
|
||||
if(dragging) {
|
||||
if (dragging) {
|
||||
e.preventDefault();
|
||||
setLeftPaneWidth(e.clientX - asideWidth - 5);
|
||||
setRightPaneWidth(screenWidth - (e.clientX) - 5);
|
||||
setRightPaneWidth(screenWidth - e.clientX - 5);
|
||||
}
|
||||
};
|
||||
const handleMouseUp = (e) => {
|
||||
if(dragging) {
|
||||
if (dragging) {
|
||||
e.preventDefault();
|
||||
setDragging(false);
|
||||
dispatch(updateRequestPaneTabWidth({
|
||||
uid: activeTabUid,
|
||||
requestPaneWidth: e.clientX - asideWidth - 5
|
||||
}));
|
||||
dispatch(
|
||||
updateRequestPaneTabWidth({
|
||||
uid: activeTabUid,
|
||||
requestPaneWidth: e.clientX - asideWidth - 5
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
const handleDragbarMouseDown = (e) => {
|
||||
@@ -74,44 +80,42 @@ const RequestTabPanel = () => {
|
||||
};
|
||||
}, [dragging, asideWidth]);
|
||||
|
||||
|
||||
if(!activeTabUid) {
|
||||
return (
|
||||
<Welcome/>
|
||||
);
|
||||
if (!activeTabUid) {
|
||||
return <Welcome />;
|
||||
}
|
||||
|
||||
if(!focusedTab || !focusedTab.uid || !focusedTab.collectionUid) {
|
||||
return (
|
||||
<div className="pb-4 px-4">An error occured!</div>
|
||||
);
|
||||
if (!focusedTab || !focusedTab.uid || !focusedTab.collectionUid) {
|
||||
return <div className="pb-4 px-4">An error occured!</div>;
|
||||
}
|
||||
|
||||
let collection = find(collections, (c) => c.uid === focusedTab.collectionUid);
|
||||
if(!collection || !collection.uid) {
|
||||
return (
|
||||
<div className="pb-4 px-4">Collection not found!</div>
|
||||
);
|
||||
if (!collection || !collection.uid) {
|
||||
return <div className="pb-4 px-4">Collection not found!</div>;
|
||||
}
|
||||
|
||||
const item = findItemInCollection(collection, activeTabUid);
|
||||
if(!item || !item.uid) {
|
||||
return (
|
||||
<RequestNotFound itemUid={activeTabUid}/>
|
||||
if (!item || !item.uid) {
|
||||
return <RequestNotFound itemUid={activeTabUid} />;
|
||||
}
|
||||
|
||||
const handleRun = async () => {
|
||||
dispatch(sendRequest(item, collection.uid)).catch((err) =>
|
||||
toast.custom((t) => <NetworkError onClose={() => toast.dismiss(t.id)} />, {
|
||||
duration: 5000
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const onGraphqlQueryChange = (value) => {};
|
||||
const runQuery = async () => {};
|
||||
|
||||
return (
|
||||
<StyledWrapper className={`flex flex-col flex-grow ${dragging ? 'dragging' : ''}`}>
|
||||
<section className="main flex flex-grow">
|
||||
<section className="request-pane mt-2">
|
||||
<div
|
||||
className="px-4"
|
||||
style={{width: `${leftPaneWidth}px`, height: 'calc(100% - 5px)'}}
|
||||
>
|
||||
<div className="pt-4 pb-3 px-4">
|
||||
<QueryUrl item={item} collection={collection} handleRun={handleRun} />
|
||||
</div>
|
||||
<section className="main flex flex-grow pb-4">
|
||||
<section className="request-pane">
|
||||
<div className="px-4" style={{ width: `${leftPaneWidth}px`, height: 'calc(100% - 5px)' }}>
|
||||
{item.type === 'graphql-request' ? (
|
||||
<GraphQLRequestPane
|
||||
onRunQuery={runQuery}
|
||||
@@ -122,13 +126,7 @@ const RequestTabPanel = () => {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{item.type === 'http-request' ? (
|
||||
<HttpRequestPane
|
||||
item={item}
|
||||
collection={collection}
|
||||
leftPaneWidth={leftPaneWidth}
|
||||
/>
|
||||
) : null}
|
||||
{item.type === 'http-request' ? <HttpRequestPane item={item} collection={collection} leftPaneWidth={leftPaneWidth} /> : null}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -136,17 +134,12 @@ const RequestTabPanel = () => {
|
||||
<div className="drag-request-border" />
|
||||
</div>
|
||||
|
||||
<section className="response-pane flex-grow mt-2">
|
||||
<ResponsePane
|
||||
item={item}
|
||||
rightPaneWidth={rightPaneWidth}
|
||||
response={item.response}
|
||||
isLoading={item.response && item.response.state === 'sending' ? true : false}
|
||||
/>
|
||||
<section className="response-pane flex-grow">
|
||||
<ResponsePane item={item} collection={collection} rightPaneWidth={rightPaneWidth} response={item.response} />
|
||||
</section>
|
||||
</section>
|
||||
</StyledWrapper>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default RequestTabPanel;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
`;
|
||||
const StyledWrapper = styled.div``;
|
||||
|
||||
export default StyledWrapper;
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import React from 'react';
|
||||
import { IconFiles } from '@tabler/icons';
|
||||
import EnvironmentSelector from 'components/EnvironmentSelector';
|
||||
import EnvironmentSelector from 'components/Environments/EnvironmentSelector';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const CollectionToolBar = ({collection}) => {
|
||||
const CollectionToolBar = ({ collection }) => {
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="flex items-center p-2">
|
||||
<div className="flex flex-1 items-center">
|
||||
<IconFiles size={18} strokeWidth={1.5}/>
|
||||
<IconFiles size={18} strokeWidth={1.5} />
|
||||
<span className="ml-2 mr-4 font-semibold">{collection.name}</span>
|
||||
</div>
|
||||
<div className="flex flex-1 items-center justify-end">
|
||||
<EnvironmentSelector />
|
||||
<EnvironmentSelector collection={collection} />
|
||||
</div>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default CollectionToolBar;
|
||||
|
||||
@@ -8,7 +8,7 @@ const StyledWrapper = styled.div`
|
||||
.tab-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.close-icon-container {
|
||||
@@ -25,16 +25,16 @@ const StyledWrapper = styled.div`
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
&:hover, &:hover .close-icon {
|
||||
&:hover,
|
||||
&:hover .close-icon {
|
||||
background-color: #eaeaea;
|
||||
color: rgb(76 76 76);
|
||||
}
|
||||
|
||||
.has-changes-icon {
|
||||
.has-changes-icon {
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
|
||||
@@ -6,27 +6,49 @@ import { findItemInCollection } from 'utils/collections';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { IconAlertTriangle } from '@tabler/icons';
|
||||
|
||||
const RequestTab = ({tab, collection}) => {
|
||||
const RequestTab = ({ tab, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleCloseClick = (event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
dispatch(closeTabs({
|
||||
tabUids: [tab.uid]
|
||||
}))
|
||||
dispatch(
|
||||
closeTabs({
|
||||
tabUids: [tab.uid]
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const getMethodColor = (method = '') => {
|
||||
let color = '';
|
||||
method = method.toLocaleLowerCase();
|
||||
switch(method) {
|
||||
switch (method) {
|
||||
case 'get': {
|
||||
color = 'rgb(5, 150, 105)';
|
||||
color = 'var(--color-method-get)';
|
||||
break;
|
||||
}
|
||||
case 'post': {
|
||||
color = '#8e44ad';
|
||||
color = 'var(--color-method-post)';
|
||||
break;
|
||||
}
|
||||
case 'put': {
|
||||
color = 'var(--color-method-put)';
|
||||
break;
|
||||
}
|
||||
case 'delete': {
|
||||
color = 'var(--color-method-delete)';
|
||||
break;
|
||||
}
|
||||
case 'patch': {
|
||||
color = 'var(--color-method-patch)';
|
||||
break;
|
||||
}
|
||||
case 'options': {
|
||||
color = 'var(--color-method-options)';
|
||||
break;
|
||||
}
|
||||
case 'head': {
|
||||
color = 'var(--color-method-head)';
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -36,16 +58,19 @@ const RequestTab = ({tab, collection}) => {
|
||||
|
||||
const item = findItemInCollection(collection, tab.uid);
|
||||
|
||||
if(!item) {
|
||||
if (!item) {
|
||||
return (
|
||||
<StyledWrapper className="flex items-center justify-between tab-container px-1">
|
||||
<div className="flex items-center tab-label pl-2">
|
||||
<IconAlertTriangle size={18} strokeWidth={1.5} className="text-yellow-600"/>
|
||||
<IconAlertTriangle size={18} strokeWidth={1.5} className="text-yellow-600" />
|
||||
<span className="ml-1">Not Found</span>
|
||||
</div>
|
||||
<div className="flex px-2 close-icon-container" onClick={(e) => handleCloseClick(e)}>
|
||||
<svg focusable="false"xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
|
||||
<path fill="currentColor" d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"></path>
|
||||
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
@@ -57,19 +82,26 @@ const RequestTab = ({tab, collection}) => {
|
||||
return (
|
||||
<StyledWrapper className="flex items-center justify-between tab-container px-1">
|
||||
<div className="flex items-baseline tab-label pl-2">
|
||||
<span className="tab-method uppercase" style={{color: getMethodColor(method), fontSize: 12}}>{method}</span>
|
||||
<span className="text-gray-700 ml-1 tab-name" title={item.name}>{item.name}</span>
|
||||
<span className="tab-method uppercase" style={{ color: getMethodColor(method), fontSize: 12 }}>
|
||||
{method}
|
||||
</span>
|
||||
<span className="text-gray-700 ml-1 tab-name" title={item.name}>
|
||||
{item.name}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex px-2 close-icon-container" onClick={(e) => handleCloseClick(e)}>
|
||||
{!item.draft ? (
|
||||
<svg focusable="false"xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
|
||||
<path fill="currentColor" d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"></path>
|
||||
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"
|
||||
></path>
|
||||
</svg>
|
||||
) : (
|
||||
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" width="8" height="16" fill="#cc7b1b" className="has-changes-icon" viewBox="0 0 8 8">
|
||||
<circle cx="4" cy="4" r="3"/>
|
||||
<circle cx="4" cy="4" r="3" />
|
||||
</svg>
|
||||
) }
|
||||
)}
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
@@ -46,7 +46,7 @@ const Wrapper = styled.div`
|
||||
}
|
||||
}
|
||||
|
||||
&:hover{
|
||||
&:hover {
|
||||
.close-icon-container .close-icon {
|
||||
display: block;
|
||||
}
|
||||
@@ -93,16 +93,6 @@ const Wrapper = styled.div`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.has-chevrons {
|
||||
ul {
|
||||
li:first-child {
|
||||
.tab-container {
|
||||
border-left: 1px solid #dcdcdc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
|
||||
@@ -21,38 +21,36 @@ const RequestTabs = () => {
|
||||
const screenWidth = useSelector((state) => state.app.screenWidth);
|
||||
|
||||
const getTabClassname = (tab, index) => {
|
||||
return classnames("request-tab select-none", {
|
||||
'active': tab.uid === activeTabUid,
|
||||
'last-tab': tabs && tabs.length && (index === tabs.length - 1)
|
||||
return classnames('request-tab select-none', {
|
||||
active: tab.uid === activeTabUid,
|
||||
'last-tab': tabs && tabs.length && index === tabs.length - 1
|
||||
});
|
||||
};
|
||||
|
||||
const handleClick = (tab) => {
|
||||
dispatch(focusTab({
|
||||
uid: tab.uid
|
||||
}));
|
||||
dispatch(
|
||||
focusTab({
|
||||
uid: tab.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const createNewTab = () => setNewRequestModalOpen(true);
|
||||
|
||||
if(!activeTabUid) {
|
||||
if (!activeTabUid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const activeTab = find(tabs, (t) => t.uid === activeTabUid);
|
||||
if(!activeTab) {
|
||||
return (
|
||||
<StyledWrapper>
|
||||
Something went wrong!
|
||||
</StyledWrapper>
|
||||
);
|
||||
if (!activeTab) {
|
||||
return <StyledWrapper>Something went wrong!</StyledWrapper>;
|
||||
}
|
||||
|
||||
const activeCollection = find(collections, (c) => c.uid === activeTab.collectionUid);
|
||||
const collectionRequestTabs = filter(tabs, (t) => t.collectionUid === activeTab.collectionUid);
|
||||
|
||||
const maxTablistWidth = screenWidth - leftSidebarWidth - 150;
|
||||
const tabsWidth = (collectionRequestTabs.length * 150) + 34; // 34: (+)icon
|
||||
const tabsWidth = collectionRequestTabs.length * 150 + 34; // 34: (+)icon
|
||||
const showChevrons = maxTablistWidth < tabsWidth;
|
||||
|
||||
const leftSlide = () => {
|
||||
@@ -78,18 +76,19 @@ const RequestTabs = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// Todo: Must support ephermal requests
|
||||
return (
|
||||
<StyledWrapper className={getRootClassname()}>
|
||||
{newRequestModalOpen && <NewRequest isEphermal={true} collection={activeCollection} onClose={() => setNewRequestModalOpen(false)}/>}
|
||||
{newRequestModalOpen && <NewRequest collection={activeCollection} onClose={() => setNewRequestModalOpen(false)} />}
|
||||
{collectionRequestTabs && collectionRequestTabs.length ? (
|
||||
<>
|
||||
<CollectionToolBar collection={activeCollection}/>
|
||||
<CollectionToolBar collection={activeCollection} />
|
||||
<div className="flex items-center pl-4">
|
||||
<ul role="tablist">
|
||||
{showChevrons ? (
|
||||
<li className="select-none short-tab" onClick={leftSlide}>
|
||||
<div className="flex items-center">
|
||||
<IconChevronLeft size={18} strokeWidth={1.5}/>
|
||||
<IconChevronLeft size={18} strokeWidth={1.5} />
|
||||
</div>
|
||||
</li>
|
||||
) : null}
|
||||
@@ -100,28 +99,30 @@ const RequestTabs = () => {
|
||||
</div>
|
||||
</li> */}
|
||||
</ul>
|
||||
<ul role="tablist" style={{maxWidth: maxTablistWidth}} ref={tabsRef}>
|
||||
{collectionRequestTabs && collectionRequestTabs.length ? collectionRequestTabs.map((tab, index) => {
|
||||
return (
|
||||
<li key={tab.uid} className={getTabClassname(tab, index)} role="tab" onClick={() => handleClick(tab)}>
|
||||
<RequestTab key={tab.uid} tab={tab} collection={activeCollection} activeTab={activeTab}/>
|
||||
</li>
|
||||
)
|
||||
}) : null}
|
||||
<ul role="tablist" style={{ maxWidth: maxTablistWidth }} ref={tabsRef}>
|
||||
{collectionRequestTabs && collectionRequestTabs.length
|
||||
? collectionRequestTabs.map((tab, index) => {
|
||||
return (
|
||||
<li key={tab.uid} className={getTabClassname(tab, index)} role="tab" onClick={() => handleClick(tab)}>
|
||||
<RequestTab key={tab.uid} tab={tab} collection={activeCollection} activeTab={activeTab} />
|
||||
</li>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</ul>
|
||||
|
||||
|
||||
<ul role="tablist">
|
||||
{showChevrons ? (
|
||||
<li className="select-none short-tab" onClick={rightSlide}>
|
||||
<div className="flex items-center">
|
||||
<IconChevronRight size={18} strokeWidth={1.5}/>
|
||||
<IconChevronRight size={18} strokeWidth={1.5} />
|
||||
</div>
|
||||
</li>
|
||||
) : null}
|
||||
<li className="select-none short-tab" onClick={createNewTab}>
|
||||
<div className="flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z" />
|
||||
</svg>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
|
||||
const NetworkError = ({ onClose }) => {
|
||||
return (
|
||||
<div className="max-w-md w-full bg-white shadow-lg rounded-lg pointer-events-auto flex bg-red-100">
|
||||
<div className="flex-1 w-0 p-4">
|
||||
<div className="flex items-start">
|
||||
<div className="ml-3 flex-1">
|
||||
<p className="text-sm font-medium text-red-800">Network Error</p>
|
||||
<p className="mt-2 text-xs text-gray-500">
|
||||
Please note that if you are using Bruno on the web, then the api you are connecting to must allow CORS. If not, please use the chrome extension or the desktop app
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="w-full border border-transparent rounded-none rounded-r-lg p-4 flex items-center justify-center text-sm font-medium focus:outline-none"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NetworkError;
|
||||
@@ -1,7 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
div.overlay{
|
||||
div.overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
@@ -18,4 +18,3 @@ const StyledWrapper = styled.div`
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
import React from 'react';
|
||||
import { IconRefresh } from '@tabler/icons';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { cancelRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StopWatch from '../../StopWatch';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const QueryResult = () => {
|
||||
const ResponseLoadingOverlay = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleCancelRequest = () => {
|
||||
dispatch(cancelRequest(item.cancelTokenUid, item, collection));
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="mt-4 px-3 w-full">
|
||||
<div className="overlay">
|
||||
<div style={{marginBottom: 15, fontSize: 26}}>
|
||||
<div style={{display: 'inline-block', fontSize: 24, marginLeft: 5, marginRight: 5}}>
|
||||
<StopWatch/>
|
||||
<div style={{ marginBottom: 15, fontSize: 26 }}>
|
||||
<div style={{ display: 'inline-block', fontSize: 24, marginLeft: 5, marginRight: 5 }}>
|
||||
<StopWatch />
|
||||
</div>
|
||||
</div>
|
||||
<IconRefresh size={24} className="animate-spin"/>
|
||||
<IconRefresh size={24} className="animate-spin" />
|
||||
<button
|
||||
className="mt-4 uppercase bg-gray-200 active:bg-blueGray-600 text-xs px-4 py-2 rounded shadow hover:shadow-md outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150" type="button"
|
||||
onClick={handleCancelRequest}
|
||||
className="mt-4 uppercase bg-gray-200 active:bg-blueGray-600 text-xs px-4 py-2 rounded shadow hover:shadow-md outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150"
|
||||
type="button"
|
||||
>
|
||||
Cancel Request
|
||||
</button>
|
||||
@@ -23,4 +33,4 @@ const QueryResult = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default QueryResult;
|
||||
export default ResponseLoadingOverlay;
|
||||
|
||||
@@ -6,4 +6,3 @@ const StyledWrapper = styled.div`
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import StyledWrapper from './StyledWrapper';
|
||||
const Placeholder = () => {
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="text-gray-300 flex justify-center" style={{fontSize: 200}}>
|
||||
<IconSend size={150} strokeWidth={1}/>
|
||||
<div className="text-gray-300 flex justify-center" style={{ fontSize: 200 }}>
|
||||
<IconSend size={150} strokeWidth={1} />
|
||||
</div>
|
||||
<div className="flex mt-4">
|
||||
<div className="flex flex-1 flex-col items-end px-1">
|
||||
@@ -17,7 +17,7 @@ const Placeholder = () => {
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col px-1">
|
||||
<div className="px-1 py-2">Cmd + Enter</div>
|
||||
<div className="px-1 py-2">Cmd + N</div>
|
||||
<div className="px-1 py-2">Cmd + B</div>
|
||||
<div className="px-1 py-2">Cmd + E</div>
|
||||
<div className="px-1 py-2">Cmd + H</div>
|
||||
</div>
|
||||
|
||||
@@ -3,9 +3,8 @@ import styled from 'styled-components';
|
||||
const StyledWrapper = styled.div`
|
||||
div.CodeMirror {
|
||||
/* todo: find a better way */
|
||||
height: calc(100vh - 240px);
|
||||
height: calc(100vh - 220px);
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@ import React from 'react';
|
||||
import CodeEditor from 'components/CodeEditor';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const QueryResult = ({value, width}) => {
|
||||
const QueryResult = ({ value, width }) => {
|
||||
return (
|
||||
<StyledWrapper className="px-3 w-full" style={{maxWidth: width}}>
|
||||
<StyledWrapper className="px-3 w-full" style={{ maxWidth: width }}>
|
||||
<div className="h-full">
|
||||
<CodeEditor value={value || ''} readOnly/>
|
||||
<CodeEditor value={value || ''} readOnly />
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const ResponseHeaders = ({headers}) => {
|
||||
const ResponseHeaders = ({ headers }) => {
|
||||
return (
|
||||
<StyledWrapper className="px-3 pb-4 w-full">
|
||||
<table>
|
||||
@@ -12,17 +12,19 @@ const ResponseHeaders = ({headers}) => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{headers && headers.length ? headers.map((header, index) => {
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td className="key">{header[0]}</td>
|
||||
<td className="value">{header[1]}</td>
|
||||
</tr>
|
||||
);
|
||||
}) : null}
|
||||
{headers && headers.length
|
||||
? headers.map((header, index) => {
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td className="key">{header[0]}</td>
|
||||
<td className="value">{header[1]}</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
</StyledWrapper>
|
||||
)
|
||||
);
|
||||
};
|
||||
export default ResponseHeaders;
|
||||
export default ResponseHeaders;
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
import React from 'react';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const ResponseSize = ({size}) => {
|
||||
let sizeToDisplay = ''
|
||||
const ResponseSize = ({ size }) => {
|
||||
let sizeToDisplay = '';
|
||||
|
||||
if(size > 1024 ) { // size is greater than 1kb
|
||||
let kb = Math.floor(size / 1024);
|
||||
let decimal = ((size % 1024) / 1024).toFixed(2) * 100;
|
||||
sizeToDisplay = kb + '.' + decimal + 'KB';
|
||||
if (size > 1024) {
|
||||
// size is greater than 1kb
|
||||
let kb = Math.floor(size / 1024);
|
||||
let decimal = ((size % 1024) / 1024).toFixed(2) * 100;
|
||||
sizeToDisplay = kb + '.' + decimal + 'KB';
|
||||
} else {
|
||||
sizeToDisplay = size + 'B'
|
||||
sizeToDisplay = size + 'B';
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledWrapper className="ml-4">
|
||||
{sizeToDisplay}
|
||||
</StyledWrapper>
|
||||
)
|
||||
return <StyledWrapper className="ml-4">{sizeToDisplay}</StyledWrapper>;
|
||||
};
|
||||
export default ResponseSize;
|
||||
export default ResponseSize;
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
import React from 'react';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const ResponseTime = ({duration}) => {
|
||||
let durationToDisplay = ''
|
||||
const ResponseTime = ({ duration }) => {
|
||||
let durationToDisplay = '';
|
||||
|
||||
if(duration > 1000 ) { // duration greater than a second
|
||||
let seconds = Math.floor(duration / 1000);
|
||||
let decimal = ((duration % 1000) / 1000) * 100;
|
||||
durationToDisplay = seconds + '.' + decimal.toFixed(0) + 's';
|
||||
if (duration > 1000) {
|
||||
// duration greater than a second
|
||||
let seconds = Math.floor(duration / 1000);
|
||||
let decimal = ((duration % 1000) / 1000) * 100;
|
||||
durationToDisplay = seconds + '.' + decimal.toFixed(0) + 's';
|
||||
} else {
|
||||
durationToDisplay = duration + 'ms'
|
||||
durationToDisplay = duration + 'ms';
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledWrapper className="ml-4">
|
||||
{durationToDisplay}
|
||||
</StyledWrapper>
|
||||
)
|
||||
return <StyledWrapper className="ml-4">{durationToDisplay}</StyledWrapper>;
|
||||
};
|
||||
export default ResponseTime;
|
||||
export default ResponseTime;
|
||||
|
||||
@@ -41,7 +41,7 @@ const statusCodePhraseMap = {
|
||||
415: 'Unsupported Media Type',
|
||||
416: 'Range Not Satisfiable',
|
||||
417: 'Expectation Failed',
|
||||
418: 'I\'m a teapot',
|
||||
418: "I'm a teapot",
|
||||
421: 'Misdirected Request',
|
||||
422: 'Unprocessable Entity',
|
||||
423: 'Locked',
|
||||
|
||||
@@ -3,7 +3,7 @@ import classnames from 'classnames';
|
||||
import statusCodePhraseMap from './get-status-code-phrase';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const StatusCode = ({status}) => {
|
||||
const StatusCode = ({ status }) => {
|
||||
const getTabClassname = () => {
|
||||
return classnames('', {
|
||||
'text-blue-700': status >= 100 && status < 200,
|
||||
@@ -18,6 +18,6 @@ const StatusCode = ({status}) => {
|
||||
<StyledWrapper className={getTabClassname()}>
|
||||
{status} {statusCodePhraseMap[status]}
|
||||
</StyledWrapper>
|
||||
)
|
||||
);
|
||||
};
|
||||
export default StatusCode;
|
||||
export default StatusCode;
|
||||
|
||||
@@ -10,7 +10,11 @@ const StyledWrapper = styled.div`
|
||||
color: var(--color-tab-inactive);
|
||||
cursor: pointer;
|
||||
|
||||
&:focus, &:active, &:focus-within, &:focus-visible, &:target {
|
||||
&:focus,
|
||||
&:active,
|
||||
&:focus-within,
|
||||
&:focus-visible,
|
||||
&:target {
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
@@ -23,4 +27,4 @@ const StyledWrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -12,34 +12,30 @@ import ResponseTime from './ResponseTime';
|
||||
import ResponseSize from './ResponseSize';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const ResponsePane = ({rightPaneWidth, item, isLoading}) => {
|
||||
const ResponsePane = ({ rightPaneWidth, item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const tabs = useSelector((state) => state.tabs.tabs);
|
||||
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
|
||||
const isLoading = item.response && item.response.state === 'sending';
|
||||
|
||||
const selectTab = (tab) => {
|
||||
dispatch(updateResponsePaneTab({
|
||||
uid: item.uid,
|
||||
responsePaneTab: tab
|
||||
}))
|
||||
dispatch(
|
||||
updateResponsePaneTab({
|
||||
uid: item.uid,
|
||||
responsePaneTab: tab
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const response = item.response || {};
|
||||
|
||||
const getTabPanel = (tab) => {
|
||||
switch(tab) {
|
||||
switch (tab) {
|
||||
case 'response': {
|
||||
return (
|
||||
<QueryResult
|
||||
width={rightPaneWidth}
|
||||
value={response.data ? JSON.stringify(response.data, null, 2) : ''}
|
||||
/>
|
||||
);
|
||||
return <QueryResult width={rightPaneWidth} value={response.data ? JSON.stringify(response.data, null, 2) : ''} />;
|
||||
}
|
||||
case 'headers': {
|
||||
return (
|
||||
<ResponseHeaders headers={response.headers}/>
|
||||
);
|
||||
return <ResponseHeaders headers={response.headers} />;
|
||||
}
|
||||
|
||||
default: {
|
||||
@@ -48,14 +44,15 @@ const ResponsePane = ({rightPaneWidth, item, isLoading}) => {
|
||||
}
|
||||
};
|
||||
|
||||
if(isLoading) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<StyledWrapper className="flex h-full relative">
|
||||
<Overlay />
|
||||
<Overlay item={item} collection={collection} />
|
||||
</StyledWrapper>
|
||||
);
|
||||
}
|
||||
if(response.state !== 'success') {
|
||||
|
||||
if (response.state !== 'success') {
|
||||
return (
|
||||
<StyledWrapper className="flex h-full relative">
|
||||
<Placeholder />
|
||||
@@ -63,43 +60,41 @@ const ResponsePane = ({rightPaneWidth, item, isLoading}) => {
|
||||
);
|
||||
}
|
||||
|
||||
if(!activeTabUid) {
|
||||
return (
|
||||
<div>Something went wrong</div>
|
||||
);
|
||||
if (!activeTabUid) {
|
||||
return <div>Something went wrong</div>;
|
||||
}
|
||||
|
||||
const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
|
||||
if(!focusedTab || !focusedTab.uid || !focusedTab.responsePaneTab) {
|
||||
return (
|
||||
<div className="pb-4 px-4">An error occured!</div>
|
||||
);
|
||||
if (!focusedTab || !focusedTab.uid || !focusedTab.responsePaneTab) {
|
||||
return <div className="pb-4 px-4">An error occured!</div>;
|
||||
}
|
||||
|
||||
const getTabClassname = (tabName) => {
|
||||
return classnames(`tab select-none ${tabName}`, {
|
||||
'active': tabName === focusedTab.responsePaneTab
|
||||
active: tabName === focusedTab.responsePaneTab
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="flex flex-col h-full relative">
|
||||
<div className="flex items-center px-3 tabs" role="tablist">
|
||||
<div className={getTabClassname('response')} role="tab" onClick={() => selectTab('response')}>Response</div>
|
||||
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>Headers</div>
|
||||
<div className={getTabClassname('response')} role="tab" onClick={() => selectTab('response')}>
|
||||
Response
|
||||
</div>
|
||||
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
|
||||
Headers
|
||||
</div>
|
||||
{!isLoading ? (
|
||||
<div className="flex flex-grow justify-end items-center">
|
||||
<StatusCode status={response.status}/>
|
||||
<ResponseTime duration={response.duration}/>
|
||||
<ResponseSize size={response.size}/>
|
||||
<StatusCode status={response.status} />
|
||||
<ResponseTime duration={response.duration} />
|
||||
<ResponseSize size={response.size} />
|
||||
</div>
|
||||
) : null }
|
||||
) : null}
|
||||
</div>
|
||||
<section className="flex flex-grow mt-5">
|
||||
{getTabPanel(focusedTab.responsePaneTab)}
|
||||
</section>
|
||||
<section className="flex flex-grow mt-5">{getTabPanel(focusedTab.responsePaneTab)}</section>
|
||||
</StyledWrapper>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default ResponsePane;
|
||||
|
||||