mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-03 01:18:32 +00:00
Compare commits
156 Commits
feature/su
...
v0.16.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6facdfd66b | ||
|
|
0f211131b1 | ||
|
|
cd3b8a948e | ||
|
|
f695036721 | ||
|
|
3661fa7df3 | ||
|
|
559fcb0806 | ||
|
|
d5da8a9e2f | ||
|
|
a3050db6c4 | ||
|
|
c27f090583 | ||
|
|
487dd73040 | ||
|
|
665428a2d0 | ||
|
|
6a2ba0f746 | ||
|
|
36f9902f2e | ||
|
|
c0b7dad030 | ||
|
|
8780d309ac | ||
|
|
08c1563a7a | ||
|
|
07ad1f9f60 | ||
|
|
8df6b241bb | ||
|
|
50e0558d7d | ||
|
|
cbe84cc512 | ||
|
|
cbb975d81d | ||
|
|
30ee472c40 | ||
|
|
c7aecbea79 | ||
|
|
b814c84411 | ||
|
|
6306ad17c3 | ||
|
|
4b800e30e4 | ||
|
|
89f418a114 | ||
|
|
9c8ef09d01 | ||
|
|
83d354c25c | ||
|
|
bb31ddc5d2 | ||
|
|
ff40178c8c | ||
|
|
1c549f7faf | ||
|
|
eb6b75ff98 | ||
|
|
eb010adeac | ||
|
|
7e5e22cfcf | ||
|
|
2515e78a10 | ||
|
|
511854369f | ||
|
|
18f185d37c | ||
|
|
7a0322d09e | ||
|
|
2dadad3af0 | ||
|
|
eaa31342dc | ||
|
|
c4fd9d38a5 | ||
|
|
9c4c219b99 | ||
|
|
8e22aa2fca | ||
|
|
6b9e085696 | ||
|
|
74282706aa | ||
|
|
aa88aa73a2 | ||
|
|
f78c1640e9 | ||
|
|
a5a17cf8eb | ||
|
|
c5a86cb343 | ||
|
|
9b94cddc9b | ||
|
|
0a172ddce8 | ||
|
|
aea1cbba9e | ||
|
|
7a1b44858d | ||
|
|
1c89ab3450 | ||
|
|
e3ce420216 | ||
|
|
c91fef2264 | ||
|
|
c83fce16dc | ||
|
|
5415e20d7e | ||
|
|
2f45b95930 | ||
|
|
4531cfc994 | ||
|
|
bd0738198c | ||
|
|
9a81793151 | ||
|
|
88c16fa388 | ||
|
|
f68eacfe0d | ||
|
|
116e050987 | ||
|
|
5af2f68252 | ||
|
|
a53dd76854 | ||
|
|
67fe264494 | ||
|
|
ae692dde06 | ||
|
|
1c4c5cc0c0 | ||
|
|
19ca1af71e | ||
|
|
4016a83626 | ||
|
|
71b18c8b21 | ||
|
|
b53fcbb3d1 | ||
|
|
19a7f397bb | ||
|
|
a103f41d85 | ||
|
|
7d4d1573af | ||
|
|
6f2bb55ecf | ||
|
|
f189cb1a2e | ||
|
|
c020cd94a8 | ||
|
|
0d4f7e6a06 | ||
|
|
f9ed68843d | ||
|
|
d3a56fdc82 | ||
|
|
e6c3a5af4c | ||
|
|
cee8073bb7 | ||
|
|
69be52cf9e | ||
|
|
9e400085e3 | ||
|
|
b07bb67943 | ||
|
|
593210456a | ||
|
|
e328a4615e | ||
|
|
18afb73238 | ||
|
|
73b71e0829 | ||
|
|
4c25aa99aa | ||
|
|
23843b5d0a | ||
|
|
7fbd338fa6 | ||
|
|
4aeb5cf56d | ||
|
|
1ed39a5ea6 | ||
|
|
74f248782b | ||
|
|
99239e19b4 | ||
|
|
f46160e161 | ||
|
|
d0147778db | ||
|
|
87119cee2e | ||
|
|
b25d896dd6 | ||
|
|
08495e7fb5 | ||
|
|
ee084696f5 | ||
|
|
aeb9fc8875 | ||
|
|
26a05f92cb | ||
|
|
fff7819c46 | ||
|
|
1d678ee0d9 | ||
|
|
fd4c188c95 | ||
|
|
97678b05fc | ||
|
|
a1c9625aee | ||
|
|
a9d74467ff | ||
|
|
bd670eceb6 | ||
|
|
1ccb66e92a | ||
|
|
d62881fe0d | ||
|
|
9ea95b4571 | ||
|
|
df9322b767 | ||
|
|
c27c750c3e | ||
|
|
94baee8e25 | ||
|
|
417b50b0ad | ||
|
|
bf5ee7e409 | ||
|
|
3ca0107f1b | ||
|
|
aeb29393c5 | ||
|
|
ec22fdb637 | ||
|
|
0866d33858 | ||
|
|
66024d04e9 | ||
|
|
330a7ad18a | ||
|
|
2976842588 | ||
|
|
51e0ea2c2d | ||
|
|
3b85e7ebcc | ||
|
|
49b0f3a322 | ||
|
|
ad905d1a0a | ||
|
|
45ca5ded96 | ||
|
|
b6528062f0 | ||
|
|
86094cc054 | ||
|
|
8e0bc68ada | ||
|
|
c36c7b44a6 | ||
|
|
0ac27dee56 | ||
|
|
ede122ab09 | ||
|
|
96e368cb18 | ||
|
|
942b75861c | ||
|
|
c1711ea01b | ||
|
|
dedfefbc9a | ||
|
|
65dd5df87e | ||
|
|
78d2393686 | ||
|
|
0b65c4580e | ||
|
|
9cc1bf1e2f | ||
|
|
b96e3d0f23 | ||
|
|
11c99d55dc | ||
|
|
d054dc4c78 | ||
|
|
9014dc5769 | ||
|
|
d346970241 | ||
|
|
bdb3051c2b | ||
|
|
f8325b22b3 |
12
.github/workflows/bump-homebrew-cask.yml
vendored
Normal file
12
.github/workflows/bump-homebrew-cask.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
name: Bump Homebrew Cask
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
bump:
|
||||
runs-on: macos-10.15
|
||||
steps:
|
||||
- name: Bump Homebrew Cask
|
||||
run: brew bump-cask-pr bruno --version "${GITHUB_REF_NAME#v}"
|
||||
4
.husky/pre-commit
Executable file
4
.husky/pre-commit
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx pretty-quick --staged
|
||||
7
.prettierrc.json
Normal file
7
.prettierrc.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120
|
||||
}
|
||||
BIN
assets/images/cli-demo.png
Normal file
BIN
assets/images/cli-demo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
BIN
assets/images/run-anywhere.png
Normal file
BIN
assets/images/run-anywhere.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 153 KiB |
BIN
assets/images/version-control.png
Normal file
BIN
assets/images/version-control.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 94 KiB |
11
package.json
11
package.json
@@ -13,11 +13,14 @@
|
||||
"packages/bruno-testbench",
|
||||
"packages/bruno-graphql-docs"
|
||||
],
|
||||
"homepage": "https://usebruno.com",
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^7.6.0",
|
||||
"@jest/globals": "^29.2.0",
|
||||
"@playwright/test": "^1.27.1",
|
||||
"husky": "^8.0.3",
|
||||
"jest": "^29.2.0",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"randomstring": "^1.2.2",
|
||||
"ts-jest": "^29.0.5"
|
||||
},
|
||||
@@ -30,9 +33,11 @@
|
||||
"build:graphql-docs": "npm run build --workspace=packages/bruno-graphql-docs",
|
||||
"build:electron": "./scripts/build-electron.sh",
|
||||
"test:e2e": "npx playwright test",
|
||||
"test:report": "npx playwright show-report"
|
||||
"test:report": "npx playwright show-report",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"overrides": {
|
||||
"rollup": "3.2.5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"printWidth": 180
|
||||
"printWidth": 120
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"@tabler/icons": "^1.46.0",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@usebruno/graphql-docs": "0.1.0",
|
||||
"@usebruno/schema": "0.3.1",
|
||||
"@usebruno/schema": "0.5.0",
|
||||
"axios": "^0.26.0",
|
||||
"classnames": "^2.3.1",
|
||||
"codemirror": "^5.65.2",
|
||||
@@ -47,11 +47,13 @@
|
||||
"react-dom": "18.2.0",
|
||||
"react-github-btn": "^1.4.0",
|
||||
"react-hot-toast": "^2.4.0",
|
||||
"react-inspector": "^6.0.2",
|
||||
"react-redux": "^7.2.6",
|
||||
"react-tooltip": "^5.5.2",
|
||||
"sass": "^1.46.0",
|
||||
"styled-components": "^5.3.3",
|
||||
"tailwindcss": "^2.2.19",
|
||||
"xml-formatter": "^3.5.0",
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -14,7 +14,11 @@ const Bruno = ({ width }) => {
|
||||
stroke="none"
|
||||
points="36,47.2521 32.9167,49.6688 30.4167,49.6688 30.3333,53.5021 31.0833,57.0021 32.1667,58.9188 35,60.4188 39.5833,59.8355 41.1667,58.0855 42.1667,53.8355 41.9167,49.8355 39.9167,50.0855"
|
||||
/>
|
||||
<polygon fill="#3F3F3F" stroke="none" points="32.5,36.9188 30.9167,40.6688 33.0833,41.9188 34.3333,42.4188 38.6667,42.5855 41.5833,40.3355 39.8333,37.0855" />
|
||||
<polygon
|
||||
fill="#3F3F3F"
|
||||
stroke="none"
|
||||
points="32.5,36.9188 30.9167,40.6688 33.0833,41.9188 34.3333,42.4188 38.6667,42.5855 41.5833,40.3355 39.8333,37.0855"
|
||||
/>
|
||||
</g>
|
||||
<g id="hair" />
|
||||
<g id="skin" />
|
||||
@@ -84,8 +88,27 @@ const Bruno = ({ width }) => {
|
||||
strokeWidth="2"
|
||||
d="M52.6309,46.4628c0,0-3.0781,6.7216-7.8049,8.2712"
|
||||
/>
|
||||
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M19.437,46.969c0,0,3.0781,6.0823,7.8049,7.632" />
|
||||
<line x1="36.2078" x2="36.2078" y1="47.3393" y2="44.3093" fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" />
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#000000"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit="10"
|
||||
strokeWidth="2"
|
||||
d="M19.437,46.969c0,0,3.0781,6.0823,7.8049,7.632"
|
||||
/>
|
||||
<line
|
||||
x1="36.2078"
|
||||
x2="36.2078"
|
||||
y1="47.3393"
|
||||
y2="44.3093"
|
||||
fill="none"
|
||||
stroke="#000000"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit="10"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,8 @@ const StyledWrapper = styled.div`
|
||||
border: solid 1px ${(props) => props.theme.codemirror.border};
|
||||
}
|
||||
|
||||
.CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-vertical div {
|
||||
.CodeMirror-overlayscroll-horizontal div,
|
||||
.CodeMirror-overlayscroll-vertical div {
|
||||
background: #d2d7db;
|
||||
}
|
||||
|
||||
@@ -17,12 +18,14 @@ const StyledWrapper = styled.div`
|
||||
// Todo: dark mode temporary fix
|
||||
// Clean this
|
||||
.CodeMirror.cm-s-monokai {
|
||||
.CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-vertical div {
|
||||
.CodeMirror-overlayscroll-horizontal div,
|
||||
.CodeMirror-overlayscroll-vertical div {
|
||||
background: #444444;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute {
|
||||
.cm-s-monokai span.cm-property,
|
||||
.cm-s-monokai span.cm-attribute {
|
||||
color: #9cdcfe !important;
|
||||
}
|
||||
|
||||
@@ -30,16 +33,20 @@ const StyledWrapper = styled.div`
|
||||
color: #ce9178 !important;
|
||||
}
|
||||
|
||||
.cm-s-monokai span.cm-number{
|
||||
.cm-s-monokai span.cm-number {
|
||||
color: #b5cea8 !important;
|
||||
}
|
||||
|
||||
.cm-s-monokai span.cm-atom{
|
||||
.cm-s-monokai span.cm-atom {
|
||||
color: #569cd6 !important;
|
||||
}
|
||||
|
||||
.cm-variable-valid{color: green}
|
||||
.cm-variable-invalid{color: red}
|
||||
.cm-variable-valid {
|
||||
color: green;
|
||||
}
|
||||
.cm-variable-invalid {
|
||||
color: red;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -43,7 +43,7 @@ export default class CodeEditor extends React.Component {
|
||||
foldGutter: true,
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||
readOnly: this.props.readOnly,
|
||||
scrollbarStyle: "overlay",
|
||||
scrollbarStyle: 'overlay',
|
||||
theme: this.props.theme === 'dark' ? 'monokai' : 'default',
|
||||
extraKeys: {
|
||||
'Cmd-Enter': () => {
|
||||
@@ -96,7 +96,7 @@ export default class CodeEditor extends React.Component {
|
||||
this.editor.setValue(this.props.value);
|
||||
}
|
||||
|
||||
if(this.editor) {
|
||||
if (this.editor) {
|
||||
let variables = getEnvironmentVariables(this.props.collection);
|
||||
if (!isEqual(variables, this.variables)) {
|
||||
this.addOverlay();
|
||||
@@ -135,7 +135,7 @@ export default class CodeEditor extends React.Component {
|
||||
|
||||
defineCodeMirrorBrunoVariablesMode(variables, mode);
|
||||
this.editor.setOption('mode', 'brunovariables');
|
||||
}
|
||||
};
|
||||
|
||||
_onEdit = () => {
|
||||
if (!this.ignoreChangeEvent && this.editor) {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
.settings-label {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.textbox {
|
||||
border: 1px solid #ccc;
|
||||
padding: 0.15rem 0.45rem;
|
||||
box-shadow: none;
|
||||
border-radius: 0px;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
transition: border-color ease-in-out 0.1s;
|
||||
border-radius: 3px;
|
||||
background-color: ${(props) => props.theme.modal.input.bg};
|
||||
border: 1px solid ${(props) => props.theme.modal.input.border};
|
||||
|
||||
&:focus {
|
||||
border: solid 1px ${(props) => props.theme.modal.input.focusBorder} !important;
|
||||
outline: none !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -0,0 +1,190 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useFormik } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
enabled: proxyConfig.enabled || false,
|
||||
protocol: proxyConfig.protocol || 'http',
|
||||
hostname: proxyConfig.hostname || '',
|
||||
port: proxyConfig.port || '',
|
||||
auth: {
|
||||
enabled: proxyConfig.auth ? proxyConfig.auth.enabled || false : false,
|
||||
username: proxyConfig.auth ? proxyConfig.auth.username || '' : '',
|
||||
password: proxyConfig.auth ? proxyConfig.auth.password || '' : ''
|
||||
}
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
enabled: Yup.boolean(),
|
||||
protocol: Yup.string().oneOf(['http', 'https']),
|
||||
hostname: Yup.string().max(1024),
|
||||
port: Yup.number().min(0).max(65535),
|
||||
auth: Yup.object({
|
||||
enabled: Yup.boolean(),
|
||||
username: Yup.string().max(1024),
|
||||
password: Yup.string().max(1024)
|
||||
})
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
onUpdate(values);
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
formik.setValues({
|
||||
enabled: proxyConfig.enabled || false,
|
||||
protocol: proxyConfig.protocol || 'http',
|
||||
hostname: proxyConfig.hostname || '',
|
||||
port: proxyConfig.port || '',
|
||||
auth: {
|
||||
enabled: proxyConfig.auth ? proxyConfig.auth.enabled || false : false,
|
||||
username: proxyConfig.auth ? proxyConfig.auth.username || '' : '',
|
||||
password: proxyConfig.auth ? proxyConfig.auth.password || '' : ''
|
||||
}
|
||||
});
|
||||
}, [proxyConfig]);
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<h1 className="font-medium mb-3">Proxy Settings</h1>
|
||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||
<div className="ml-4 mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="enabled">
|
||||
Enabled
|
||||
</label>
|
||||
<input type="checkbox" name="enabled" checked={formik.values.enabled} onChange={formik.handleChange} />
|
||||
</div>
|
||||
<div className="ml-4 mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="protocol">
|
||||
Protocol
|
||||
</label>
|
||||
<div className="flex items-center">
|
||||
<label className="flex items-center mr-4">
|
||||
<input
|
||||
type="radio"
|
||||
name="protocol"
|
||||
value="http"
|
||||
checked={formik.values.protocol === 'http'}
|
||||
onChange={formik.handleChange}
|
||||
className="mr-1"
|
||||
/>
|
||||
http
|
||||
</label>
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="radio"
|
||||
name="protocol"
|
||||
value="https"
|
||||
checked={formik.values.protocol === 'https'}
|
||||
onChange={formik.handleChange}
|
||||
className="mr-1"
|
||||
/>
|
||||
https
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-4 mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="hostname">
|
||||
Hostname
|
||||
</label>
|
||||
<input
|
||||
id="hostname"
|
||||
type="text"
|
||||
name="hostname"
|
||||
className="block textbox"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.hostname || ''}
|
||||
/>
|
||||
{formik.touched.hostname && formik.errors.hostname ? (
|
||||
<div className="text-red-500">{formik.errors.hostname}</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="ml-4 mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="port">
|
||||
Port
|
||||
</label>
|
||||
<input
|
||||
id="port"
|
||||
type="number"
|
||||
name="port"
|
||||
className="block textbox"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.port}
|
||||
/>
|
||||
{formik.touched.port && formik.errors.port ? <div className="text-red-500">{formik.errors.port}</div> : null}
|
||||
</div>
|
||||
<div className="ml-4 mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="auth.enabled">
|
||||
Auth
|
||||
</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="auth.enabled"
|
||||
checked={formik.values.auth.enabled}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="ml-4 mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="auth.username">
|
||||
Username
|
||||
</label>
|
||||
<input
|
||||
id="auth.username"
|
||||
type="text"
|
||||
name="auth.username"
|
||||
className="block textbox"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={formik.values.auth.username}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
{formik.touched.auth?.username && formik.errors.auth?.username ? (
|
||||
<div className="text-red-500">{formik.errors.auth.username}</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="ml-4 mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="auth.password">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
id="auth.password"
|
||||
type="text"
|
||||
name="auth.password"
|
||||
className="block textbox"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={formik.values.auth.password}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
{formik.touched.auth?.password && formik.errors.auth?.password ? (
|
||||
<div className="text-red-500">{formik.errors.auth.password}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<button type="submit" className="submit btn btn-md btn-secondary">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProxySettings;
|
||||
@@ -0,0 +1,20 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
table {
|
||||
thead,
|
||||
td {
|
||||
border: 1px solid ${(props) => props.theme.table.border};
|
||||
|
||||
li {
|
||||
background-color: ${(props) => props.theme.bg} !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import get from 'lodash/get';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import toast from 'react-hot-toast';
|
||||
import { updateBrunoConfig } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import ProxySettings from './ProxySettings';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const CollectionSettings = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const proxyConfig = get(collection, 'brunoConfig.proxy', {});
|
||||
|
||||
const onProxySettingsUpdate = (config) => {
|
||||
const brunoConfig = cloneDeep(collection.brunoConfig);
|
||||
brunoConfig.proxy = config;
|
||||
dispatch(updateBrunoConfig(brunoConfig, collection.uid))
|
||||
.then(() => {
|
||||
toast.success('Collection settings updated successfully');
|
||||
})
|
||||
.catch((err) => console.log(err) && toast.error('Failed to update collection settings'));
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="px-4 py-4">
|
||||
<h1 className="font-semibold mb-4">Collection Settings</h1>
|
||||
|
||||
<ProxySettings proxyConfig={proxyConfig} onUpdate={onProxySettingsUpdate} />
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default CollectionSettings;
|
||||
@@ -5,7 +5,16 @@ import StyledWrapper from './StyledWrapper';
|
||||
const Dropdown = ({ icon, children, onCreate, placement }) => {
|
||||
return (
|
||||
<StyledWrapper className="dropdown">
|
||||
<Tippy content={children} placement={placement || 'bottom-end'} animation={false} arrow={false} onCreate={onCreate} interactive={true} trigger="click" appendTo="parent">
|
||||
<Tippy
|
||||
content={children}
|
||||
placement={placement || 'bottom-end'}
|
||||
animation={false}
|
||||
arrow={false}
|
||||
onCreate={onCreate}
|
||||
interactive={true}
|
||||
trigger="click"
|
||||
appendTo="parent"
|
||||
>
|
||||
{icon}
|
||||
</Tippy>
|
||||
</StyledWrapper>
|
||||
|
||||
@@ -2,7 +2,7 @@ import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
.current-enviroment {
|
||||
background-color: ${(props) => props.theme.sidebar.badge.bg};
|
||||
background-color: ${(props) => props.theme.sidebar.badge.bg};
|
||||
border-radius: 15px;
|
||||
|
||||
.caret {
|
||||
|
||||
@@ -35,7 +35,7 @@ const EnvironmentSelector = ({ collection }) => {
|
||||
toast.success(`No Environments are active now`);
|
||||
}
|
||||
})
|
||||
.catch((err) => console.log(err) && toast.error('An error occured while selecting the environment'));
|
||||
.catch((err) => console.log(err) && toast.error('An error occurred while selecting the environment'));
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -64,7 +64,7 @@ const EnvironmentSelector = ({ collection }) => {
|
||||
}}
|
||||
>
|
||||
<IconDatabaseOff size={18} strokeWidth={1.5} />
|
||||
<span className='ml-2'>No Environment</span>
|
||||
<span className="ml-2">No Environment</span>
|
||||
</div>
|
||||
<div className="dropdown-item border-top" onClick={() => setOpenSettingsModal(true)}>
|
||||
<div className="pr-2 text-gray-600">
|
||||
|
||||
@@ -16,7 +16,10 @@ const CreateEnvironment = ({ collection, onClose }) => {
|
||||
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')
|
||||
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))
|
||||
@@ -40,7 +43,13 @@ const CreateEnvironment = ({ collection, onClose }) => {
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Modal size="sm" title={'Create Environment'} confirmText="Create" handleConfirm={onSubmit} handleCancel={onClose}>
|
||||
<Modal
|
||||
size="sm"
|
||||
title={'Create Environment'}
|
||||
confirmText="Create"
|
||||
handleConfirm={onSubmit}
|
||||
handleCancel={onClose}
|
||||
>
|
||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||
<div>
|
||||
<label htmlFor="name" className="block font-semibold">
|
||||
@@ -59,7 +68,9 @@ const CreateEnvironment = ({ collection, onClose }) => {
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.name || ''}
|
||||
/>
|
||||
{formik.touched.name && formik.errors.name ? <div className="text-red-500">{formik.errors.name}</div> : null}
|
||||
{formik.touched.name && formik.errors.name ? (
|
||||
<div className="text-red-500">{formik.errors.name}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
@@ -20,7 +20,13 @@ const DeleteEnvironment = ({ onClose, environment, collection }) => {
|
||||
return (
|
||||
<Portal>
|
||||
<StyledWrapper>
|
||||
<Modal size="sm" title={'Delete Environment'} confirmText="Delete" handleConfirm={onConfirm} handleCancel={onClose}>
|
||||
<Modal
|
||||
size="sm"
|
||||
title={'Delete Environment'}
|
||||
confirmText="Delete"
|
||||
handleConfirm={onConfirm}
|
||||
handleCancel={onClose}
|
||||
>
|
||||
Are you sure you want to delete <span className="font-semibold">{environment.name}</span> ?
|
||||
</Modal>
|
||||
</StyledWrapper>
|
||||
|
||||
@@ -5,18 +5,30 @@ const Wrapper = styled.div`
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-weight: 600;
|
||||
table-layout: fixed;
|
||||
|
||||
thead,
|
||||
td {
|
||||
border: 1px solid ${(props) => props.theme.collection.environment.settings.gridBorder};
|
||||
padding: 4px 10px;
|
||||
|
||||
&:nth-child(1),
|
||||
&:nth-child(4),
|
||||
&:nth-child(5) {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
width: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
thead {
|
||||
color: ${(props) => props.theme.table.thead.color};;
|
||||
color: ${(props) => props.theme.table.thead.color};
|
||||
font-size: 0.8125rem;
|
||||
user-select: none;
|
||||
}
|
||||
td {
|
||||
thead td {
|
||||
padding: 6px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,16 @@ import React, { useReducer } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { IconTrash } from '@tabler/icons';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import reducer from './reducer';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const EnvironmentVariables = ({ environment, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
const [state, reducerDispatch] = useReducer(reducer, { hasChanges: false, variables: environment.variables || [] });
|
||||
const { variables, hasChanges } = state;
|
||||
|
||||
@@ -44,6 +47,10 @@ const EnvironmentVariables = ({ environment, collection }) => {
|
||||
variable.enabled = e.target.checked;
|
||||
break;
|
||||
}
|
||||
case 'secret': {
|
||||
variable.secret = e.target.checked;
|
||||
break;
|
||||
}
|
||||
}
|
||||
reducerDispatch({
|
||||
type: 'UPDATE_VAR',
|
||||
@@ -63,8 +70,10 @@ const EnvironmentVariables = ({ environment, collection }) => {
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Enabled</td>
|
||||
<td>Name</td>
|
||||
<td>Value</td>
|
||||
<td>Secret</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -73,6 +82,14 @@ const EnvironmentVariables = ({ environment, collection }) => {
|
||||
? variables.map((variable, index) => {
|
||||
return (
|
||||
<tr key={variable.uid}>
|
||||
<td className="text-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={variable.enabled}
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleVarChange(e, variable, 'enabled')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
@@ -86,24 +103,25 @@ const EnvironmentVariables = ({ environment, collection }) => {
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
<SingleLineEditor
|
||||
value={variable.value}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleVarChange(e, variable, 'value')}
|
||||
theme={storedTheme}
|
||||
onChange={(newValue) => handleVarChange({ target: { value: newValue } }, variable, 'value')}
|
||||
collection={collection}
|
||||
/>
|
||||
</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>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={variable.secret}
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleVarChange(e, variable, 'secret')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<button onClick={() => handleRemoveVars(variable)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
@@ -119,7 +137,12 @@ const EnvironmentVariables = ({ environment, collection }) => {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="submit" className="submit btn btn-md btn-secondary mt-2" disabled={!hasChanges} onClick={saveChanges}>
|
||||
<button
|
||||
type="submit"
|
||||
className="submit btn btn-md btn-secondary mt-2"
|
||||
disabled={!hasChanges}
|
||||
onClick={saveChanges}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,7 @@ const reducer = (state, action) => {
|
||||
name: '',
|
||||
value: '',
|
||||
type: 'text',
|
||||
secret: false,
|
||||
enabled: true
|
||||
});
|
||||
draft.hasChanges = true;
|
||||
@@ -24,6 +25,7 @@ const reducer = (state, action) => {
|
||||
variable.name = action.variable.name;
|
||||
variable.value = action.variable.value;
|
||||
variable.enabled = action.variable.enabled;
|
||||
variable.secret = action.variable.secret;
|
||||
draft.hasChanges = true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,8 +10,16 @@ const EnvironmentDetails = ({ environment, collection }) => {
|
||||
|
||||
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} />}
|
||||
{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} />
|
||||
|
||||
@@ -14,12 +14,12 @@ const EnvironmentList = ({ collection }) => {
|
||||
const prevEnvUids = usePrevious(envUids);
|
||||
|
||||
useEffect(() => {
|
||||
if(selectedEnvironment) {
|
||||
if (selectedEnvironment) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
|
||||
if(environment) {
|
||||
if (environment) {
|
||||
setSelectedEnvironment(environment);
|
||||
} else {
|
||||
setSelectedEnvironment(environments && environments.length ? environments[0] : null);
|
||||
@@ -28,9 +28,9 @@ const EnvironmentList = ({ collection }) => {
|
||||
|
||||
useEffect(() => {
|
||||
// check env add
|
||||
if (prevEnvUids && prevEnvUids.length && envUids.length > prevEnvUids.length) {
|
||||
if (prevEnvUids && prevEnvUids.length && envUids.length > prevEnvUids.length) {
|
||||
const newEnv = environments.find((env) => !prevEnvUids.includes(env.uid));
|
||||
if(newEnv){
|
||||
if (newEnv) {
|
||||
setSelectedEnvironment(newEnv);
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ const EnvironmentList = ({ collection }) => {
|
||||
// check env delete
|
||||
if (prevEnvUids && prevEnvUids.length && envUids.length < prevEnvUids.length) {
|
||||
setSelectedEnvironment(environments && environments.length ? environments[0] : null);
|
||||
}
|
||||
}
|
||||
}, [envUids, environments, prevEnvUids]);
|
||||
|
||||
if (!selectedEnvironment) {
|
||||
|
||||
@@ -16,7 +16,10 @@ const RenameEnvironment = ({ onClose, environment, collection }) => {
|
||||
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')
|
||||
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))
|
||||
@@ -40,7 +43,13 @@ const RenameEnvironment = ({ onClose, environment, collection }) => {
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Modal size="sm" title={'Rename Environment'} confirmText="Rename" handleConfirm={onSubmit} handleCancel={onClose}>
|
||||
<Modal
|
||||
size="sm"
|
||||
title={'Rename Environment'}
|
||||
confirmText="Rename"
|
||||
handleConfirm={onSubmit}
|
||||
handleCancel={onClose}
|
||||
>
|
||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||
<div>
|
||||
<label htmlFor="name" className="block font-semibold">
|
||||
@@ -59,7 +68,9 @@ const RenameEnvironment = ({ onClose, environment, collection }) => {
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.name || ''}
|
||||
/>
|
||||
{formik.touched.name && formik.errors.name ? <div className="text-red-500">{formik.errors.name}</div> : null}
|
||||
{formik.touched.name && formik.errors.name ? (
|
||||
<div className="text-red-500">{formik.errors.name}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
@@ -11,11 +11,21 @@ const EnvironmentSettings = ({ collection, onClose }) => {
|
||||
if (!environments || !environments.length) {
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<Modal size="md" title="Environments" confirmText={'Close'} handleConfirm={onClose} handleCancel={onClose} hideCancel={true}>
|
||||
<Modal
|
||||
size="md"
|
||||
title="Environments"
|
||||
confirmText={'Close'}
|
||||
handleConfirm={onClose}
|
||||
handleCancel={onClose}
|
||||
hideCancel={true}
|
||||
>
|
||||
{openCreateModal && <CreateEnvironment collection={collection} onClose={() => setOpenCreateModal(false)} />}
|
||||
<div className="text-center">
|
||||
<p>No environments found!</p>
|
||||
<button className="btn-create-environment text-link pr-2 py-3 mt-2 select-none" onClick={() => setOpenCreateModal(true)}>
|
||||
<button
|
||||
className="btn-create-environment text-link pr-2 py-3 mt-2 select-none"
|
||||
onClick={() => setOpenCreateModal(true)}
|
||||
>
|
||||
+ <span>Create Environment</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import React from 'react';
|
||||
|
||||
const SendIcon = ({color, width}) => {
|
||||
const SendIcon = ({ color, width }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={width}
|
||||
height={width}
|
||||
viewBox="0 0 48 48"
|
||||
>
|
||||
<path fill={color} d="M4.02 42l41.98-18-41.98-18-.02 14 30 4-30 4z"/>
|
||||
<path d="M0 0h48v48h-48z" fill="none"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={width} viewBox="0 0 48 48">
|
||||
<path fill={color} d="M4.02 42l41.98-18-41.98-18-.02 14 30 4-30 4z" />
|
||||
<path d="M0 0h48v48h-48z" fill="none" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default SendIcon;
|
||||
|
||||
@@ -19,7 +19,7 @@ const Wrapper = styled.div`
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
overflow-y: auto;
|
||||
z-index: 1003;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.bruno-modal-card {
|
||||
@@ -28,7 +28,7 @@ const Wrapper = styled.div`
|
||||
background: var(--color-background-top);
|
||||
border-radius: var(--border-radius);
|
||||
position: relative;
|
||||
z-index: 1003;
|
||||
z-index: 10;
|
||||
max-width: calc(100% - var(--spacing-base-unit));
|
||||
box-shadow: var(--box-shadow-base);
|
||||
display: flex;
|
||||
@@ -100,7 +100,7 @@ const Wrapper = styled.div`
|
||||
border-radius: 0px;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
transition: border-color ease-in-out .1s;
|
||||
transition: border-color ease-in-out 0.1s;
|
||||
border-radius: 3px;
|
||||
background-color: ${(props) => props.theme.modal.input.bg};
|
||||
border: 1px solid ${(props) => props.theme.modal.input.border};
|
||||
|
||||
@@ -14,7 +14,15 @@ const ModalHeader = ({ title, handleCancel }) => (
|
||||
|
||||
const ModalContent = ({ children }) => <div className="bruno-modal-content px-4 py-6">{children}</div>;
|
||||
|
||||
const ModalFooter = ({ confirmText, cancelText, handleSubmit, handleCancel, confirmDisabled, hideCancel, hideFooter }) => {
|
||||
const ModalFooter = ({
|
||||
confirmText,
|
||||
cancelText,
|
||||
handleSubmit,
|
||||
handleCancel,
|
||||
confirmDisabled,
|
||||
hideCancel,
|
||||
hideFooter
|
||||
}) => {
|
||||
confirmText = confirmText || 'Save';
|
||||
cancelText = cancelText || 'Cancel';
|
||||
|
||||
@@ -30,7 +38,12 @@ const ModalFooter = ({ confirmText, cancelText, handleSubmit, handleCancel, conf
|
||||
</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>
|
||||
@@ -38,7 +51,18 @@ const ModalFooter = ({ confirmText, cancelText, handleSubmit, handleCancel, conf
|
||||
);
|
||||
};
|
||||
|
||||
const Modal = ({ size, title, confirmText, cancelText, handleCancel, handleConfirm, children, confirmDisabled, hideCancel, hideFooter }) => {
|
||||
const Modal = ({
|
||||
size,
|
||||
title,
|
||||
confirmText,
|
||||
cancelText,
|
||||
handleCancel,
|
||||
handleConfirm,
|
||||
children,
|
||||
confirmDisabled,
|
||||
hideCancel,
|
||||
hideFooter
|
||||
}) => {
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
const escFunction = (event) => {
|
||||
const escKeyCode = 27;
|
||||
@@ -64,7 +88,7 @@ const Modal = ({ size, title, confirmText, cancelText, handleCancel, handleConfi
|
||||
if (isClosing) {
|
||||
classes += ' modal--animate-out';
|
||||
}
|
||||
if(hideFooter) {
|
||||
if (hideFooter) {
|
||||
classes += ' modal-footer-none';
|
||||
}
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
color: ${(props) => props.theme.text};
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -0,0 +1,38 @@
|
||||
import React, { useState } from 'react';
|
||||
import { usePreferences } from 'providers/Preferences';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const General = () => {
|
||||
const { preferences, setPreferences } = usePreferences();
|
||||
|
||||
const [sslVerification, setSslVerification] = useState(preferences.request.sslVerification);
|
||||
|
||||
const handleCheckboxChange = () => {
|
||||
const updatedPreferences = {
|
||||
...preferences,
|
||||
request: {
|
||||
...preferences.request,
|
||||
sslVerification: !sslVerification
|
||||
}
|
||||
};
|
||||
|
||||
setPreferences(updatedPreferences)
|
||||
.then(() => {
|
||||
setSslVerification(!sslVerification);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="flex items-center mt-2">
|
||||
<input type="checkbox" checked={sslVerification} onChange={handleCheckboxChange} className="mr-3 mousetrap" />
|
||||
SSL Certificate Verification
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default General;
|
||||
@@ -0,0 +1,36 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
div.tabs {
|
||||
margin-top: -0.5rem;
|
||||
|
||||
div.tab {
|
||||
padding: 6px 0px;
|
||||
border: none;
|
||||
border-bottom: solid 2px transparent;
|
||||
margin-right: 1.25rem;
|
||||
color: var(--color-tab-inactive);
|
||||
cursor: pointer;
|
||||
|
||||
&:focus,
|
||||
&:active,
|
||||
&:focus-within,
|
||||
&:focus-visible,
|
||||
&:target {
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: ${(props) => props.theme.tabs.active.color} !important;
|
||||
border-bottom: solid 2px ${(props) => props.theme.tabs.active.border} !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section.tab-panel {
|
||||
min-height: 300px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -1,8 +1,8 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
color: var(--color-text);
|
||||
.collection-options {
|
||||
color: ${(props) => props.theme.text};
|
||||
.rows {
|
||||
svg {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { IconSpeakerphone, IconBrandTwitter, IconBrandGithub, IconBrandDiscord, IconBook } from '@tabler/icons';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const Support = () => {
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="rows">
|
||||
<div className="mt-2">
|
||||
<a href="https://docs.usebruno.com" target="_blank" className="flex items-end">
|
||||
<IconBook size={18} strokeWidth={2} />
|
||||
<span className="label ml-2">Documentation</span>
|
||||
</a>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<a href="https://github.com/usebruno/bruno/issues" target="_blank" className="flex items-end">
|
||||
<IconSpeakerphone size={18} strokeWidth={2} />
|
||||
<span className="label ml-2">Report Issues</span>
|
||||
</a>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<a href="https://discord.com/invite/KgcZUncpjq" target="_blank" className="flex items-end">
|
||||
<IconBrandDiscord size={18} strokeWidth={2} />
|
||||
<span className="label ml-2">Discord</span>
|
||||
</a>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<a href="https://github.com/usebruno/bruno" target="_blank" className="flex items-end">
|
||||
<IconBrandGithub size={18} strokeWidth={2} />
|
||||
<span className="label ml-2">Github</span>
|
||||
</a>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<a href="https://twitter.com/use_bruno" target="_blank" className="flex items-end">
|
||||
<IconBrandTwitter size={18} strokeWidth={2} />
|
||||
<span className="label ml-2">Twitter</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Support;
|
||||
@@ -0,0 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
color: var(--color-text);
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
64
packages/bruno-app/src/components/Preferences/Theme/index.js
Normal file
64
packages/bruno-app/src/components/Preferences/Theme/index.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import { useFormik } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
|
||||
const Theme = () => {
|
||||
const { storedTheme, setStoredTheme } = useTheme();
|
||||
|
||||
const formik = useFormik({
|
||||
enableReinitialize: true,
|
||||
initialValues: {
|
||||
theme: storedTheme
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
theme: Yup.string().oneOf(['light', 'dark']).required('theme is required')
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
setStoredTheme(values.theme);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="bruno-form">
|
||||
<div className="flex items-center mt-2">
|
||||
<input
|
||||
id="light-theme"
|
||||
className="cursor-pointer"
|
||||
type="radio"
|
||||
name="theme"
|
||||
onChange={(e) => {
|
||||
formik.handleChange(e);
|
||||
formik.handleSubmit();
|
||||
}}
|
||||
value="light"
|
||||
checked={formik.values.theme === 'light'}
|
||||
/>
|
||||
<label htmlFor="light-theme" className="ml-1 cursor-pointer select-none">
|
||||
Light
|
||||
</label>
|
||||
|
||||
<input
|
||||
id="dark-theme"
|
||||
className="ml-4 cursor-pointer"
|
||||
type="radio"
|
||||
name="theme"
|
||||
onChange={(e) => {
|
||||
formik.handleChange(e);
|
||||
formik.handleSubmit();
|
||||
}}
|
||||
value="dark"
|
||||
checked={formik.values.theme === 'dark'}
|
||||
/>
|
||||
<label htmlFor="dark-theme" className="ml-1 cursor-pointer select-none">
|
||||
Dark
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Theme;
|
||||
54
packages/bruno-app/src/components/Preferences/index.js
Normal file
54
packages/bruno-app/src/components/Preferences/index.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import Modal from 'components/Modal/index';
|
||||
import classnames from 'classnames';
|
||||
import React, { useState } from 'react';
|
||||
import Support from './Support';
|
||||
import General from './General';
|
||||
import Theme from './Theme';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const Preferences = ({ onClose }) => {
|
||||
const [tab, setTab] = useState('general');
|
||||
|
||||
const getTabClassname = (tabName) => {
|
||||
return classnames(`tab select-none ${tabName}`, {
|
||||
active: tabName === tab
|
||||
});
|
||||
};
|
||||
|
||||
const getTabPanel = (tab) => {
|
||||
switch (tab) {
|
||||
case 'general': {
|
||||
return <General />;
|
||||
}
|
||||
|
||||
case 'theme': {
|
||||
return <Theme />;
|
||||
}
|
||||
|
||||
case 'support': {
|
||||
return <Support />;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<Modal size="lg" title="Preferences" handleCancel={onClose} hideFooter={true}>
|
||||
<div className="flex items-center px-2 tabs" role="tablist">
|
||||
<div className={getTabClassname('general')} role="tab" onClick={() => setTab('general')}>
|
||||
General
|
||||
</div>
|
||||
<div className={getTabClassname('theme')} role="tab" onClick={() => setTab('theme')}>
|
||||
Theme
|
||||
</div>
|
||||
<div className={getTabClassname('support')} role="tab" onClick={() => setTab('support')}>
|
||||
Support
|
||||
</div>
|
||||
</div>
|
||||
<section className="flex flex-grow px-2 mt-4 tab-panel">{getTabPanel(tab)}</section>
|
||||
</Modal>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Preferences;
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
|
||||
/**
|
||||
* Assertion operators
|
||||
*
|
||||
*
|
||||
* eq : equal to
|
||||
* neq : not equal to
|
||||
* gt : greater than
|
||||
@@ -33,10 +33,32 @@ import React from 'react';
|
||||
|
||||
const AssertionOperator = ({ operator, onChange }) => {
|
||||
const operators = [
|
||||
'eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'in', 'notIn',
|
||||
'contains', 'notContains', 'length', 'matches', 'notMatches',
|
||||
'startsWith', 'endsWith', 'between', 'isEmpty', 'isNull', 'isUndefined',
|
||||
'isDefined', 'isTruthy', 'isFalsy', 'isJson', 'isNumber', 'isString', 'isBoolean'
|
||||
'eq',
|
||||
'neq',
|
||||
'gt',
|
||||
'gte',
|
||||
'lt',
|
||||
'lte',
|
||||
'in',
|
||||
'notIn',
|
||||
'contains',
|
||||
'notContains',
|
||||
'length',
|
||||
'matches',
|
||||
'notMatches',
|
||||
'startsWith',
|
||||
'endsWith',
|
||||
'between',
|
||||
'isEmpty',
|
||||
'isNull',
|
||||
'isUndefined',
|
||||
'isDefined',
|
||||
'isTruthy',
|
||||
'isFalsy',
|
||||
'isJson',
|
||||
'isNumber',
|
||||
'isString',
|
||||
'isBoolean'
|
||||
];
|
||||
|
||||
const handleChange = (e) => {
|
||||
@@ -44,7 +66,7 @@ const AssertionOperator = ({ operator, onChange }) => {
|
||||
};
|
||||
|
||||
const getLabel = (operator) => {
|
||||
switch(operator) {
|
||||
switch (operator) {
|
||||
case 'eq':
|
||||
return 'equals';
|
||||
case 'neq':
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useTheme } from 'providers/Theme';
|
||||
|
||||
/**
|
||||
* Assertion operators
|
||||
*
|
||||
*
|
||||
* eq : equal to
|
||||
* neq : not equal to
|
||||
* gt : greater than
|
||||
@@ -35,7 +35,7 @@ import { useTheme } from 'providers/Theme';
|
||||
* isBoolean : is boolean
|
||||
*/
|
||||
const parseAssertionOperator = (str = '') => {
|
||||
if(!str || typeof str !== 'string' || !str.length) {
|
||||
if (!str || typeof str !== 'string' || !str.length) {
|
||||
return {
|
||||
operator: 'eq',
|
||||
value: str
|
||||
@@ -43,27 +43,58 @@ const parseAssertionOperator = (str = '') => {
|
||||
}
|
||||
|
||||
const operators = [
|
||||
'eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'in', 'notIn',
|
||||
'contains', 'notContains', 'length', 'matches', 'notMatches',
|
||||
'startsWith', 'endsWith', 'between', 'isEmpty', 'isNull', 'isUndefined',
|
||||
'isDefined', 'isTruthy', 'isFalsy', 'isJson', 'isNumber', 'isString', 'isBoolean'
|
||||
'eq',
|
||||
'neq',
|
||||
'gt',
|
||||
'gte',
|
||||
'lt',
|
||||
'lte',
|
||||
'in',
|
||||
'notIn',
|
||||
'contains',
|
||||
'notContains',
|
||||
'length',
|
||||
'matches',
|
||||
'notMatches',
|
||||
'startsWith',
|
||||
'endsWith',
|
||||
'between',
|
||||
'isEmpty',
|
||||
'isNull',
|
||||
'isUndefined',
|
||||
'isDefined',
|
||||
'isTruthy',
|
||||
'isFalsy',
|
||||
'isJson',
|
||||
'isNumber',
|
||||
'isString',
|
||||
'isBoolean'
|
||||
];
|
||||
|
||||
const unaryOperators = [
|
||||
'isEmpty', 'isNull', 'isUndefined', 'isDefined', 'isTruthy', 'isFalsy', 'isJson', 'isNumber', 'isString', 'isBoolean'
|
||||
'isEmpty',
|
||||
'isNull',
|
||||
'isUndefined',
|
||||
'isDefined',
|
||||
'isTruthy',
|
||||
'isFalsy',
|
||||
'isJson',
|
||||
'isNumber',
|
||||
'isString',
|
||||
'isBoolean'
|
||||
];
|
||||
|
||||
const [operator, ...rest] = str.trim().split(' ');
|
||||
const value = rest.join(' ');
|
||||
|
||||
if(unaryOperators.includes(operator)) {
|
||||
if (unaryOperators.includes(operator)) {
|
||||
return {
|
||||
operator,
|
||||
value: ''
|
||||
};
|
||||
}
|
||||
|
||||
if(operators.includes(operator)) {
|
||||
if (operators.includes(operator)) {
|
||||
return {
|
||||
operator,
|
||||
value
|
||||
@@ -78,22 +109,33 @@ const parseAssertionOperator = (str = '') => {
|
||||
|
||||
const isUnaryOperator = (operator) => {
|
||||
const unaryOperators = [
|
||||
'isEmpty', 'isNull', 'isUndefined', 'isDefined', 'isTruthy', 'isFalsy', 'isJson', 'isNumber', 'isString', 'isBoolean'
|
||||
'isEmpty',
|
||||
'isNull',
|
||||
'isUndefined',
|
||||
'isDefined',
|
||||
'isTruthy',
|
||||
'isFalsy',
|
||||
'isJson',
|
||||
'isNumber',
|
||||
'isString',
|
||||
'isBoolean'
|
||||
];
|
||||
|
||||
return unaryOperators.includes(operator);
|
||||
};
|
||||
|
||||
const AssertionRow = ({
|
||||
item, collection, assertion, handleAssertionChange, handleRemoveAssertion,
|
||||
onSave, handleRun
|
||||
item,
|
||||
collection,
|
||||
assertion,
|
||||
handleAssertionChange,
|
||||
handleRemoveAssertion,
|
||||
onSave,
|
||||
handleRun
|
||||
}) => {
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const {
|
||||
operator,
|
||||
value
|
||||
} = parseAssertionOperator(assertion.value);
|
||||
const { operator, value } = parseAssertionOperator(assertion.value);
|
||||
|
||||
return (
|
||||
<tr key={assertion.uid}>
|
||||
@@ -112,11 +154,17 @@ const AssertionRow = ({
|
||||
<td>
|
||||
<AssertionOperator
|
||||
operator={operator}
|
||||
onChange={(op) => handleAssertionChange({
|
||||
target: {
|
||||
value: `${op} ${value}`
|
||||
}
|
||||
}, assertion, 'value')}
|
||||
onChange={(op) =>
|
||||
handleAssertionChange(
|
||||
{
|
||||
target: {
|
||||
value: `${op} ${value}`
|
||||
}
|
||||
},
|
||||
assertion,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
@@ -126,20 +174,22 @@ const AssertionRow = ({
|
||||
theme={storedTheme}
|
||||
readOnly={true}
|
||||
onSave={onSave}
|
||||
onChange={(newValue) => handleAssertionChange({
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
}, assertion, 'value')}
|
||||
onChange={(newValue) =>
|
||||
handleAssertionChange(
|
||||
{
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
},
|
||||
assertion,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
type="text"
|
||||
className='cursor-default'
|
||||
disabled
|
||||
/>
|
||||
<input type="text" className="cursor-default" disabled />
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@@ -27,6 +27,10 @@ const Wrapper = styled.div`
|
||||
&:nth-child(4) {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
select {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,11 @@ import cloneDeep from 'lodash/cloneDeep';
|
||||
import { IconTrash } from '@tabler/icons';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { addFormUrlEncodedParam, updateFormUrlEncodedParam, deleteFormUrlEncodedParam } from 'providers/ReduxStore/slices/collections';
|
||||
import {
|
||||
addFormUrlEncodedParam,
|
||||
updateFormUrlEncodedParam,
|
||||
deleteFormUrlEncodedParam
|
||||
} from 'providers/ReduxStore/slices/collections';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
@@ -88,22 +92,33 @@ const FormUrlEncodedParams = ({ item, collection }) => {
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<SingleLineEditor
|
||||
<SingleLineEditor
|
||||
value={param.value}
|
||||
theme={storedTheme}
|
||||
onSave={onSave}
|
||||
onChange={(newValue) => handleParamChange({
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
}, param, 'value')}
|
||||
onChange={(newValue) =>
|
||||
handleParamChange(
|
||||
{
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
},
|
||||
param,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<input type="checkbox" checked={param.enabled} className="mr-3 mousetrap" onChange={(e) => handleParamChange(e, param, 'enabled')} />
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={param.enabled}
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'enabled')}
|
||||
/>
|
||||
<button onClick={() => handleRemoveParams(param)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
|
||||
@@ -24,29 +24,24 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
|
||||
const tabs = useSelector((state) => state.tabs.tabs);
|
||||
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
|
||||
const query = item.draft ? get(item, 'draft.request.body.graphql.query') : get(item, 'request.body.graphql.query');
|
||||
const variables = item.draft ? get(item, 'draft.request.body.graphql.variables') : get(item, 'request.body.graphql.variables');
|
||||
const variables = item.draft
|
||||
? get(item, 'draft.request.body.graphql.variables')
|
||||
: get(item, 'request.body.graphql.variables');
|
||||
const url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
|
||||
const {
|
||||
storedTheme
|
||||
} = useTheme();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
|
||||
|
||||
let {
|
||||
schema,
|
||||
loadSchema,
|
||||
isLoading: isSchemaLoading,
|
||||
error: schemaError
|
||||
} = useGraphqlSchema(url, environment);
|
||||
|
||||
let { schema, loadSchema, isLoading: isSchemaLoading, error: schemaError } = useGraphqlSchema(url, environment);
|
||||
|
||||
const loadGqlSchema = () => {
|
||||
if(!isSchemaLoading) {
|
||||
if (!isSchemaLoading) {
|
||||
loadSchema();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if(onSchemaLoad) {
|
||||
if (onSchemaLoad) {
|
||||
onSchemaLoad(schema);
|
||||
}
|
||||
}, [schema]);
|
||||
@@ -75,17 +70,19 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
|
||||
const getTabPanel = (tab) => {
|
||||
switch (tab) {
|
||||
case 'query': {
|
||||
return <QueryEditor
|
||||
collection={collection}
|
||||
theme={storedTheme}
|
||||
schema={schema}
|
||||
width={leftPaneWidth}
|
||||
onSave={onSave}
|
||||
value={query}
|
||||
onRun={onRun}
|
||||
onEdit={onQueryChange}
|
||||
onClickReference={handleGqlClickReference}
|
||||
/>;
|
||||
return (
|
||||
<QueryEditor
|
||||
collection={collection}
|
||||
theme={storedTheme}
|
||||
schema={schema}
|
||||
width={leftPaneWidth}
|
||||
onSave={onSave}
|
||||
value={query}
|
||||
onRun={onRun}
|
||||
onEdit={onQueryChange}
|
||||
onClickReference={handleGqlClickReference}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'variables': {
|
||||
return <GraphQLVariables item={item} variables={variables} collection={collection} />;
|
||||
@@ -150,20 +147,16 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
|
||||
<div className={getTabClassname('tests')} role="tab" onClick={() => selectTab('tests')}>
|
||||
Tests
|
||||
</div>
|
||||
<div className="flex flex-grow justify-end items-center" style={{fontSize: 13}}>
|
||||
<div className='flex items-center cursor-pointer hover:underline' onClick={loadGqlSchema}>
|
||||
{isSchemaLoading ? (
|
||||
<IconLoader2 className="animate-spin" size={18} strokeWidth={1.5}/>
|
||||
) : null}
|
||||
{!isSchemaLoading && !schema ? <IconDownload size={18} strokeWidth={1.5}/> : null }
|
||||
{!isSchemaLoading && schema ? <IconRefresh size={18} strokeWidth={1.5}/> : null }
|
||||
<span className='ml-1'>Schema</span>
|
||||
<div className="flex flex-grow justify-end items-center" style={{ fontSize: 13 }}>
|
||||
<div className="flex items-center cursor-pointer hover:underline" onClick={loadGqlSchema}>
|
||||
{isSchemaLoading ? <IconLoader2 className="animate-spin" size={18} strokeWidth={1.5} /> : null}
|
||||
{!isSchemaLoading && !schema ? <IconDownload size={18} strokeWidth={1.5} /> : null}
|
||||
{!isSchemaLoading && schema ? <IconRefresh size={18} strokeWidth={1.5} /> : null}
|
||||
<span className="ml-1">Schema</span>
|
||||
</div>
|
||||
<div
|
||||
className='flex items-center cursor-pointer hover:underline ml-2'
|
||||
onClick={toggleDocs}
|
||||
>
|
||||
<IconBook size={18} strokeWidth={1.5} /><span className='ml-1'>Docs</span>
|
||||
<div className="flex items-center cursor-pointer hover:underline ml-2" onClick={toggleDocs}>
|
||||
<IconBook size={18} strokeWidth={1.5} />
|
||||
<span className="ml-1">Docs</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@ const useGraphqlSchema = (endpoint, environment) => {
|
||||
const [schema, setSchema] = useState(() => {
|
||||
try {
|
||||
const saved = localStorage.getItem(localStorageKey);
|
||||
if(!saved) {
|
||||
if (!saved) {
|
||||
return null;
|
||||
}
|
||||
return buildClientSchema(JSON.parse(saved));
|
||||
|
||||
@@ -9,9 +9,7 @@ import StyledWrapper from './StyledWrapper';
|
||||
const GraphQLVariables = ({ variables, item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const {
|
||||
storedTheme
|
||||
} = useTheme();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const onEdit = (value) => {
|
||||
dispatch(
|
||||
@@ -29,10 +27,11 @@ const GraphQLVariables = ({ variables, item, collection }) => {
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<CodeEditor
|
||||
collection={collection} value={variables || ''}
|
||||
collection={collection}
|
||||
value={variables || ''}
|
||||
theme={storedTheme}
|
||||
onEdit={onEdit}
|
||||
mode='javascript'
|
||||
mode="javascript"
|
||||
onRun={onRun}
|
||||
onSave={onSave}
|
||||
/>
|
||||
|
||||
@@ -103,7 +103,9 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<section className={`flex w-full ${['script', 'vars'].includes(focusedTab.requestPaneTab) ? '' : 'mt-5'}`}>{getTabPanel(focusedTab.requestPaneTab)}</section>
|
||||
<section className={`flex w-full ${['script', 'vars'].includes(focusedTab.requestPaneTab) ? '' : 'mt-5'}`}>
|
||||
{getTabPanel(focusedTab.requestPaneTab)}
|
||||
</section>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -41,7 +41,6 @@ const Wrapper = styled.div`
|
||||
color: ${(props) => props.theme.table.input.color};
|
||||
background: transparent;
|
||||
|
||||
|
||||
&:focus {
|
||||
outline: none !important;
|
||||
border: solid 1px transparent;
|
||||
|
||||
@@ -4,7 +4,11 @@ import cloneDeep from 'lodash/cloneDeep';
|
||||
import { IconTrash } from '@tabler/icons';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { addMultipartFormParam, updateMultipartFormParam, deleteMultipartFormParam } from 'providers/ReduxStore/slices/collections';
|
||||
import {
|
||||
addMultipartFormParam,
|
||||
updateMultipartFormParam,
|
||||
deleteMultipartFormParam
|
||||
} from 'providers/ReduxStore/slices/collections';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
@@ -88,22 +92,33 @@ const MultipartFormParams = ({ item, collection }) => {
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<SingleLineEditor
|
||||
<SingleLineEditor
|
||||
onSave={onSave}
|
||||
theme={storedTheme}
|
||||
value={param.value}
|
||||
onChange={(newValue) => handleParamChange({
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
}, param, 'value')}
|
||||
value={param.value}
|
||||
onChange={(newValue) =>
|
||||
handleParamChange(
|
||||
{
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
},
|
||||
param,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<input type="checkbox" checked={param.enabled} className="mr-3 mousetrap" onChange={(e) => handleParamChange(e, param, 'enabled')} />
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={param.enabled}
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'enabled')}
|
||||
/>
|
||||
<button onClick={() => handleRemoveParams(param)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
|
||||
@@ -15,16 +15,19 @@ const StyledWrapper = styled.div`
|
||||
// Todo: dark mode temporary fix
|
||||
// Clean this
|
||||
.CodeMirror.cm-s-monokai {
|
||||
.CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-vertical div {
|
||||
.CodeMirror-overlayscroll-horizontal div,
|
||||
.CodeMirror-overlayscroll-vertical div {
|
||||
background: #444444;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute {
|
||||
.cm-s-monokai span.cm-property,
|
||||
.cm-s-monokai span.cm-attribute {
|
||||
color: #9cdcfe !important;
|
||||
}
|
||||
|
||||
.cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute {
|
||||
.cm-s-monokai span.cm-property,
|
||||
.cm-s-monokai span.cm-attribute {
|
||||
color: #9cdcfe !important;
|
||||
}
|
||||
|
||||
@@ -32,16 +35,20 @@ const StyledWrapper = styled.div`
|
||||
color: #ce9178 !important;
|
||||
}
|
||||
|
||||
.cm-s-monokai span.cm-number{
|
||||
.cm-s-monokai span.cm-number {
|
||||
color: #b5cea8 !important;
|
||||
}
|
||||
|
||||
.cm-s-monokai span.cm-atom{
|
||||
.cm-s-monokai span.cm-atom {
|
||||
color: #569cd6 !important;
|
||||
}
|
||||
|
||||
.cm-variable-valid{color: green}
|
||||
.cm-variable-invalid{color: red}
|
||||
.cm-variable-valid {
|
||||
color: green;
|
||||
}
|
||||
.cm-variable-invalid {
|
||||
color: red;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -43,7 +43,7 @@ export default class QueryEditor extends React.Component {
|
||||
mode: 'graphql',
|
||||
// mode: 'brunovariables',
|
||||
brunoVarInfo: {
|
||||
variables: getAllVariables(this.props.collection),
|
||||
variables: getAllVariables(this.props.collection)
|
||||
},
|
||||
theme: this.props.editorTheme || 'graphiql',
|
||||
theme: this.props.theme === 'dark' ? 'monokai' : 'default',
|
||||
@@ -51,7 +51,7 @@ export default class QueryEditor extends React.Component {
|
||||
autoCloseBrackets: true,
|
||||
matchBrackets: true,
|
||||
showCursorWhenSelecting: true,
|
||||
scrollbarStyle: "overlay",
|
||||
scrollbarStyle: 'overlay',
|
||||
readOnly: this.props.readOnly ? 'nocursor' : false,
|
||||
foldGutter: {
|
||||
minFoldSize: 4
|
||||
@@ -179,14 +179,13 @@ export default class QueryEditor extends React.Component {
|
||||
}
|
||||
|
||||
// Todo: Overlay is messing up with schema hint
|
||||
// Fix this
|
||||
// Fix this
|
||||
addOverlay = () => {
|
||||
// let variables = getAllVariables(this.props.collection);
|
||||
// this.variables = variables;
|
||||
|
||||
// defineCodeMirrorBrunoVariablesMode(variables, 'graphql');
|
||||
// this.editor.setOption('mode', 'brunovariables');
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
||||
@@ -59,7 +59,10 @@ export default function onHasCompletion(_cm, data, onHintInformationRender) {
|
||||
const description = ctx.description ? md.render(ctx.description) : 'Self descriptive.';
|
||||
const type = ctx.type ? '<span className="infoType">' + renderType(ctx.type) + '</span>' : '';
|
||||
|
||||
information.innerHTML = '<div className="content">' + (description.slice(0, 3) === '<p>' ? '<p>' + type + description.slice(3) : type + description) + '</div>';
|
||||
information.innerHTML =
|
||||
'<div className="content">' +
|
||||
(description.slice(0, 3) === '<p>' ? '<p>' + type + description.slice(3) : type + description) +
|
||||
'</div>';
|
||||
|
||||
if (ctx && deprecation && ctx.deprecationReason) {
|
||||
const reason = ctx.deprecationReason ? md.render(ctx.deprecationReason) : '';
|
||||
|
||||
@@ -13,7 +13,7 @@ const Wrapper = styled.div`
|
||||
}
|
||||
|
||||
thead {
|
||||
color: ${(props) => props.theme.table.thead.color};;
|
||||
color: ${(props) => props.theme.table.thead.color};
|
||||
font-size: 0.8125rem;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@@ -91,22 +91,33 @@ const QueryParams = ({ item, collection }) => {
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<SingleLineEditor
|
||||
<SingleLineEditor
|
||||
value={param.value}
|
||||
theme={storedTheme}
|
||||
onSave={onSave}
|
||||
onChange={(newValue) => handleParamChange({
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
}, param, 'value')}
|
||||
onChange={(newValue) =>
|
||||
handleParamChange(
|
||||
{
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
},
|
||||
param,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<input type="checkbox" checked={param.enabled} className="mr-3 mousetrap" onChange={(e) => handleParamChange(e, param, 'enabled')} />
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={param.enabled}
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleParamChange(e, param, 'enabled')}
|
||||
/>
|
||||
<button onClick={() => handleRemoveParam(param)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
|
||||
@@ -10,7 +10,9 @@ const HttpMethodSelector = ({ method, onMethodSelect }) => {
|
||||
const Icon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="flex w-full items-center pl-3 py-1 select-none uppercase">
|
||||
<div className="flex-grow font-medium" id="create-new-request-method">{method}</div>
|
||||
<div className="flex-grow font-medium" id="create-new-request-method">
|
||||
{method}
|
||||
</div>
|
||||
<div>
|
||||
<IconCaretDown className="caret ml-2 mr-2" size={14} strokeWidth={2} />
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect} from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import get from 'lodash/get';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { requestUrlChanged, updateRequestMethod } from 'providers/ReduxStore/slices/collections';
|
||||
@@ -18,7 +18,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
const [methodSelectorWidth, setMethodSelectorWidth] = useState(90);
|
||||
|
||||
useEffect(() => {
|
||||
const el = document.querySelector(".method-selector-container");
|
||||
const el = document.querySelector('.method-selector-container');
|
||||
setMethodSelectorWidth(el.offsetWidth);
|
||||
}, [method]);
|
||||
|
||||
@@ -56,8 +56,8 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
maxWidth: `calc(100% - ${methodSelectorWidth}px)`
|
||||
}}
|
||||
>
|
||||
<SingleLineEditor
|
||||
value={url}
|
||||
<SingleLineEditor
|
||||
value={url}
|
||||
onSave={onSave}
|
||||
theme={storedTheme}
|
||||
onChange={(newValue) => onUrlChange(newValue)}
|
||||
@@ -65,7 +65,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
||||
collection={collection}
|
||||
/>
|
||||
<div className="flex items-center h-full mr-2 cursor-pointer" id="send-request" onClick={handleRun}>
|
||||
<SendIcon color={theme.requestTabPanel.url.icon} width={22}/>
|
||||
<SendIcon color={theme.requestTabPanel.url.icon} width={22} />
|
||||
</div>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
|
||||
@@ -13,9 +13,7 @@ const RequestBody = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const body = item.draft ? get(item, 'draft.request.body') : get(item, 'request.body');
|
||||
const bodyMode = item.draft ? get(item, 'draft.request.body.mode') : get(item, 'request.body.mode');
|
||||
const {
|
||||
storedTheme
|
||||
} = useTheme();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const onEdit = (value) => {
|
||||
dispatch(
|
||||
@@ -45,7 +43,15 @@ const RequestBody = ({ item, collection }) => {
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<CodeEditor collection={collection} theme={storedTheme} value={bodyContent[bodyMode] || ''} onEdit={onEdit} onRun={onRun} onSave={onSave} mode={codeMirrorMode[bodyMode]} />
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
theme={storedTheme}
|
||||
value={bodyContent[bodyMode] || ''}
|
||||
onEdit={onEdit}
|
||||
onRun={onRun}
|
||||
onSave={onSave}
|
||||
mode={codeMirrorMode[bodyMode]}
|
||||
/>
|
||||
</StyledWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -72,19 +72,27 @@ const RequestHeaders = ({ item, collection }) => {
|
||||
</thead>
|
||||
<tbody>
|
||||
{headers && headers.length
|
||||
? headers.map((header, index) => {
|
||||
? headers.map((header) => {
|
||||
return (
|
||||
<tr key={header.uid}>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
<SingleLineEditor
|
||||
value={header.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleHeaderValueChange(e, header, 'name')}
|
||||
theme={storedTheme}
|
||||
onSave={onSave}
|
||||
onChange={(newValue) =>
|
||||
handleHeaderValueChange(
|
||||
{
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
},
|
||||
header,
|
||||
'name'
|
||||
)
|
||||
}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
@@ -92,18 +100,29 @@ const RequestHeaders = ({ item, collection }) => {
|
||||
value={header.value}
|
||||
theme={storedTheme}
|
||||
onSave={onSave}
|
||||
onChange={(newValue) => handleHeaderValueChange({
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
}, header, 'value')}
|
||||
onChange={(newValue) =>
|
||||
handleHeaderValueChange(
|
||||
{
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
},
|
||||
header,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center">
|
||||
<input type="checkbox" checked={header.enabled} className="mr-3 mousetrap" onChange={(e) => handleHeaderValueChange(e, header, 'enabled')} />
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={header.enabled}
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleHeaderValueChange(e, header, 'enabled')}
|
||||
/>
|
||||
<button onClick={() => handleRemoveHeader(header)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
|
||||
@@ -28,7 +28,14 @@ const SaveRequest = ({ items, onClose }) => {
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<Modal size="md" title="Save Request" confirmText="Save" cancelText="Cancel" handleCancel={onClose} handleConfirm={onClose}>
|
||||
<Modal
|
||||
size="md"
|
||||
title="Save Request"
|
||||
confirmText="Save"
|
||||
cancelText="Cancel"
|
||||
handleCancel={onClose}
|
||||
handleConfirm={onClose}
|
||||
>
|
||||
<p className="mb-2">Select a folder to save request:</p>
|
||||
<div className="folder-list">
|
||||
{showFolders && showFolders.length
|
||||
|
||||
@@ -12,9 +12,7 @@ const Script = ({ item, collection }) => {
|
||||
const requestScript = item.draft ? get(item, 'draft.request.script.req') : get(item, 'request.script.req');
|
||||
const responseScript = item.draft ? get(item, 'draft.request.script.res') : get(item, 'request.script.res');
|
||||
|
||||
const {
|
||||
storedTheme
|
||||
} = useTheme();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const onRequestScriptEdit = (value) => {
|
||||
dispatch(
|
||||
@@ -29,36 +27,38 @@ const Script = ({ item, collection }) => {
|
||||
const onResponseScriptEdit = (value) => {
|
||||
dispatch(
|
||||
updateResponseScript({
|
||||
script: value,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
script: value,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const onRun = () => dispatch(sendRequest(item, collection.uid));
|
||||
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full flex flex-col">
|
||||
<div className='flex-1 mt-2'>
|
||||
<div className='mb-1 title text-xs'>Pre Request</div>
|
||||
<div className="flex-1 mt-2">
|
||||
<div className="mb-1 title text-xs">Pre Request</div>
|
||||
<CodeEditor
|
||||
collection={collection} value={requestScript || ''}
|
||||
collection={collection}
|
||||
value={requestScript || ''}
|
||||
theme={storedTheme}
|
||||
onEdit={onRequestScriptEdit}
|
||||
mode='javascript'
|
||||
mode="javascript"
|
||||
onRun={onRun}
|
||||
onSave={onSave}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex-1 mt-6'>
|
||||
<div className='mt-1 mb-1 title text-xs'>Post Response</div>
|
||||
<div className="flex-1 mt-6">
|
||||
<div className="mt-1 mb-1 title text-xs">Post Response</div>
|
||||
<CodeEditor
|
||||
collection={collection} value={responseScript || ''}
|
||||
collection={collection}
|
||||
value={responseScript || ''}
|
||||
theme={storedTheme}
|
||||
onEdit={onResponseScriptEdit}
|
||||
mode='javascript'
|
||||
mode="javascript"
|
||||
onRun={onRun}
|
||||
onSave={onSave}
|
||||
/>
|
||||
|
||||
@@ -11,9 +11,7 @@ const Tests = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const tests = item.draft ? get(item, 'draft.request.tests') : get(item, 'request.tests');
|
||||
|
||||
const {
|
||||
storedTheme
|
||||
} = useTheme();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const onEdit = (value) => {
|
||||
dispatch(
|
||||
@@ -31,10 +29,11 @@ const Tests = ({ item, collection }) => {
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<CodeEditor
|
||||
collection={collection} value={tests || ''}
|
||||
collection={collection}
|
||||
value={tests || ''}
|
||||
theme={storedTheme}
|
||||
onEdit={onEdit}
|
||||
mode='javascript'
|
||||
mode="javascript"
|
||||
onRun={onRun}
|
||||
onSave={onSave}
|
||||
/>
|
||||
|
||||
@@ -68,18 +68,18 @@ const VarsTable = ({ item, collection, vars, varType }) => {
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
{ varType === 'request' ? (
|
||||
{varType === 'request' ? (
|
||||
<td>
|
||||
<div className='flex items-center'>
|
||||
<div className="flex items-center">
|
||||
<span>Value</span>
|
||||
<Tooltip text="You can write any valid JS Template Literal here" tooltipId="request-var"/>
|
||||
<Tooltip text="You can write any valid JS Template Literal here" tooltipId="request-var" />
|
||||
</div>
|
||||
</td>
|
||||
) : (
|
||||
) : (
|
||||
<td>
|
||||
<div className='flex items-center'>
|
||||
<div className="flex items-center">
|
||||
<span>Expr</span>
|
||||
<Tooltip text="You can write any valid JS expression here" tooltipId="response-var"/>
|
||||
<Tooltip text="You can write any valid JS expression here" tooltipId="response-var" />
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
@@ -108,11 +108,17 @@ const VarsTable = ({ item, collection, vars, varType }) => {
|
||||
value={_var.value}
|
||||
theme={storedTheme}
|
||||
onSave={onSave}
|
||||
onChange={(newValue) => handleVarChange({
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
}, _var, 'value')}
|
||||
onChange={(newValue) =>
|
||||
handleVarChange(
|
||||
{
|
||||
target: {
|
||||
value: newValue
|
||||
}
|
||||
},
|
||||
_var,
|
||||
'value'
|
||||
)
|
||||
}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
/>
|
||||
|
||||
@@ -12,9 +12,7 @@ const Vars = ({ item, collection }) => {
|
||||
const requestVars = item.draft ? get(item, 'draft.request.vars.req') : get(item, 'request.vars.req');
|
||||
const responseVars = item.draft ? get(item, 'draft.request.vars.res') : get(item, 'request.vars.res');
|
||||
|
||||
const {
|
||||
storedTheme
|
||||
} = useTheme();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const onRequestScriptEdit = (value) => {
|
||||
dispatch(
|
||||
@@ -29,25 +27,25 @@ const Vars = ({ item, collection }) => {
|
||||
const onResponseScriptEdit = (value) => {
|
||||
dispatch(
|
||||
updateResponseScript({
|
||||
script: value,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
script: value,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const onRun = () => dispatch(sendRequest(item, collection.uid));
|
||||
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full flex flex-col">
|
||||
<div className='flex-1 mt-2'>
|
||||
<div className='mb-1 title text-xs'>Pre Request</div>
|
||||
<VarsTable item={item} collection={collection} vars={requestVars} varType='request'/>
|
||||
<div className="flex-1 mt-2">
|
||||
<div className="mb-1 title text-xs">Pre Request</div>
|
||||
<VarsTable item={item} collection={collection} vars={requestVars} varType="request" />
|
||||
</div>
|
||||
<div className='flex-1'>
|
||||
<div className='mt-1 mb-1 title text-xs'>Post Response</div>
|
||||
<VarsTable item={item} collection={collection} vars={responseVars} varType='response'/>
|
||||
<div className="flex-1">
|
||||
<div className="mt-1 mb-1 title text-xs">Post Response</div>
|
||||
<VarsTable item={item} collection={collection} vars={responseVars} varType="response" />
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@ const RequestNotFound = ({ itemUid }) => {
|
||||
// and then shows the error message after a delay
|
||||
// this will prevent the error message from flashing on the screen
|
||||
|
||||
if(!showErrorMessage) {
|
||||
if (!showErrorMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,9 @@ const RequestNotFound = ({ itemUid }) => {
|
||||
<div className="mt-6 px-6">
|
||||
<div className="p-4 bg-orange-100 border-l-4 border-yellow-500 text-yellow-700 bg-yellow-100 p-4">
|
||||
<div>Request no longer exists.</div>
|
||||
<div className="mt-2">This can happen when the .bru file associated with this request was deleted on your filesystem.</div>
|
||||
<div className="mt-2">
|
||||
This can happen when the .bru file associated with this request was deleted on your filesystem.
|
||||
</div>
|
||||
</div>
|
||||
<button className="btn btn-md btn-secondary mt-6" onClick={closeTab}>
|
||||
Close Tab
|
||||
|
||||
@@ -13,6 +13,8 @@ import RequestNotFound from './RequestNotFound';
|
||||
import QueryUrl from 'components/RequestPane/QueryUrl';
|
||||
import NetworkError from 'components/ResponsePane/NetworkError';
|
||||
import RunnerResults from 'components/RunnerResults';
|
||||
import VariablesEditor from 'components/VariablesEditor';
|
||||
import CollectionSettings from 'components/CollectionSettings';
|
||||
import { DocExplorer } from '@usebruno/graphql-docs';
|
||||
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
@@ -33,7 +35,9 @@ const RequestTabPanel = () => {
|
||||
|
||||
let asideWidth = useSelector((state) => state.app.leftSidebarWidth);
|
||||
const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
|
||||
const [leftPaneWidth, setLeftPaneWidth] = useState(focusedTab && focusedTab.requestPaneWidth ? focusedTab.requestPaneWidth : (screenWidth - asideWidth) / 2.2); // 2.2 so that request pane is relatively smaller
|
||||
const [leftPaneWidth, setLeftPaneWidth] = useState(
|
||||
focusedTab && focusedTab.requestPaneWidth ? focusedTab.requestPaneWidth : (screenWidth - asideWidth) / 2.2
|
||||
); // 2.2 so that request pane is relatively smaller
|
||||
const [rightPaneWidth, setRightPaneWidth] = useState(screenWidth - asideWidth - leftPaneWidth - DEFAULT_PADDING);
|
||||
const [dragging, setDragging] = useState(false);
|
||||
|
||||
@@ -45,10 +49,10 @@ const RequestTabPanel = () => {
|
||||
const onSchemaLoad = (schema) => setSchema(schema);
|
||||
const toggleDocs = () => setShowGqlDocs((showGqlDocs) => !showGqlDocs);
|
||||
const handleGqlClickReference = (reference) => {
|
||||
if(docExplorerRef.current) {
|
||||
if (docExplorerRef.current) {
|
||||
docExplorerRef.current.showDocForReference(reference);
|
||||
}
|
||||
if(!showGqlDocs) {
|
||||
if (!showGqlDocs) {
|
||||
setShowGqlDocs(true);
|
||||
}
|
||||
};
|
||||
@@ -66,10 +70,13 @@ const RequestTabPanel = () => {
|
||||
if (dragging) {
|
||||
e.preventDefault();
|
||||
let leftPaneXPosition = e.clientX + 2;
|
||||
if (leftPaneXPosition < (asideWidth+ DEFAULT_PADDING + MIN_LEFT_PANE_WIDTH) || leftPaneXPosition > (screenWidth - MIN_RIGHT_PANE_WIDTH )) {
|
||||
if (
|
||||
leftPaneXPosition < asideWidth + DEFAULT_PADDING + MIN_LEFT_PANE_WIDTH ||
|
||||
leftPaneXPosition > screenWidth - MIN_RIGHT_PANE_WIDTH
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setLeftPaneWidth(leftPaneXPosition- asideWidth);
|
||||
setLeftPaneWidth(leftPaneXPosition - asideWidth);
|
||||
setRightPaneWidth(screenWidth - e.clientX - DEFAULT_PADDING);
|
||||
}
|
||||
};
|
||||
@@ -113,9 +120,16 @@ const RequestTabPanel = () => {
|
||||
return <div className="pb-4 px-4">Collection not found!</div>;
|
||||
}
|
||||
|
||||
const showRunner = collection.showRunner;
|
||||
if(showRunner) {
|
||||
return <RunnerResults collection={collection}/>;
|
||||
if (focusedTab.type === 'collection-runner') {
|
||||
return <RunnerResults collection={collection} />;
|
||||
}
|
||||
|
||||
if (focusedTab.type === 'variables') {
|
||||
return <VariablesEditor collection={collection} />;
|
||||
}
|
||||
|
||||
if (focusedTab.type === 'collection-settings') {
|
||||
return <CollectionSettings collection={collection} />;
|
||||
}
|
||||
|
||||
const item = findItemInCollection(collection, activeTabUid);
|
||||
@@ -138,7 +152,13 @@ const RequestTabPanel = () => {
|
||||
</div>
|
||||
<section className="main flex flex-grow pb-4 relative">
|
||||
<section className="request-pane">
|
||||
<div className="px-4" style={{ width: `${Math.max(leftPaneWidth, MIN_LEFT_PANE_WIDTH)}px`, height: `calc(100% - ${DEFAULT_PADDING}px)` }}>
|
||||
<div
|
||||
className="px-4"
|
||||
style={{
|
||||
width: `${Math.max(leftPaneWidth, MIN_LEFT_PANE_WIDTH)}px`,
|
||||
height: `calc(100% - ${DEFAULT_PADDING}px)`
|
||||
}}
|
||||
>
|
||||
{item.type === 'graphql-request' ? (
|
||||
<GraphQLRequestPane
|
||||
item={item}
|
||||
@@ -150,7 +170,9 @@ 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>
|
||||
|
||||
@@ -165,19 +187,15 @@ const RequestTabPanel = () => {
|
||||
|
||||
{item.type === 'graphql-request' ? (
|
||||
<div className={`graphql-docs-explorer-container ${showGqlDocs ? '' : 'hidden'}`}>
|
||||
<DocExplorer schema={schema} ref={(r) => docExplorerRef.current = r}>
|
||||
<button
|
||||
className='mr-2'
|
||||
onClick={toggleDocs}
|
||||
aria-label="Close Documentation Explorer"
|
||||
>
|
||||
<DocExplorer schema={schema} ref={(r) => (docExplorerRef.current = r)}>
|
||||
<button className="mr-2" onClick={toggleDocs} aria-label="Close Documentation Explorer">
|
||||
{'\u2715'}
|
||||
</button>
|
||||
</DocExplorer>
|
||||
</div>
|
||||
): null}
|
||||
) : null}
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default RequestTabPanel;
|
||||
export default RequestTabPanel;
|
||||
|
||||
@@ -1,18 +1,42 @@
|
||||
import React from 'react';
|
||||
import { IconFiles, IconRun } from '@tabler/icons';
|
||||
import { uuid } from 'utils/common';
|
||||
import { IconFiles, IconRun, IconEye, IconSettings } from '@tabler/icons';
|
||||
import EnvironmentSelector from 'components/Environments/EnvironmentSelector';
|
||||
import VariablesView from 'components/VariablesView';
|
||||
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { toggleRunnerView } from 'providers/ReduxStore/slices/collections';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const CollectionToolBar = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleRun = () => {
|
||||
dispatch(toggleRunnerView({
|
||||
collectionUid: collection.uid
|
||||
}));
|
||||
dispatch(
|
||||
addTab({
|
||||
uid: uuid(),
|
||||
collectionUid: collection.uid,
|
||||
type: 'collection-runner'
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const viewVariables = () => {
|
||||
dispatch(
|
||||
addTab({
|
||||
uid: uuid(),
|
||||
collectionUid: collection.uid,
|
||||
type: 'variables'
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const viewCollectionSettings = () => {
|
||||
dispatch(
|
||||
addTab({
|
||||
uid: uuid(),
|
||||
collectionUid: collection.uid,
|
||||
type: 'collection-settings'
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -26,7 +50,12 @@ const CollectionToolBar = ({ collection }) => {
|
||||
<span className="mr-2">
|
||||
<IconRun className="cursor-pointer" size={20} strokeWidth={1.5} onClick={handleRun} />
|
||||
</span>
|
||||
<VariablesView collection={collection}/>
|
||||
<span className="mr-3">
|
||||
<IconEye className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewVariables} />
|
||||
</span>
|
||||
<span className="mr-3">
|
||||
<IconSettings className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewCollectionSettings} />
|
||||
</span>
|
||||
<EnvironmentSelector collection={collection} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { IconAlertTriangle } from '@tabler/icons';
|
||||
|
||||
const RequestTabNotFound = ({handleCloseClick}) => {
|
||||
const RequestTabNotFound = ({ handleCloseClick }) => {
|
||||
const [showErrorMessage, setShowErrorMessage] = useState(false);
|
||||
|
||||
// add a delay component in react that shows a loading spinner
|
||||
@@ -13,7 +13,7 @@ const RequestTabNotFound = ({handleCloseClick}) => {
|
||||
}, 300);
|
||||
}, []);
|
||||
|
||||
if(!showErrorMessage) {
|
||||
if (!showErrorMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import { IconVariable, IconSettings, IconRun } from '@tabler/icons';
|
||||
|
||||
const SpecialTab = ({ handleCloseClick, type }) => {
|
||||
const getTabInfo = (type) => {
|
||||
switch (type) {
|
||||
case 'collection-settings': {
|
||||
return (
|
||||
<>
|
||||
<IconSettings size={18} strokeWidth={1.5} className="text-yellow-600" />
|
||||
<span className="ml-1">Settings</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
case 'variables': {
|
||||
return (
|
||||
<>
|
||||
<IconVariable size={18} strokeWidth={1.5} className="text-yellow-600" />
|
||||
<span className="ml-1">Variables</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
case 'collection-runner': {
|
||||
return (
|
||||
<>
|
||||
<IconRun size={18} strokeWidth={1.5} className="text-yellow-600" />
|
||||
<span className="ml-1">Runner</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center tab-label pl-2">{getTabInfo(type)}</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>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpecialTab;
|
||||
@@ -5,6 +5,7 @@ import { useDispatch } from 'react-redux';
|
||||
import { findItemInCollection } from 'utils/collections';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import RequestTabNotFound from './RequestTabNotFound';
|
||||
import SpecialTab from './SpecialTab';
|
||||
|
||||
const RequestTab = ({ tab, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -56,6 +57,14 @@ const RequestTab = ({ tab, collection }) => {
|
||||
return color;
|
||||
};
|
||||
|
||||
if (['collection-settings', 'variables', 'collection-runner'].includes(tab.type)) {
|
||||
return (
|
||||
<StyledWrapper className="flex items-center justify-between tab-container px-1">
|
||||
<SpecialTab handleCloseClick={handleCloseClick} type={tab.type} />
|
||||
</StyledWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const item = findItemInCollection(collection, tab.uid);
|
||||
|
||||
if (!item) {
|
||||
@@ -87,7 +96,15 @@ const RequestTab = ({ tab, collection }) => {
|
||||
></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">
|
||||
<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" />
|
||||
</svg>
|
||||
)}
|
||||
|
||||
@@ -76,70 +76,79 @@ const RequestTabs = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const showRunner = activeCollection && activeCollection.showRunner;
|
||||
|
||||
// Todo: Must support ephermal requests
|
||||
return (
|
||||
<StyledWrapper className={getRootClassname()}>
|
||||
{newRequestModalOpen && <NewRequest collection={activeCollection} onClose={() => setNewRequestModalOpen(false)} />}
|
||||
{newRequestModalOpen && (
|
||||
<NewRequest collection={activeCollection} onClose={() => setNewRequestModalOpen(false)} />
|
||||
)}
|
||||
{collectionRequestTabs && collectionRequestTabs.length ? (
|
||||
<>
|
||||
<CollectionToolBar collection={activeCollection} />
|
||||
{!showRunner ? (
|
||||
<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} />
|
||||
</div>
|
||||
</li>
|
||||
) : null}
|
||||
{/* Moved to post mvp */}
|
||||
{/* <li className="select-none new-tab mr-1" onClick={createNewTab}>
|
||||
<div className="flex items-center home-icon-container">
|
||||
<IconHome2 size={18} strokeWidth={1.5}/>
|
||||
</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>
|
||||
|
||||
<ul role="tablist">
|
||||
{showChevrons ? (
|
||||
<li className="select-none short-tab" onClick={rightSlide}>
|
||||
<div className="flex items-center">
|
||||
<IconChevronRight size={18} strokeWidth={1.5} />
|
||||
</div>
|
||||
</li>
|
||||
) : null}
|
||||
<li className="select-none short-tab" id="create-new-tab" onClick={createNewTab}>
|
||||
<div className="flex items-center pl-4">
|
||||
<ul role="tablist">
|
||||
{showChevrons ? (
|
||||
<li className="select-none short-tab" onClick={leftSlide}>
|
||||
<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>
|
||||
<IconChevronLeft size={18} strokeWidth={1.5} />
|
||||
</div>
|
||||
</li>
|
||||
{/* Moved to post mvp */}
|
||||
{/* <li className="select-none new-tab choose-request">
|
||||
) : null}
|
||||
{/* Moved to post mvp */}
|
||||
{/* <li className="select-none new-tab mr-1" onClick={createNewTab}>
|
||||
<div className="flex items-center home-icon-container">
|
||||
<IconHome2 size={18} strokeWidth={1.5}/>
|
||||
</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} />
|
||||
</li>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</ul>
|
||||
|
||||
<ul role="tablist">
|
||||
{showChevrons ? (
|
||||
<li className="select-none short-tab" onClick={rightSlide}>
|
||||
<div className="flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M3 9.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/>
|
||||
</svg>
|
||||
<IconChevronRight size={18} strokeWidth={1.5} />
|
||||
</div>
|
||||
</li> */}
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
</li>
|
||||
) : null}
|
||||
<li className="select-none short-tab" id="create-new-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>
|
||||
</div>
|
||||
</li>
|
||||
{/* Moved to post mvp */}
|
||||
{/* <li className="select-none new-tab choose-request">
|
||||
<div className="flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M3 9.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</li> */}
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</StyledWrapper>
|
||||
|
||||
@@ -6,14 +6,12 @@ import { sendRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const QueryResult = ({ item, collection, value, width, disableRunEventListener }) => {
|
||||
const {
|
||||
storedTheme
|
||||
} = useTheme();
|
||||
const QueryResult = ({ item, collection, value, width, disableRunEventListener, mode }) => {
|
||||
const { storedTheme } = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onRun = () => {
|
||||
if(disableRunEventListener) {
|
||||
if (disableRunEventListener) {
|
||||
return;
|
||||
}
|
||||
dispatch(sendRequest(item, collection.uid));
|
||||
@@ -22,7 +20,14 @@ const QueryResult = ({ item, collection, value, width, disableRunEventListener }
|
||||
return (
|
||||
<StyledWrapper className="px-3 w-full" style={{ maxWidth: width }}>
|
||||
<div className="h-full">
|
||||
<CodeEditor collection={collection} theme={storedTheme} onRun={onRun} value={value || ''} readOnly />
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
theme={storedTheme}
|
||||
onRun={onRun}
|
||||
value={value || ''}
|
||||
mode={mode}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
@@ -3,11 +3,11 @@ import styled from 'styled-components';
|
||||
const Wrapper = styled.div`
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
|
||||
|
||||
&.text-ok {
|
||||
color: ${(props) => props.theme.requestTabPanel.responseOk};
|
||||
}
|
||||
|
||||
|
||||
&.text-error {
|
||||
color: ${(props) => props.theme.requestTabPanel.responseError};
|
||||
}
|
||||
|
||||
@@ -5,11 +5,7 @@ const TestResults = ({ results, assertionResults }) => {
|
||||
results = results || [];
|
||||
assertionResults = assertionResults || [];
|
||||
if (!results.length && !assertionResults.length) {
|
||||
return (
|
||||
<div className="px-3">
|
||||
No tests found
|
||||
</div>
|
||||
);
|
||||
return <div className="px-3">No tests found</div>;
|
||||
}
|
||||
|
||||
const passedTests = results.filter((result) => result.status === 'pass');
|
||||
@@ -19,7 +15,7 @@ const TestResults = ({ results, assertionResults }) => {
|
||||
const failedAssertions = assertionResults.filter((result) => result.status === 'fail');
|
||||
|
||||
return (
|
||||
<StyledWrapper className='flex flex-col px-3'>
|
||||
<StyledWrapper className="flex flex-col px-3">
|
||||
<div className="py-2 font-medium test-summary">
|
||||
Tests ({results.length}/{results.length}), Passed: {passedTests.length}, Failed: {failedTests.length}
|
||||
</div>
|
||||
@@ -27,18 +23,12 @@ const TestResults = ({ results, assertionResults }) => {
|
||||
{results.map((result) => (
|
||||
<li key={result.uid} className="py-1">
|
||||
{result.status === 'pass' ? (
|
||||
<span className="test-success">
|
||||
✔ {result.description}
|
||||
</span>
|
||||
<span className="test-success">✔ {result.description}</span>
|
||||
) : (
|
||||
<>
|
||||
<span className="test-failure">
|
||||
✘ {result.description}
|
||||
</span>
|
||||
<span className="test-failure">✘ {result.description}</span>
|
||||
<br />
|
||||
<span className="error-message pl-8">
|
||||
{result.error}
|
||||
</span>
|
||||
<span className="error-message pl-8">{result.error}</span>
|
||||
</>
|
||||
)}
|
||||
</li>
|
||||
@@ -46,7 +36,8 @@ const TestResults = ({ results, assertionResults }) => {
|
||||
</ul>
|
||||
|
||||
<div className="py-2 font-medium test-summary">
|
||||
Assertions ({assertionResults.length}/{assertionResults.length}), Passed: {passedAssertions.length}, Failed: {failedAssertions.length}
|
||||
Assertions ({assertionResults.length}/{assertionResults.length}), Passed: {passedAssertions.length}, Failed:{' '}
|
||||
{failedAssertions.length}
|
||||
</div>
|
||||
<ul className="">
|
||||
{assertionResults.map((result) => (
|
||||
@@ -61,9 +52,7 @@ const TestResults = ({ results, assertionResults }) => {
|
||||
✘ {result.lhsExpr}: {result.rhsExpr}
|
||||
</span>
|
||||
<br />
|
||||
<span className="error-message pl-8">
|
||||
{result.error}
|
||||
</span>
|
||||
<span className="error-message pl-8">{result.error}</span>
|
||||
</>
|
||||
)}
|
||||
</li>
|
||||
|
||||
@@ -3,32 +3,28 @@ import React from 'react';
|
||||
const TestResultsLabel = ({ results, assertionResults }) => {
|
||||
results = results || [];
|
||||
assertionResults = assertionResults || [];
|
||||
if(!results.length && !assertionResults.length) {
|
||||
if (!results.length && !assertionResults.length) {
|
||||
return 'Tests';
|
||||
}
|
||||
|
||||
const numberOfTests = results.length;
|
||||
const numberOfFailedTests = results.filter(result => result.status === 'fail').length;
|
||||
const numberOfFailedTests = results.filter((result) => result.status === 'fail').length;
|
||||
|
||||
const numberOfAssertions = assertionResults.length;
|
||||
const numberOfFailedAssertions = assertionResults.filter(result => result.status === 'fail').length;
|
||||
const numberOfFailedAssertions = assertionResults.filter((result) => result.status === 'fail').length;
|
||||
|
||||
const totalNumberOfTests = numberOfTests + numberOfAssertions;
|
||||
const totalNumberOfFailedTests = numberOfFailedTests + numberOfFailedAssertions;
|
||||
|
||||
return (
|
||||
<div className='flex items-center'>
|
||||
<div className="flex items-center">
|
||||
<div>Tests</div>
|
||||
{totalNumberOfFailedTests ? (
|
||||
<sup className='sups some-tests-failed ml-1 font-medium'>
|
||||
{totalNumberOfFailedTests}
|
||||
</sup>
|
||||
<sup className="sups some-tests-failed ml-1 font-medium">{totalNumberOfFailedTests}</sup>
|
||||
) : (
|
||||
<sup className='sups all-tests-passed ml-1 font-medium'>
|
||||
{totalNumberOfTests}
|
||||
</sup>
|
||||
<sup className="sups all-tests-passed ml-1 font-medium">{totalNumberOfTests}</sup>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import forOwn from 'lodash/forOwn';
|
||||
import { safeStringifyJSON } from 'utils/common';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const Timeline = ({ request, response}) => {
|
||||
const Timeline = ({ request, response }) => {
|
||||
const requestHeaders = [];
|
||||
const responseHeaders = response.headers || [];
|
||||
|
||||
@@ -22,32 +22,32 @@ const Timeline = ({ request, response}) => {
|
||||
return (
|
||||
<StyledWrapper className="px-3 pb-4 w-full">
|
||||
<div>
|
||||
<pre className='line request font-bold'>
|
||||
<pre className="line request font-bold">
|
||||
<span className="arrow">{'>'}</span> {request.method} {request.url}
|
||||
</pre>
|
||||
{requestHeaders.map((h) => {
|
||||
return (
|
||||
<pre className='line request' key={h.name}>
|
||||
<pre className="line request" key={h.name}>
|
||||
<span className="arrow">{'>'}</span> {h.name}: {h.value}
|
||||
</pre>
|
||||
);
|
||||
})}
|
||||
|
||||
{requestData ? (
|
||||
<pre className='line request'>
|
||||
<pre className="line request">
|
||||
<span className="arrow">{'>'}</span> data {requestData}
|
||||
</pre>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className='mt-4'>
|
||||
<pre className='line response font-bold'>
|
||||
<div className="mt-4">
|
||||
<pre className="line response font-bold">
|
||||
<span className="arrow">{'<'}</span> {response.status} {response.statusText}
|
||||
</pre>
|
||||
|
||||
{responseHeaders.map((h) => {
|
||||
return (
|
||||
<pre className='line response' key={h[0]}>
|
||||
<pre className="line response" key={h[0]}>
|
||||
<span className="arrow">{'<'}</span> {h[0]}: {h[1]}
|
||||
</pre>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import find from 'lodash/find';
|
||||
import classnames from 'classnames';
|
||||
import { safeStringifyJSON } from 'utils/common';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { getContentType, formatResponse } from 'utils/common';
|
||||
import { updateResponsePaneTab } from 'providers/ReduxStore/slices/tabs';
|
||||
import QueryResult from './QueryResult';
|
||||
import Overlay from './Overlay';
|
||||
@@ -36,18 +36,21 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
|
||||
const getTabPanel = (tab) => {
|
||||
switch (tab) {
|
||||
case 'response': {
|
||||
return <QueryResult
|
||||
item={item}
|
||||
collection={collection}
|
||||
width={rightPaneWidth}
|
||||
value={response.data ? safeStringifyJSON(response.data, true) : ''}
|
||||
/>;
|
||||
return (
|
||||
<QueryResult
|
||||
item={item}
|
||||
collection={collection}
|
||||
width={rightPaneWidth}
|
||||
value={response.data ? formatResponse(response) : ''}
|
||||
mode={getContentType(response.headers)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'headers': {
|
||||
return <ResponseHeaders headers={response.headers} />;
|
||||
}
|
||||
case 'timeline': {
|
||||
return <Timeline request={item.requestSent} response={item.response}/>;
|
||||
return <Timeline request={item.requestSent} response={item.response} />;
|
||||
}
|
||||
case 'tests': {
|
||||
return <TestResults results={item.testResults} assertionResults={item.assertionResults} />;
|
||||
@@ -81,7 +84,7 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
|
||||
|
||||
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>;
|
||||
return <div className="pb-4 px-4">An error occurred!</div>;
|
||||
}
|
||||
|
||||
const getTabClassname = (tabName) => {
|
||||
@@ -90,6 +93,10 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const isJson = (headers) => {
|
||||
return getContentType(headers) === 'application/ld+json';
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="flex flex-col h-full relative">
|
||||
<div className="flex items-center px-3 tabs" role="tablist">
|
||||
|
||||
@@ -15,11 +15,7 @@ import StyledWrapper from './StyledWrapper';
|
||||
const ResponsePane = ({ rightPaneWidth, item, collection }) => {
|
||||
const [selectedTab, setSelectedTab] = useState('response');
|
||||
|
||||
const {
|
||||
requestSent,
|
||||
responseReceived,
|
||||
testResults
|
||||
} = item;
|
||||
const { requestSent, responseReceived, testResults } = item;
|
||||
|
||||
const headers = get(item, 'responseReceived.headers', {});
|
||||
const status = get(item, 'responseReceived.status', 0);
|
||||
@@ -31,13 +27,15 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
|
||||
const getTabPanel = (tab) => {
|
||||
switch (tab) {
|
||||
case 'response': {
|
||||
return <QueryResult
|
||||
item={item}
|
||||
collection={collection}
|
||||
width={rightPaneWidth}
|
||||
disableRunEventListener={true}
|
||||
value={(responseReceived && responseReceived.data) ? safeStringifyJSON(responseReceived.data, true) : ''}
|
||||
/>;
|
||||
return (
|
||||
<QueryResult
|
||||
item={item}
|
||||
collection={collection}
|
||||
width={rightPaneWidth}
|
||||
disableRunEventListener={true}
|
||||
value={responseReceived && responseReceived.data ? safeStringifyJSON(responseReceived.data, true) : ''}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'headers': {
|
||||
return <ResponseHeaders headers={headers} />;
|
||||
|
||||
@@ -3,7 +3,7 @@ import path from 'path';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { get, each, cloneDeep } from 'lodash';
|
||||
import { runCollectionFolder } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { closeCollectionRunner } from 'providers/ReduxStore/slices/collections';
|
||||
import { resetCollectionRunner } from 'providers/ReduxStore/slices/collections';
|
||||
import { findItemInCollection, getTotalRequestCountInCollection } from 'utils/collections';
|
||||
import { IconRefresh, IconCircleCheck, IconCircleX, IconCheck, IconX, IconRun } from '@tabler/icons';
|
||||
import slash from 'utils/common/slash';
|
||||
@@ -18,14 +18,14 @@ const getRelativePath = (fullPath, pathname) => {
|
||||
let relativePath = path.relative(fullPath, pathname);
|
||||
const { dir, name } = path.parse(relativePath);
|
||||
return path.join(dir, name);
|
||||
}
|
||||
};
|
||||
|
||||
export default function RunnerResults({collection}) {
|
||||
export default function RunnerResults({ collection }) {
|
||||
const dispatch = useDispatch();
|
||||
const [selectedItem, setSelectedItem] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if(!collection.runnerResult) {
|
||||
if (!collection.runnerResult) {
|
||||
setSelectedItem(null);
|
||||
}
|
||||
}, [collection, setSelectedItem]);
|
||||
@@ -42,8 +42,8 @@ export default function RunnerResults({collection}) {
|
||||
item.pathname = info.pathname;
|
||||
item.relativePath = getRelativePath(collection.pathname, info.pathname);
|
||||
|
||||
if(item.status !== "error") {
|
||||
if(item.testResults) {
|
||||
if (item.status !== 'error') {
|
||||
if (item.testResults) {
|
||||
const failed = item.testResults.filter((result) => result.status === 'fail');
|
||||
|
||||
item.testStatus = failed.length ? 'fail' : 'pass';
|
||||
@@ -51,7 +51,7 @@ export default function RunnerResults({collection}) {
|
||||
item.testStatus = 'pass';
|
||||
}
|
||||
|
||||
if(item.assertionResults) {
|
||||
if (item.assertionResults) {
|
||||
const failed = item.assertionResults.filter((result) => result.status === 'fail');
|
||||
|
||||
item.assertionStatus = failed.length ? 'fail' : 'pass';
|
||||
@@ -69,51 +69,53 @@ export default function RunnerResults({collection}) {
|
||||
dispatch(runCollectionFolder(collection.uid, runnerInfo.folderUid, runnerInfo.isRecursive));
|
||||
};
|
||||
|
||||
const closeRunner = () => {
|
||||
dispatch(closeCollectionRunner({
|
||||
collectionUid: collection.uid,
|
||||
}));
|
||||
const resetRunner = () => {
|
||||
dispatch(
|
||||
resetCollectionRunner({
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const totalRequestsInCollection = getTotalRequestCountInCollection(collectionCopy);
|
||||
const passedRequests = items.filter((item) => {
|
||||
return item.status !== "error" && item.testStatus === 'pass' && item.assertionStatus === 'pass';
|
||||
return item.status !== 'error' && item.testStatus === 'pass' && item.assertionStatus === 'pass';
|
||||
});
|
||||
const failedRequests = items.filter((item) => {
|
||||
return item.status !== "error" && item.testStatus === 'fail' || item.assertionStatus === 'fail';
|
||||
return (item.status !== 'error' && item.testStatus === 'fail') || item.assertionStatus === 'fail';
|
||||
});
|
||||
|
||||
if(!items || !items.length) {
|
||||
if (!items || !items.length) {
|
||||
return (
|
||||
<StyledWrapper className='px-4'>
|
||||
<div className='font-medium mt-6 title flex items-center'>
|
||||
<StyledWrapper className="px-4">
|
||||
<div className="font-medium mt-6 title flex items-center">
|
||||
Runner
|
||||
<IconRun size={20} strokeWidth={1.5} className='ml-2'/>
|
||||
<IconRun size={20} strokeWidth={1.5} className="ml-2" />
|
||||
</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
You have <span className='font-medium'>{totalRequestsInCollection}</span> requests in this collection.
|
||||
<div className="mt-6">
|
||||
You have <span className="font-medium">{totalRequestsInCollection}</span> requests in this collection.
|
||||
</div>
|
||||
|
||||
<button type="submit" className="submit btn btn-sm btn-secondary mt-6" onClick={runCollection}>
|
||||
Run Collection
|
||||
</button>
|
||||
|
||||
<button className="submit btn btn-sm btn-close mt-6 ml-3" onClick={closeRunner}>
|
||||
Close
|
||||
<button className="submit btn btn-sm btn-close mt-6 ml-3" onClick={resetRunner}>
|
||||
Reset
|
||||
</button>
|
||||
</StyledWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledWrapper className='px-4'>
|
||||
<div className='font-medium mt-6 mb-4 title flex items-center'>
|
||||
<StyledWrapper className="px-4">
|
||||
<div className="font-medium mt-6 mb-4 title flex items-center">
|
||||
Runner
|
||||
<IconRun size={20} strokeWidth={1.5} className='ml-2'/>
|
||||
<IconRun size={20} strokeWidth={1.5} className="ml-2" />
|
||||
</div>
|
||||
<div className='flex'>
|
||||
<div className='flex flex-col flex-1'>
|
||||
<div className="flex">
|
||||
<div className="flex flex-col flex-1">
|
||||
<div className="py-2 font-medium test-summary">
|
||||
Total Requests: {items.length}, Passed: {passedRequests.length}, Failed: {failedRequests.length}
|
||||
</div>
|
||||
@@ -123,73 +125,69 @@ export default function RunnerResults({collection}) {
|
||||
<div className="item-path mt-2">
|
||||
<div className="flex items-center">
|
||||
<span>
|
||||
{item.status !== "error" && item.testStatus === 'pass' ? (
|
||||
<IconCircleCheck className="test-success" size={20} strokeWidth={1.5}/>
|
||||
) : (
|
||||
<IconCircleX className="test-failure" size={20} strokeWidth={1.5}/>
|
||||
)}
|
||||
{item.status !== 'error' && item.testStatus === 'pass' ? (
|
||||
<IconCircleCheck className="test-success" size={20} strokeWidth={1.5} />
|
||||
) : (
|
||||
<IconCircleX className="test-failure" size={20} strokeWidth={1.5} />
|
||||
)}
|
||||
</span>
|
||||
<span className={`mr-1 ml-2 ${(item.status == "error" || item.testStatus == 'fail') ? 'danger' : ''}`}>{item.relativePath}</span>
|
||||
{(item.status !== "error" && item.status !== "completed") ? (
|
||||
<IconRefresh className="animate-spin ml-1" size={18} strokeWidth={1.5}/>
|
||||
<span
|
||||
className={`mr-1 ml-2 ${item.status == 'error' || item.testStatus == 'fail' ? 'danger' : ''}`}
|
||||
>
|
||||
{item.relativePath}
|
||||
</span>
|
||||
{item.status !== 'error' && item.status !== 'completed' ? (
|
||||
<IconRefresh className="animate-spin ml-1" size={18} strokeWidth={1.5} />
|
||||
) : (
|
||||
<span className='text-xs link cursor-pointer' onClick={() => setSelectedItem(item)}>
|
||||
(<span className='mr-1'>
|
||||
{get(item.responseReceived, 'status')}
|
||||
</span>
|
||||
<span>
|
||||
{get(item.responseReceived, 'statusText')}
|
||||
</span>)
|
||||
<span className="text-xs link cursor-pointer" onClick={() => setSelectedItem(item)}>
|
||||
(<span className="mr-1">{get(item.responseReceived, 'status')}</span>
|
||||
<span>{get(item.responseReceived, 'statusText')}</span>)
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{item.status == "error" ? (
|
||||
<div className="error-message pl-8 pt-2 text-xs">
|
||||
{item.error}
|
||||
</div>
|
||||
) : null }
|
||||
{item.status == 'error' ? <div className="error-message pl-8 pt-2 text-xs">{item.error}</div> : null}
|
||||
|
||||
<ul className="pl-8">
|
||||
{item.testResults ? item.testResults.map((result) => (
|
||||
<li key={result.uid}>
|
||||
{result.status === 'pass' ? (
|
||||
<span className="test-success flex items-center">
|
||||
<IconCheck size={18} strokeWidth={2} className="mr-2"/>
|
||||
{result.description}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<span className="test-failure flex items-center">
|
||||
<IconX size={18} strokeWidth={2} className="mr-2"/>
|
||||
{result.description}
|
||||
</span>
|
||||
<span className="error-message pl-8 text-xs">
|
||||
{result.error}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</li>
|
||||
)): null}
|
||||
{item.assertionResults ? item.assertionResults.map((result) => (
|
||||
<li key={result.uid}>
|
||||
{result.status === 'pass' ? (
|
||||
<span className="test-success flex items-center">
|
||||
<IconCheck size={18} strokeWidth={2} className="mr-2"/>
|
||||
{result.lhsExpr}: {result.rhsExpr}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<span className="test-failure flex items-center">
|
||||
<IconX size={18} strokeWidth={2} className="mr-2"/>
|
||||
{result.lhsExpr}: {result.rhsExpr}
|
||||
</span>
|
||||
<span className="error-message pl-8 text-xs">
|
||||
{result.error}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</li>
|
||||
)): null}
|
||||
{item.testResults
|
||||
? item.testResults.map((result) => (
|
||||
<li key={result.uid}>
|
||||
{result.status === 'pass' ? (
|
||||
<span className="test-success flex items-center">
|
||||
<IconCheck size={18} strokeWidth={2} className="mr-2" />
|
||||
{result.description}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<span className="test-failure flex items-center">
|
||||
<IconX size={18} strokeWidth={2} className="mr-2" />
|
||||
{result.description}
|
||||
</span>
|
||||
<span className="error-message pl-8 text-xs">{result.error}</span>
|
||||
</>
|
||||
)}
|
||||
</li>
|
||||
))
|
||||
: null}
|
||||
{item.assertionResults
|
||||
? item.assertionResults.map((result) => (
|
||||
<li key={result.uid}>
|
||||
{result.status === 'pass' ? (
|
||||
<span className="test-success flex items-center">
|
||||
<IconCheck size={18} strokeWidth={2} className="mr-2" />
|
||||
{result.lhsExpr}: {result.rhsExpr}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<span className="test-failure flex items-center">
|
||||
<IconX size={18} strokeWidth={2} className="mr-2" />
|
||||
{result.lhsExpr}: {result.rhsExpr}
|
||||
</span>
|
||||
<span className="error-message pl-8 text-xs">{result.error}</span>
|
||||
</>
|
||||
)}
|
||||
</li>
|
||||
))
|
||||
: null}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -204,31 +202,31 @@ export default function RunnerResults({collection}) {
|
||||
<button type="submit" className="submit btn btn-sm btn-secondary mt-6 ml-3" onClick={runCollection}>
|
||||
Run Collection
|
||||
</button>
|
||||
<button className="btn btn-sm btn-close mt-6 ml-3" onClick={closeRunner}>
|
||||
Close
|
||||
<button className="btn btn-sm btn-close mt-6 ml-3" onClick={resetRunner}>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className='flex flex-1' style={{width: '50%'}}>
|
||||
<div className="flex flex-1" style={{ width: '50%' }}>
|
||||
{selectedItem ? (
|
||||
<div className='flex flex-col w-full overflow-auto'>
|
||||
<div className="flex flex-col w-full overflow-auto">
|
||||
<div className="flex items-center px-3 mb-4 font-medium">
|
||||
<span className='mr-2'>{selectedItem.relativePath}</span>
|
||||
<span className="mr-2">{selectedItem.relativePath}</span>
|
||||
<span>
|
||||
{selectedItem.testStatus === 'pass' ? (
|
||||
<IconCircleCheck className="test-success" size={20} strokeWidth={1.5}/>
|
||||
<IconCircleCheck className="test-success" size={20} strokeWidth={1.5} />
|
||||
) : (
|
||||
<IconCircleX className="test-failure" size={20} strokeWidth={1.5}/>
|
||||
<IconCircleX className="test-failure" size={20} strokeWidth={1.5} />
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
{/* <div className='px-3 mb-4 font-medium'>{selectedItem.relativePath}</div> */}
|
||||
<ResponsePane item={selectedItem} collection={collection}/>
|
||||
<ResponsePane item={selectedItem} collection={collection} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,7 +17,10 @@ const CloneCollectionItem = ({ collection, item, onClose }) => {
|
||||
name: item.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')
|
||||
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(cloneItem(values.name, item.uid, collection.uid))
|
||||
@@ -25,7 +28,7 @@ const CloneCollectionItem = ({ collection, item, onClose }) => {
|
||||
onClose();
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(err ? err.message : 'An error occured while cloning the request')
|
||||
toast.error(err ? err.message : 'An error occured while cloning the request');
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -39,7 +42,13 @@ const CloneCollectionItem = ({ collection, item, onClose }) => {
|
||||
const onSubmit = () => formik.handleSubmit();
|
||||
|
||||
return (
|
||||
<Modal size="sm" title={`Clone ${isFolder ? 'Folder' : 'Request'}`} confirmText="Clone" handleConfirm={onSubmit} handleCancel={onClose}>
|
||||
<Modal
|
||||
size="sm"
|
||||
title={`Clone ${isFolder ? 'Folder' : 'Request'}`}
|
||||
confirmText="Clone"
|
||||
handleConfirm={onSubmit}
|
||||
handleCancel={onClose}
|
||||
>
|
||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||
<div>
|
||||
<label htmlFor="name" className="block font-semibold">
|
||||
|
||||
@@ -31,7 +31,13 @@ const DeleteCollectionItem = ({ onClose, item, collection }) => {
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<Modal size="sm" title={`Delete ${isFolder ? 'Folder' : 'Request'}`} confirmText="Delete" handleConfirm={onConfirm} handleCancel={onClose}>
|
||||
<Modal
|
||||
size="sm"
|
||||
title={`Delete ${isFolder ? 'Folder' : 'Request'}`}
|
||||
confirmText="Delete"
|
||||
handleConfirm={onConfirm}
|
||||
handleCancel={onClose}
|
||||
>
|
||||
Are you sure you want to delete <span className="font-semibold">{item.name}</span> ?
|
||||
</Modal>
|
||||
</StyledWrapper>
|
||||
|
||||
@@ -16,7 +16,10 @@ const RenameCollectionItem = ({ collection, item, onClose }) => {
|
||||
name: item.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')
|
||||
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(renameItem(values.name, item.uid, collection.uid));
|
||||
@@ -33,7 +36,13 @@ const RenameCollectionItem = ({ collection, item, onClose }) => {
|
||||
const onSubmit = () => formik.handleSubmit();
|
||||
|
||||
return (
|
||||
<Modal size="sm" title={`Rename ${isFolder ? 'Folder' : 'Request'}`} confirmText="Rename" handleConfirm={onSubmit} handleCancel={onClose}>
|
||||
<Modal
|
||||
size="sm"
|
||||
title={`Rename ${isFolder ? 'Folder' : 'Request'}`}
|
||||
confirmText="Rename"
|
||||
handleConfirm={onSubmit}
|
||||
handleCancel={onClose}
|
||||
>
|
||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||
<div>
|
||||
<label htmlFor="name" className="block font-semibold">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
.bruno-modal-content {
|
||||
.bruno-modal-content {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import get from 'lodash/get';
|
||||
import { uuid } from 'utils/common';
|
||||
import Modal from 'components/Modal';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
||||
import { runCollectionFolder } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { showRunnerView } from 'providers/ReduxStore/slices/collections';
|
||||
import { flattenItems } from 'utils/collections';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
@@ -11,9 +12,13 @@ const RunCollectionItem = ({ collection, item, onClose }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onSubmit = (recursive) => {
|
||||
dispatch(showRunnerView({
|
||||
collectionUid: collection.uid,
|
||||
}));
|
||||
dispatch(
|
||||
addTab({
|
||||
uid: uuid(),
|
||||
collectionUid: collection.uid,
|
||||
type: 'collection-runner'
|
||||
})
|
||||
);
|
||||
dispatch(runCollectionFolder(collection.uid, item ? item.uid : null, recursive));
|
||||
onClose();
|
||||
};
|
||||
@@ -25,25 +30,21 @@ const RunCollectionItem = ({ collection, item, onClose }) => {
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<Modal size="md" title='Collection Runner' hideFooter={true} handleCancel={onClose}>
|
||||
<div className='mb-1'>
|
||||
<span className='font-medium'>Run</span>
|
||||
<span className='ml-1 text-xs'>({runLength} requests)</span>
|
||||
</div>
|
||||
<div className='mb-8'>
|
||||
This will only run the requests in this folder.
|
||||
<Modal size="md" title="Collection Runner" hideFooter={true} handleCancel={onClose}>
|
||||
<div className="mb-1">
|
||||
<span className="font-medium">Run</span>
|
||||
<span className="ml-1 text-xs">({runLength} requests)</span>
|
||||
</div>
|
||||
<div className="mb-8">This will only run the requests in this folder.</div>
|
||||
|
||||
<div className='mb-1'>
|
||||
<span className='font-medium'>Recursive Run</span>
|
||||
<span className='ml-1 text-xs'>({recursiveRunLength} requests)</span>
|
||||
</div>
|
||||
<div className='mb-8'>
|
||||
This will run all the requests in this folder and all its subfolders.
|
||||
<div className="mb-1">
|
||||
<span className="font-medium">Recursive Run</span>
|
||||
<span className="ml-1 text-xs">({recursiveRunLength} requests)</span>
|
||||
</div>
|
||||
<div className="mb-8">This will run all the requests in this folder and all its subfolders.</div>
|
||||
|
||||
<div className="flex justify-end bruno-modal-footer">
|
||||
<span className='mr-3'>
|
||||
<span className="mr-3">
|
||||
<button type="button" onClick={onClose} className="btn btn-md btn-close">
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useDrag, useDrop } from 'react-dnd';
|
||||
import { IconChevronRight, IconDots } from '@tabler/icons';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { addTab, focusTab } from 'providers/ReduxStore/slices/tabs';
|
||||
import { collectionFolderClicked, hideRunnerView } from 'providers/ReduxStore/slices/collections';
|
||||
import { collectionFolderClicked } from 'providers/ReduxStore/slices/collections';
|
||||
import { moveItem } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import NewRequest from 'components/Sidebar/NewRequest';
|
||||
@@ -86,9 +86,6 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
||||
});
|
||||
|
||||
const handleClick = (event) => {
|
||||
dispatch(hideRunnerView({
|
||||
collectionUid: collection.uid
|
||||
}));
|
||||
if (isItemARequest(item)) {
|
||||
if (itemIsOpenedInTabs(item, tabs)) {
|
||||
dispatch(
|
||||
@@ -151,12 +148,24 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
||||
|
||||
return (
|
||||
<StyledWrapper className={className}>
|
||||
{renameItemModalOpen && <RenameCollectionItem item={item} collection={collection} onClose={() => setRenameItemModalOpen(false)} />}
|
||||
{cloneItemModalOpen && <CloneCollectionItem item={item} collection={collection} onClose={() => setCloneItemModalOpen(false)} />}
|
||||
{deleteItemModalOpen && <DeleteCollectionItem item={item} collection={collection} onClose={() => setDeleteItemModalOpen(false)} />}
|
||||
{newRequestModalOpen && <NewRequest item={item} collection={collection} onClose={() => setNewRequestModalOpen(false)} />}
|
||||
{newFolderModalOpen && <NewFolder item={item} collection={collection} onClose={() => setNewFolderModalOpen(false)} />}
|
||||
{runCollectionModalOpen && <RunCollectionItem collection={collection} item={item} onClose={() => setRunCollectionModalOpen(false)} />}
|
||||
{renameItemModalOpen && (
|
||||
<RenameCollectionItem item={item} collection={collection} onClose={() => setRenameItemModalOpen(false)} />
|
||||
)}
|
||||
{cloneItemModalOpen && (
|
||||
<CloneCollectionItem item={item} collection={collection} onClose={() => setCloneItemModalOpen(false)} />
|
||||
)}
|
||||
{deleteItemModalOpen && (
|
||||
<DeleteCollectionItem item={item} collection={collection} onClose={() => setDeleteItemModalOpen(false)} />
|
||||
)}
|
||||
{newRequestModalOpen && (
|
||||
<NewRequest item={item} collection={collection} onClose={() => setNewRequestModalOpen(false)} />
|
||||
)}
|
||||
{newFolderModalOpen && (
|
||||
<NewFolder item={item} collection={collection} onClose={() => setNewFolderModalOpen(false)} />
|
||||
)}
|
||||
{runCollectionModalOpen && (
|
||||
<RunCollectionItem collection={collection} item={item} onClose={() => setRunCollectionModalOpen(false)} />
|
||||
)}
|
||||
<div className={itemRowClassName} ref={(node) => drag(drop(node))}>
|
||||
<div className="flex items-center h-full w-full">
|
||||
{indents && indents.length
|
||||
@@ -185,7 +194,14 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
||||
}}
|
||||
>
|
||||
<div style={{ width: 16, minWidth: 16 }}>
|
||||
{isFolder ? <IconChevronRight size={16} strokeWidth={2} className={iconClassName} style={{ color: 'rgb(160 160 160)' }} /> : null}
|
||||
{isFolder ? (
|
||||
<IconChevronRight
|
||||
size={16}
|
||||
strokeWidth={2}
|
||||
className={iconClassName}
|
||||
style={{ color: 'rgb(160 160 160)' }}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="ml-1 flex items-center overflow-hidden">
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
import Modal from 'components/Modal';
|
||||
|
||||
function countRequests(items) {
|
||||
let count = 0;
|
||||
|
||||
function recurse(item) {
|
||||
if (item && typeof item === 'object') {
|
||||
if (item.type !== 'folder') {
|
||||
count++;
|
||||
}
|
||||
if (Array.isArray(item.items)) {
|
||||
item.items.forEach(recurse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
items.forEach(recurse);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
const CollectionProperties = ({ collection, onClose }) => {
|
||||
return (
|
||||
<Modal size="sm" title="Collection Properties" hideFooter={true} handleCancel={onClose}>
|
||||
<table className="w-full border-collapse">
|
||||
<tbody>
|
||||
<tr className="">
|
||||
<td className="py-2 px-2 text-right">Name :</td>
|
||||
<td className="py-2 px-2">{collection.name}</td>
|
||||
</tr>
|
||||
<tr className="">
|
||||
<td className="py-2 px-2 text-right">Location :</td>
|
||||
<td className="py-2 px-2">{collection.pathname}</td>
|
||||
</tr>
|
||||
<tr className="">
|
||||
<td className="py-2 px-2 text-right">Environments :</td>
|
||||
<td className="py-2 px-2">{collection.environments?.length || 0}</td>
|
||||
</tr>
|
||||
<tr className="">
|
||||
<td className="py-2 px-2 text-right">Requests :</td>
|
||||
<td className="py-2 px-2">{countRequests(collection.items)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default CollectionProperties;
|
||||
@@ -15,7 +15,10 @@ const RenameCollection = ({ collection, onClose }) => {
|
||||
name: collection.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')
|
||||
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(renameCollection(values.name, collection.uid));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState, forwardRef, useRef, useEffect } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { uuid } from 'utils/common';
|
||||
import filter from 'lodash/filter';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { useDrop } from 'react-dnd';
|
||||
@@ -8,11 +9,12 @@ import Dropdown from 'components/Dropdown';
|
||||
import { collectionClicked } from 'providers/ReduxStore/slices/collections';
|
||||
import { moveItemToRootOfCollection } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
||||
import NewRequest from 'components/Sidebar/NewRequest';
|
||||
import NewFolder from 'components/Sidebar/NewFolder';
|
||||
import CollectionItem from './CollectionItem';
|
||||
import RemoveCollection from './RemoveCollection';
|
||||
import RunCollectionItem from './CollectionItem/RunCollectionItem';
|
||||
import CollectionProperties from './CollectionProperties';
|
||||
import { doesCollectionHaveItemsMatchingSearchText } from 'utils/collections/search';
|
||||
import { isItemAFolder, isItemARequest, transformCollectionToSaveToIdb } from 'utils/collections';
|
||||
import exportCollection from 'utils/collections/export';
|
||||
@@ -25,7 +27,7 @@ const Collection = ({ collection, searchText }) => {
|
||||
const [showNewRequestModal, setShowNewRequestModal] = useState(false);
|
||||
const [showRenameCollectionModal, setShowRenameCollectionModal] = useState(false);
|
||||
const [showRemoveCollectionModal, setShowRemoveCollectionModal] = useState(false);
|
||||
const [showRunCollectionModal, setShowRunCollectionModal] = useState(false);
|
||||
const [collectionPropertiesModal, setCollectionPropertiesModal] = useState(false);
|
||||
const [collectionIsCollapsed, setCollectionIsCollapsed] = useState(collection.collapsed);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -39,6 +41,16 @@ const Collection = ({ collection, searchText }) => {
|
||||
);
|
||||
});
|
||||
|
||||
const handleRun = () => {
|
||||
dispatch(
|
||||
addTab({
|
||||
uid: uuid(),
|
||||
collectionUid: collection.uid,
|
||||
type: 'collection-runner'
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (searchText && searchText.length) {
|
||||
setCollectionIsCollapsed(false);
|
||||
@@ -97,13 +109,26 @@ const Collection = ({ collection, searchText }) => {
|
||||
<StyledWrapper className="flex flex-col">
|
||||
{showNewRequestModal && <NewRequest collection={collection} onClose={() => setShowNewRequestModal(false)} />}
|
||||
{showNewFolderModal && <NewFolder collection={collection} onClose={() => setShowNewFolderModal(false)} />}
|
||||
{showRenameCollectionModal && <RenameCollection collection={collection} onClose={() => setShowRenameCollectionModal(false)} />}
|
||||
{showRemoveCollectionModal && <RemoveCollection collection={collection} onClose={() => setShowRemoveCollectionModal(false)} />}
|
||||
{showRunCollectionModal && <RunCollectionItem collection={collection} onClose={() => setShowRunCollectionModal(false)} />}
|
||||
{showRenameCollectionModal && (
|
||||
<RenameCollection collection={collection} onClose={() => setShowRenameCollectionModal(false)} />
|
||||
)}
|
||||
{showRemoveCollectionModal && (
|
||||
<RemoveCollection collection={collection} onClose={() => setShowRemoveCollectionModal(false)} />
|
||||
)}
|
||||
{collectionPropertiesModal && (
|
||||
<CollectionProperties collection={collection} onClose={() => setCollectionPropertiesModal(false)} />
|
||||
)}
|
||||
<div className="flex py-1 collection-name items-center" ref={drop}>
|
||||
<div className="flex flex-grow items-center overflow-hidden" onClick={handleClick}>
|
||||
<IconChevronRight size={16} strokeWidth={2} className={iconClassName} style={{ width: 16, minWidth:16, color: 'rgb(160 160 160)' }} />
|
||||
<div className="ml-1" id="sidebar-collection-name">{collection.name}</div>
|
||||
<IconChevronRight
|
||||
size={16}
|
||||
strokeWidth={2}
|
||||
className={iconClassName}
|
||||
style={{ width: 16, minWidth: 16, color: 'rgb(160 160 160)' }}
|
||||
/>
|
||||
<div className="ml-1" id="sidebar-collection-name">
|
||||
{collection.name}
|
||||
</div>
|
||||
</div>
|
||||
<div className="collection-actions">
|
||||
<Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement="bottom-start">
|
||||
@@ -129,7 +154,7 @@ const Collection = ({ collection, searchText }) => {
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
menuDropdownTippyRef.current.hide();
|
||||
setShowRunCollectionModal(true);
|
||||
handleRun();
|
||||
}}
|
||||
>
|
||||
Run
|
||||
@@ -152,6 +177,15 @@ const Collection = ({ collection, searchText }) => {
|
||||
>
|
||||
Export
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
menuDropdownTippyRef.current.hide();
|
||||
setCollectionPropertiesModal(true);
|
||||
}}
|
||||
>
|
||||
Properties
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
|
||||
@@ -18,10 +18,16 @@ const CreateOrOpenCollection = () => {
|
||||
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
|
||||
|
||||
const handleOpenCollection = () => {
|
||||
dispatch(openCollection()).catch((err) => console.log(err) && toast.error('An error occured while opening the collection'));
|
||||
dispatch(openCollection()).catch(
|
||||
(err) => console.log(err) && toast.error('An error occured while opening the collection')
|
||||
);
|
||||
};
|
||||
const CreateLink = () => (
|
||||
<LinkStyle className="underline text-link cursor-pointer" theme={theme} onClick={() => setCreateCollectionModalOpen(true)}>
|
||||
<LinkStyle
|
||||
className="underline text-link cursor-pointer"
|
||||
theme={theme}
|
||||
onClick={() => setCreateCollectionModalOpen(true)}
|
||||
>
|
||||
Create
|
||||
</LinkStyle>
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@ const StyledWrapper = styled.div`
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props) => props.theme.plainGrid.hoverBg};;
|
||||
background-color: ${(props) => props.theme.plainGrid.hoverBg};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -61,7 +61,7 @@ const Collections = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex flex-col">
|
||||
<div className="mt-4 flex flex-col overflow-y-auto absolute top-32 bottom-10 left-0 right-0">
|
||||
{collections && collections.length
|
||||
? collections.map((c) => {
|
||||
return (
|
||||
@@ -77,4 +77,3 @@ const Collections = () => {
|
||||
};
|
||||
|
||||
export default Collections;
|
||||
|
||||
|
||||
@@ -20,8 +20,14 @@ const CreateCollection = ({ onClose }) => {
|
||||
collectionLocation: ''
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
collectionName: Yup.string().min(1, 'must be atleast 1 characters').max(50, 'must be 50 characters or less').required('collection name is required'),
|
||||
collectionFolderName: Yup.string().min(1, 'must be atleast 1 characters').max(50, 'must be 50 characters or less').required('folder name is required'),
|
||||
collectionName: Yup.string()
|
||||
.min(1, 'must be atleast 1 characters')
|
||||
.max(50, 'must be 50 characters or less')
|
||||
.required('collection name is required'),
|
||||
collectionFolderName: Yup.string()
|
||||
.min(1, 'must be atleast 1 characters')
|
||||
.max(50, 'must be 50 characters or less')
|
||||
.required('folder name is required'),
|
||||
collectionLocation: Yup.string().required('location is required')
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
@@ -58,8 +64,8 @@ const CreateCollection = ({ onClose }) => {
|
||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||
<div>
|
||||
<label htmlFor="collectionName" className="flex items-center">
|
||||
<span className='font-semibold'>Name</span>
|
||||
<Tooltip text="Name of the collection" tooltipId="collection-name"/>
|
||||
<span className="font-semibold">Name</span>
|
||||
<Tooltip text="Name of the collection" tooltipId="collection-name" />
|
||||
</label>
|
||||
<input
|
||||
id="collection-name"
|
||||
@@ -74,11 +80,13 @@ const CreateCollection = ({ onClose }) => {
|
||||
spellCheck="false"
|
||||
value={formik.values.collectionName || ''}
|
||||
/>
|
||||
{formik.touched.collectionName && formik.errors.collectionName ? <div className="text-red-500">{formik.errors.collectionName}</div> : null}
|
||||
{formik.touched.collectionName && formik.errors.collectionName ? (
|
||||
<div className="text-red-500">{formik.errors.collectionName}</div>
|
||||
) : null}
|
||||
|
||||
<label htmlFor="collectionFolderName" className="flex items-center mt-3">
|
||||
<span className='font-semibold'>Folder Name</span>
|
||||
<Tooltip text="Name of the folder where your collection is stored" tooltipId="collection-folder-name"/>
|
||||
<span className="font-semibold">Folder Name</span>
|
||||
<Tooltip text="Name of the folder where your collection is stored" tooltipId="collection-folder-name" />
|
||||
</label>
|
||||
<input
|
||||
id="collection-folder-name"
|
||||
@@ -92,7 +100,9 @@ const CreateCollection = ({ onClose }) => {
|
||||
spellCheck="false"
|
||||
value={formik.values.collectionFolderName || ''}
|
||||
/>
|
||||
{formik.touched.collectionFolderName && formik.errors.collectionFolderName ? <div className="text-red-500">{formik.errors.collectionFolderName}</div> : null}
|
||||
{formik.touched.collectionFolderName && formik.errors.collectionFolderName ? (
|
||||
<div className="text-red-500">{formik.errors.collectionFolderName}</div>
|
||||
) : null}
|
||||
|
||||
<>
|
||||
<label htmlFor="collectionLocation" className="block font-semibold mt-3">
|
||||
|
||||
@@ -33,22 +33,13 @@ const ImportCollection = ({ onClose, handleSubmit }) => {
|
||||
return (
|
||||
<Modal size="sm" title="Import Collection" hideFooter={true} handleConfirm={onClose} handleCancel={onClose}>
|
||||
<div>
|
||||
<div
|
||||
className='text-link hover:underline cursor-pointer'
|
||||
onClick={handleImportBrunoCollection}
|
||||
>
|
||||
<div className="text-link hover:underline cursor-pointer" onClick={handleImportBrunoCollection}>
|
||||
Bruno Collection
|
||||
</div>
|
||||
<div
|
||||
className='text-link hover:underline cursor-pointer mt-2'
|
||||
onClick={handleImportPostmanCollection}
|
||||
>
|
||||
<div className="text-link hover:underline cursor-pointer mt-2" onClick={handleImportPostmanCollection}>
|
||||
Postman Collection
|
||||
</div>
|
||||
<div
|
||||
className='text-link hover:underline cursor-pointer mt-2'
|
||||
onClick={handleImportInsomniaCollection}
|
||||
>
|
||||
<div className="text-link hover:underline cursor-pointer mt-2" onClick={handleImportInsomniaCollection}>
|
||||
Insomnia Collection
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,10 @@ const ImportCollectionLocation = ({ onClose, handleSubmit, collectionName }) =>
|
||||
collectionLocation: ''
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
collectionLocation: Yup.string().min(1, 'must be atleast 1 characters').max(500, 'must be 500 characters or less').required('name is required')
|
||||
collectionLocation: Yup.string()
|
||||
.min(1, 'must be atleast 1 characters')
|
||||
.max(500, 'must be 500 characters or less')
|
||||
.required('name is required')
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
console.log('here');
|
||||
@@ -49,7 +52,7 @@ const ImportCollectionLocation = ({ onClose, handleSubmit, collectionName }) =>
|
||||
<label htmlFor="collectionName" className="block font-semibold">
|
||||
Name
|
||||
</label>
|
||||
<div className='mt-2'>{collectionName}</div>
|
||||
<div className="mt-2">{collectionName}</div>
|
||||
|
||||
<>
|
||||
<label htmlFor="collectionLocation" className="block font-semibold mt-3">
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
background-color: ${(props) => props.theme.menubar.bg};
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
min-height: 100vh;
|
||||
|
||||
.menu-item {
|
||||
padding: 0.6rem;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
color: rgba(255, 255, 255);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@@ -1,60 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { isElectron } from 'utils/common/platform';
|
||||
import { toggleLeftMenuBar } from 'providers/ReduxStore/slices/app';
|
||||
import { IconCode, IconFiles, IconMoon, IconChevronsLeft, IconLifebuoy } from '@tabler/icons';
|
||||
|
||||
import Link from 'next/link';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import BrunoSupport from 'components/BrunoSupport';
|
||||
import SwitchTheme from 'components/SwitchTheme';
|
||||
|
||||
const MenuBar = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useDispatch();
|
||||
const [openTheme, setOpenTheme] = useState(false);
|
||||
const [openBrunoSupport, setOpenBrunoSupport] = useState(false);
|
||||
const isPlatformElectron = isElectron();
|
||||
|
||||
const getClassName = (menu) => {
|
||||
return router.pathname === menu ? 'active menu-item' : 'menu-item';
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="h-full flex flex-col">
|
||||
{openBrunoSupport && <BrunoSupport onClose={() => setOpenBrunoSupport(false)} />}
|
||||
{openTheme && <SwitchTheme onClose={() => setOpenTheme(false)} />}
|
||||
|
||||
<div className="flex flex-col">
|
||||
{/* Todo: Fix this: Clicking on this crashes the app */}
|
||||
{/* <Link href="/">
|
||||
<div className={getClassName('/')}>
|
||||
<IconCode size={28} strokeWidth={1.5} />
|
||||
</div>
|
||||
</Link> */}
|
||||
{/* <div className="menu-item">
|
||||
<IconUsers size={28} strokeWidth={1.5}/>
|
||||
</div> */}
|
||||
</div>
|
||||
<div className="flex flex-col flex-grow justify-end">
|
||||
{/* <Link href="/login">
|
||||
<div className="menu-item">
|
||||
<IconUser size={28} strokeWidth={1.5}/>
|
||||
</div>
|
||||
</Link> */}
|
||||
<div className="menu-item" onClick={() => setOpenBrunoSupport(true)}>
|
||||
<IconLifebuoy size={28} strokeWidth={1.5}/>
|
||||
</div>
|
||||
<div className="menu-item" onClick={() => setOpenTheme(true)}>
|
||||
<IconMoon size={28} strokeWidth={1.5}/>
|
||||
</div>
|
||||
<div className="menu-item" onClick={() => dispatch(toggleLeftMenuBar())}>
|
||||
<IconChevronsLeft size={28} strokeWidth={1.5} />
|
||||
</div>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default MenuBar;
|
||||
@@ -21,11 +21,11 @@ const NewFolder = ({ collection, item, onClose }) => {
|
||||
.test({
|
||||
name: 'folderName',
|
||||
message: 'The folder name "environments" at the root of the collection is reserved in bruno',
|
||||
test:(value) => {
|
||||
if(item && item.uid) {
|
||||
test: (value) => {
|
||||
if (item && item.uid) {
|
||||
return true;
|
||||
}
|
||||
return value && !(value.trim().toLowerCase().includes('environments'))
|
||||
return value && !value.trim().toLowerCase().includes('environments');
|
||||
}
|
||||
})
|
||||
}),
|
||||
@@ -64,7 +64,9 @@ const NewFolder = ({ collection, item, onClose }) => {
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.folderName || ''}
|
||||
/>
|
||||
{formik.touched.folderName && formik.errors.folderName ? <div className="text-red-500">{formik.errors.folderName}</div> : null}
|
||||
{formik.touched.folderName && formik.errors.folderName ? (
|
||||
<div className="text-red-500">{formik.errors.folderName}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
@@ -30,7 +30,7 @@ const NewRequest = ({ collection, item, isEphermal, onClose }) => {
|
||||
.test({
|
||||
name: 'requestName',
|
||||
message: 'The request name "index" is reserved in bruno',
|
||||
test: value => value && !(value.trim().toLowerCase().includes('index')),
|
||||
test: (value) => value && !value.trim().toLowerCase().includes('index')
|
||||
})
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
@@ -51,7 +51,7 @@ const NewRequest = ({ collection, item, isEphermal, onClose }) => {
|
||||
addTab({
|
||||
uid: uid,
|
||||
collectionUid: collection.uid,
|
||||
requestPaneTab: getDefaultRequestPaneTab({type: values.requestType})
|
||||
requestPaneTab: getDefaultRequestPaneTab({ type: values.requestType })
|
||||
})
|
||||
);
|
||||
onClose();
|
||||
@@ -140,7 +140,9 @@ const NewRequest = ({ collection, item, isEphermal, onClose }) => {
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.requestName || ''}
|
||||
/>
|
||||
{formik.touched.requestName && formik.errors.requestName ? <div className="text-red-500">{formik.errors.requestName}</div> : null}
|
||||
{formik.touched.requestName && formik.errors.requestName ? (
|
||||
<div className="text-red-500">{formik.errors.requestName}</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
@@ -150,7 +152,10 @@ const NewRequest = ({ collection, item, isEphermal, onClose }) => {
|
||||
|
||||
<div className="flex items-center mt-2 ">
|
||||
<div className="flex items-center h-full method-selector-container">
|
||||
<HttpMethodSelector method={formik.values.requestMethod} onMethodSelect={(val) => formik.setFieldValue('requestMethod', val)} />
|
||||
<HttpMethodSelector
|
||||
method={formik.values.requestMethod}
|
||||
onMethodSelect={(val) => formik.setFieldValue('requestMethod', val)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center flex-grow input-container h-full">
|
||||
<input
|
||||
@@ -167,7 +172,9 @@ const NewRequest = ({ collection, item, isEphermal, onClose }) => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{formik.touched.requestUrl && formik.errors.requestUrl ? <div className="text-red-500">{formik.errors.requestUrl}</div> : null}
|
||||
{formik.touched.requestUrl && formik.errors.requestUrl ? (
|
||||
<div className="text-red-500">{formik.errors.requestUrl}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
@@ -32,7 +32,7 @@ const Wrapper = styled.div`
|
||||
input {
|
||||
border: ${(props) => props.theme.sidebar.search.border};
|
||||
border-radius: 2px;
|
||||
background-color: ${(props) => props.theme.sidebar.search.bg};
|
||||
background-color: ${(props) => props.theme.sidebar.search.bg};
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
|
||||
@@ -45,20 +45,24 @@ const TitleBar = () => {
|
||||
const handleTitleClick = () => dispatch(showHomePage());
|
||||
|
||||
const handleOpenCollection = () => {
|
||||
dispatch(openCollection()).catch((err) => console.log(err) && toast.error('An error occured while opening the collection'));
|
||||
dispatch(openCollection()).catch(
|
||||
(err) => console.log(err) && toast.error('An error occured while opening the collection')
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="px-2 py-2">
|
||||
{createCollectionModalOpen ? <CreateCollection onClose={() => setCreateCollectionModalOpen(false)} /> : null}
|
||||
{importCollectionModalOpen ? <ImportCollection onClose={() => setImportCollectionModalOpen(false)} handleSubmit={handleImportCollection} /> : null}
|
||||
{importCollectionModalOpen ? (
|
||||
<ImportCollection onClose={() => setImportCollectionModalOpen(false)} handleSubmit={handleImportCollection} />
|
||||
) : null}
|
||||
{importCollectionLocationModalOpen ? (
|
||||
<ImportCollectionLocation
|
||||
collectionName={importedCollection.name}
|
||||
onClose={() => setImportCollectionLocationModalOpen(false)}
|
||||
handleSubmit={handleImportCollectionLocation}
|
||||
/>
|
||||
): null}
|
||||
) : null}
|
||||
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center cursor-pointer" onClick={handleTitleClick}>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import MenuBar from './MenuBar';
|
||||
import TitleBar from './TitleBar';
|
||||
import Collections from './Collections';
|
||||
import StyledWrapper, { BottomWrapper, VersionNumber } from './StyledWrapper';
|
||||
import GitHubButton from 'react-github-btn'
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import GitHubButton from 'react-github-btn';
|
||||
import Preferences from 'components/Preferences';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { IconChevronsRight } from '@tabler/icons';
|
||||
import { updateLeftSidebarWidth, updateIsDragging, toggleLeftMenuBar } from 'providers/ReduxStore/slices/app';
|
||||
import { IconSettings } from '@tabler/icons';
|
||||
import { updateLeftSidebarWidth, updateIsDragging } from 'providers/ReduxStore/slices/app';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
|
||||
const MIN_LEFT_SIDEBAR_WIDTH = 222;
|
||||
@@ -15,13 +15,11 @@ const MAX_LEFT_SIDEBAR_WIDTH = 600;
|
||||
|
||||
const Sidebar = () => {
|
||||
const leftSidebarWidth = useSelector((state) => state.app.leftSidebarWidth);
|
||||
const leftMenuBarOpen = useSelector((state) => state.app.leftMenuBarOpen);
|
||||
const [preferencesOpen, setPreferencesOpen] = useState(false);
|
||||
|
||||
const [asideWidth, setAsideWidth] = useState(leftSidebarWidth);
|
||||
|
||||
const {
|
||||
storedTheme
|
||||
} = useTheme();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const [dragging, setDragging] = useState(false);
|
||||
@@ -76,27 +74,28 @@ const Sidebar = () => {
|
||||
setAsideWidth(leftSidebarWidth);
|
||||
}, [leftSidebarWidth]);
|
||||
|
||||
const leftMenuBarWidth = leftMenuBarOpen ? 48 : 0;
|
||||
const collectionsWidth = asideWidth - leftMenuBarWidth;
|
||||
|
||||
return (
|
||||
<StyledWrapper className="flex relative">
|
||||
<StyledWrapper className="flex relative h-screen">
|
||||
<aside>
|
||||
<div className="flex flex-row h-full w-full">
|
||||
{leftMenuBarOpen && <MenuBar />}
|
||||
<div className="flex flex-row h-screen w-full">
|
||||
{preferencesOpen && <Preferences onClose={() => setPreferencesOpen(false)} />}
|
||||
|
||||
<div className="flex flex-col w-full" style={{width: collectionsWidth}}>
|
||||
<div className="flex flex-col w-full" style={{ width: asideWidth }}>
|
||||
<div className="flex flex-col flex-grow">
|
||||
<TitleBar />
|
||||
<Collections />
|
||||
</div>
|
||||
|
||||
<div className="footer flex px-1 py-2 items-center cursor-pointer select-none">
|
||||
<div className="footer flex px-1 py-2 absolute bottom-0 left-0 right-0 items-center cursor-pointer select-none">
|
||||
<div className="flex items-center ml-1 text-xs ">
|
||||
{!leftMenuBarOpen && <IconChevronsRight size={24} strokeWidth={1.5} className="mr-2 hover:text-gray-700" onClick={() => dispatch(toggleLeftMenuBar())} />}
|
||||
{/* <IconLayoutGrid size={20} strokeWidth={1.5} className="mr-2"/> */}
|
||||
<IconSettings
|
||||
size={18}
|
||||
strokeWidth={1.5}
|
||||
className="mr-2 hover:text-gray-700"
|
||||
onClick={() => setPreferencesOpen(true)}
|
||||
/>
|
||||
</div>
|
||||
<div className="pl-1" style={{position: 'relative', top: '3px'}}>
|
||||
<div className="pl-1" style={{ position: 'relative', top: '3px' }}>
|
||||
{storedTheme === 'dark' ? (
|
||||
<GitHubButton
|
||||
href="https://github.com/usebruno/bruno"
|
||||
@@ -117,7 +116,7 @@ const Sidebar = () => {
|
||||
</GitHubButton>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-grow items-center justify-end text-xs mr-2">v0.10.2</div>
|
||||
<div className="flex flex-grow items-center justify-end text-xs mr-2">v0.16.1</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user