Compare commits

..

1 Commits

Author SHA1 Message Date
Anusree Subash
41d9bbd2d8 fix: fixed issue renaming workspaces and creating collections 2022-10-22 16:03:26 +05:30
187 changed files with 910 additions and 5832 deletions

View File

@@ -1,27 +0,0 @@
name: Playwright Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install dependencies
run: npm i --legacy-peer-deps
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npm run test:e2e
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30

View File

@@ -1,19 +0,0 @@
name: Unit Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install dependencies
run: npm i --legacy-peer-deps
- name: Test Package bruno-lang
run: npm run test --workspace=packages/bruno-lang

4
.gitignore vendored
View File

@@ -17,7 +17,6 @@ chrome-extension
chrome-extension.pem
chrome-extension.crx
bruno.zip
*.zip
# misc
.DS_Store
@@ -38,6 +37,3 @@ yarn-error.log*
/renderer
/renderer/.next/
/renderer/out/
/test-results/
/playwright-report/
/playwright/.cache/

2
.nvmrc
View File

@@ -1 +1 @@
v14.18.0
v14.17.0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 474 KiB

View File

@@ -1,31 +1,28 @@
## Lets make bruno better, together !!
I am happy that you are looking to improve bruno. Below are the guidelines to get started bringing up bruno on your computer.
I am happy that you are looking to improve bruno. Below are the guidelines to get started bringing up bruno on your computed.
### Technology Stack
Bruno is built using NextJs and React. We also use electron to ship a desktop version (that supports local collections)
Libraries we use
Libraries we use
* CSS - Tailwind
* Code Editors - Codemirror
* State Management - Redux
* Icons - Tabler Icons
* Forms - formik
* Schema Validation - Yup
* Request Client - axios
* Filesystem Watcher - chokidar
- CSS - Tailwind
- Code Editors - Codemirror
- State Management - Redux
- Icons - Tabler Icons
- Forms - formik
- Schema Validation - Yup
- Request Client - axios
- Filesystem Watcher - chokidar
### Dependencies
You would need [Node v14.x or the latest LTS version](https://nodejs.org/en/) and npm 8.x. We use npm workspaces in the project
You would need Node v14.x and npm 8.x. We use npm workspaces in the project
### Lets start coding
```bash
# clone and cd into bruno
# use Node 14.x, Npm 8.x
# use Node 14.x, Npm 8.x
# Install deps (note that we use npm workspaces)
npm i
@@ -44,10 +41,4 @@ open http://localhost:3000
```
### Raising Pull Request
- Please keep the PR's small and focused on one thing
- Please follow the format of creating branches
- feature/[feature name]: This branch should contain changes for a specific feature
- Example: feature/dark-mode
- bugfix/[bug name]: This branch should container only bug fixes for a specific bug
- Example bugfix/bug-1
* Please keep the PR's small and focused on one thing

55
main/app/menu-template.js Normal file
View File

@@ -0,0 +1,55 @@
const { ipcMain } = require('electron');
const template = [
{
label: 'Collection',
submenu: [
{
label: 'Open Collection',
click () {
ipcMain.emit('main:open-collection');
}
},
{ role: 'quit' }
]
},
{
label: 'Edit',
submenu: [
{ role: 'undo'},
{ role: 'redo'},
{ role: 'separator'},
{ role: 'cut'},
{ role: 'copy'},
{ role: 'paste'}
]
},
{
label: 'View',
submenu: [
{ role: 'reload'},
{ role: 'toggledevtools'},
{ role: 'separator'},
{ role: 'resetzoom'},
{ role: 'zoomin'},
{ role: 'zoomout'},
{ role: 'separator'},
{ role: 'togglefullscreen'}
]
},
{
role: 'window',
submenu: [
{ role: 'minimize'},
{ role: 'close'}
]
},
{
role: 'help',
submenu: [
{ label: 'Learn More'}
]
}
];
module.exports = template;

54
main/index.js Normal file
View File

@@ -0,0 +1,54 @@
const path = require('path');
const { format } = require('url');
const { BrowserWindow, app, Menu } = require('electron');
const { setContentSecurityPolicy } = require('electron-util');
const menuTemplate = require('./app/menu-template');
const registerIpc = require('./ipc');
const isDev = require('electron-is-dev');
const prepareNext = require('electron-next');
setContentSecurityPolicy(`
default-src * 'unsafe-inline' 'unsafe-eval';
script-src * 'unsafe-inline' 'unsafe-eval';
connect-src * 'unsafe-inline';
base-uri 'none';
form-action 'none';
frame-ancestors 'none';
`);
const menu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(menu);
let mainWindow;
// Prepare the renderer once the app is ready
app.on('ready', async () => {
await prepareNext('./renderer');
mainWindow = new BrowserWindow({
width: 1280,
height: 768,
webPreferences: {
nodeIntegration: true,
contextIsolation: true,
preload: path.join(__dirname, "preload.js")
},
});
const url = isDev
? 'http://localhost:8000'
: format({
pathname: path.join(__dirname, '../renderer/out/index.html'),
protocol: 'file:',
slashes: true
});
mainWindow.loadURL(url);
// register all ipc handlers
registerIpc(mainWindow);
});
// Quit the app once all windows are closed
app.on('window-all-closed', app.quit);

47
main/ipc.js Normal file
View File

@@ -0,0 +1,47 @@
const axios = require('axios');
const FormData = require('form-data');
const { ipcMain } = require('electron');
const { forOwn, extend } = require('lodash');
const registerIpc = () => {
// handler for sending http request
ipcMain.handle('send-http-request', async (event, request) => {
try {
// make axios work in node using form data
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
if(request.headers && request.headers['content-type'] === 'multipart/form-data') {
const form = new FormData();
forOwn(request.data, (value, key) => {
form.append(key, value);
});
extend(request.headers, form.getHeaders());
request.data = form;
}
const result = await axios(request);
return {
status: result.status,
headers: result.headers,
data: result.data
};
} catch (error) {
if(error.response) {
return {
status: error.response.status,
headers: error.response.headers,
data: error.response.data
};
}
return {
status: -1,
headers: [],
data: null
};
}
});
};
module.exports = registerIpc;

14
main/preload.js Normal file
View File

@@ -0,0 +1,14 @@
const { ipcRenderer, contextBridge } = require('electron');
contextBridge.exposeInMainWorld('ipcRenderer', {
invoke: (channel, ...args) => ipcRenderer.invoke(channel, ...args),
on: (channel, handler) => {
// Deliberately strip event as it includes `sender`
const subscription = (event, ...args) => handler(...args);
ipcRenderer.on(channel, subscription);
return () => {
ipcRenderer.removeListener(channel, subscription);
};
}
});

14
main/utils/common.js Normal file
View File

@@ -0,0 +1,14 @@
const { customAlphabet } = require('nanoid');
// a customized version of nanoid without using _ and -
const uuid = () => {
// https://github.com/ai/nanoid/blob/main/url-alphabet/index.js
const urlAlphabet = 'useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict';
const customNanoId = customAlphabet (urlAlphabet, 21);
return customNanoId();
};
module.exports = {
uuid
};

View File

@@ -6,28 +6,17 @@
"packages/bruno-electron",
"packages/bruno-tauri",
"packages/bruno-schema",
"packages/bruno-lang",
"packages/bruno-testbench",
"packages/bruno-graphql-docs"
"packages/bruno-testbench"
],
"devDependencies": {
"@faker-js/faker": "^7.6.0",
"@playwright/test": "^1.27.1",
"jest": "^29.2.0",
"randomstring": "^1.2.2"
},
"scripts": {
"dev:web": "npm run dev --workspace=packages/bruno-app",
"build:web": "npm run build --workspace=packages/bruno-app",
"prettier:web": "npm run prettier --workspace=packages/bruno-app",
"dev:electron": "npm run dev --workspace=packages/bruno-electron",
"build:graphql-docs": "npm run build --workspace=packages/bruno-graphql-docs",
"build:chrome-extension": "./scripts/build-chrome-extension.sh",
"build:electron": "./scripts/build-electron.sh",
"test:e2e": "npx playwright test",
"test:report": "npx playwright show-report"
},
"overrides": {
"rollup": "3.2.5"
"build:electron": "./scripts/build-electron.sh"
}
}

View File

@@ -1,39 +0,0 @@
{
"type": "http-request",
"name": "Send Bulk SMS",
"request": {
"method": "GET",
"url": "https://api.textlocal.in/bulk_json?apiKey=secret=&numbers=998877665&message=hello&sender=600010",
"params": [
{
"name": "apiKey",
"value": "secret",
"enabled": true
},
{
"name": "numbers",
"value": "998877665",
"enabled": true
},
{
"name": "message",
"value": "hello",
"enabled": true
},
{
"name": "sender",
"value": "600010",
"enabled": true
}
],
"headers": [],
"body": {
"mode": "json",
"json": "{\n apikey: \"secret\",\n numbers: \"+919988776655\",\n data: {\n sender: \"TXTLCL\",\n messages: [{\n numbers: \"+919988776655\",\n message: \"Hello World\"\n }]\n }\n}",
"text": null,
"xml": null,
"multipartForm": null,
"formUrlEncoded": null
}
}
}

View File

@@ -1,102 +0,0 @@
ver 1.0
type http-request
name Send Bulk SMS
method GET
url https://api.textlocal.in/bulk_json?apiKey=secret=&numbers=919988776655&message=hello&sender=600010
params
1 apiKey secret
1 numbers 998877665
1 message hello
/params
headers
1 content-type application/json
1 accept-language en-US,en;q=0.9,hi;q=0.8
0 transaction-id {{transactionId}}
/headers
body-mode json
body(type=json)
{
apikey: "secret",
numbers: "+91998877665",
data: {
sender: "TXTLCL",
messages: [{
numbers: "+91998877665",
message: "Hello World"
}]
}
}
/body
body(type=graphql)
{
launchesPast {
launch_site {
site_name
}
launch_success
}
}
/body
script
let user = 'John Doe';
function onRequest(request) {
request.body.user = user;
}
function onResponse(request, response) {
expect(response.status).to.equal(200);
}
/script
assert
{
"$res.data.order.items.length": 1,
"$res.data.orderNumber.isDefined": true
}
/assert
vars
1 petId $res.data.id
/vars
readme
Documentation about the request
/readme
response-example
name Created
status 201
headers
1 content-type application/json
1 accept-language en-US,en;q=0.9,hi;q=0.8
0 transaction-id {{transactionId}}
/headers
body
{
"data": {
"launchesPast": [
{
"launch_site": {
"site_name": "CCAFS SLC 40"
},
"launch_success": true
},
{
"launch_site": {
"site_name": "VAFB SLC 4E"
},
"launch_success": true
}
]
}
}
/body
/response-example

View File

@@ -1,101 +0,0 @@
ver 1.0
type http-request
name Send Bulk SMS
method GET
url https://api.textlocal.in/bulk_json?apiKey=secret=&numbers=919988776655&message=hello&sender=600010
body-mode json
params
1 apiKey secret
1 numbers 998877665
1 message hello
/params
headers
1 content-type application/json
1 accept-language en-US,en;q=0.9,hi;q=0.8
0 transaction-id {{transactionId}}
/headers
body(type=json)
{
apikey: "secret",
numbers: "+91998877665",
data: {
sender: "TXTLCL",
messages: [{
numbers: "+91998877665",
message: "Hello World"
}]
}
}
/body
body(type=graphql)
{
launchesPast {
launch_site {
site_name
}
launch_success
}
}
/body
script
let user = 'John Doe';
function onRequest(request) {
request.body.user = user;
}
function onResponse(request, response) {
expect(response.status).to.equal(200);
}
/script
assert
{
"$res.data.order.items.length": 1,
"$res.data.orderNumber.isDefined": true
}
/assert
vars
1 petId $res.data.id
/vars
readme
Documentation about the request
/readme
response-example
name Created
status 201
headers
1 content-type application/json
1 accept-language en-US,en;q=0.9,hi;q=0.8
0 transaction-id {{transactionId}}
/headers
body
{
"data": {
"launchesPast": [
{
"launch_site": {
"site_name": "CCAFS SLC 40"
},
"launch_success": true
},
{
"launch_site": {
"site_name": "VAFB SLC 4E"
},
"launch_success": true
}
]
}
}
/body
/response-example

View File

@@ -6,8 +6,6 @@
"paths": {
"assets/*": ["src/assets/*"],
"components/*": ["src/components/*"],
"hooks/*": ["src/hooks/*"],
"themes/*": ["src/themes/*"],
"api/*": ["src/api/*"],
"pageComponents/*": ["src/pageComponents/*"],
"providers/*": ["src/providers/*"],

View File

@@ -1,9 +1,5 @@
module.exports = {
reactStrictMode: false,
publicRuntimeConfig: {
CI: process.env.CI,
PLAYWRIGHT: process.env.PLAYWRIGHT
},
reactStrictMode: true,
webpack: (config, { isServer }) => {
// Fixes npm packages that depend on `fs` module
if (!isServer) {

View File

@@ -1,6 +1,5 @@
{
"name": "@usebruno/app",
"version": "0.3.0",
"private": true,
"scripts": {
"dev": "next dev",
@@ -16,8 +15,7 @@
"@reduxjs/toolkit": "^1.8.0",
"@tabler/icons": "^1.46.0",
"@tippyjs/react": "^4.2.6",
"@usebruno/graphql-docs": "0.1.0",
"@usebruno/schema": "0.2.0",
"@usebruno/schema": "0.1.0",
"axios": "^0.26.0",
"classnames": "^2.3.1",
"codemirror": "^5.65.2",
@@ -32,21 +30,16 @@
"idb": "^7.0.0",
"immer": "^9.0.15",
"lodash": "^4.17.21",
"markdown-it": "^13.0.1",
"mousetrap": "^1.6.5",
"nanoid": "3.3.4",
"next": "12.3.3",
"next": "12.3.1",
"path": "^0.12.7",
"platform": "^1.3.6",
"posthog-node": "^2.1.0",
"qs": "^6.11.0",
"react": "18.2.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "18.2.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-hot-toast": "^2.4.0",
"react-redux": "^7.2.6",
"reckonjs": "^0.1.2",
"react-tabs": "^3.2.3",
"sass": "^1.46.0",
"split-on-first": "^3.0.0",
"styled-components": "^5.3.3",

View File

Before

Width:  |  Height:  |  Size: 436 KiB

After

Width:  |  Height:  |  Size: 436 KiB

View File

@@ -1,30 +0,0 @@
const darkTheme = {
brand: '#546de5',
text: 'rgb(52 52 52)',
'primary-text': '#ffffff',
'primary-theme': '#1e1e1e',
'secondary-text': '#929292',
'sidebar-collection-item-active-indent-border': '#d0d0d0',
'sidebar-collection-item-active-background': '#e1e1e1',
'sidebar-background': '#252526',
'sidebar-bottom-bg': '#68217a',
'request-dragbar-background': '#efefef',
'request-dragbar-background-active': 'rgb(200, 200, 200)',
'tab-inactive': 'rgb(155 155 155)',
'tab-active-border': '#546de5',
'layout-border': '#dedede',
'codemirror-border': '#efefef',
'codemirror-background': 'rgb(243, 243, 243)',
'text-link': '#1663bb',
'text-danger': 'rgb(185, 28, 28)',
'background-danger': '#dc3545',
'method-get': 'rgb(5, 150, 105)',
'method-post': '#8e44ad',
'method-delete': 'rgb(185, 28, 28)',
'method-patch': 'rgb(52 52 52)',
'method-options': 'rgb(52 52 52)',
'method-head': 'rgb(52 52 52)',
'table-stripe': '#f3f3f3'
};
export default darkTheme;

View File

@@ -1,7 +0,0 @@
import darkTheme from './dark';
import lightTheme from './light';
export default {
Light: lightTheme,
Dark: darkTheme
};

View File

@@ -1,30 +0,0 @@
const lightTheme = {
brand: '#546de5',
text: 'rgb(52 52 52)',
'primary-text': 'rgb(52 52 52)',
'primary-theme': '#ffffff',
'secondary-text': '#929292',
'sidebar-collection-item-active-indent-border': '#d0d0d0',
'sidebar-collection-item-active-background': '#e1e1e1',
'sidebar-background': '#f3f3f3',
'sidebar-bottom-bg': '#f3f3f3',
'request-dragbar-background': '#efefef',
'request-dragbar-background-active': 'rgb(200, 200, 200)',
'tab-inactive': 'rgb(155 155 155)',
'tab-active-border': '#546de5',
'layout-border': '#dedede',
'codemirror-border': '#efefef',
'codemirror-background': 'rgb(243, 243, 243)',
'text-link': '#1663bb',
'text-danger': 'rgb(185, 28, 28)',
'background-danger': '#dc3545',
'method-get': 'rgb(5, 150, 105)',
'method-post': '#8e44ad',
'method-delete': 'rgb(185, 28, 28)',
'method-patch': 'rgb(52 52 52)',
'method-options': 'rgb(52 52 52)',
'method-head': 'rgb(52 52 52)',
'table-stripe': '#f3f3f3'
};
export default lightTheme;

View File

@@ -1,7 +1,7 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
color: ${(props) => props.theme.text};
color: var(--color-text);
.collection-options {
svg {
position: relative;

View File

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

View File

@@ -2,31 +2,12 @@ import styled from 'styled-components';
const StyledWrapper = styled.div`
div.CodeMirror {
background: ${(props) => props.theme.codemirror.bg};
border: solid 1px ${(props) => props.theme.codemirror.border};
border: solid 1px var(--color-codemirror-border);
}
textarea.cm-editor {
position: relative;
}
// Todo: dark mode temporary fix
// Clean this
.cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute {
color: #9cdcfe !important;
}
.cm-s-monokai span.cm-string {
color: #ce9178 !important;
}
.cm-s-monokai span.cm-number{
color: #b5cea8 !important;
}
.cm-s-monokai span.cm-atom{
color: #569cd6 !important;
}
`;
export default StyledWrapper;

View File

@@ -38,8 +38,7 @@ export default class QueryEditor extends React.Component {
showCursorWhenSelecting: true,
foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
readOnly: this.props.readOnly,
theme: this.props.theme === 'dark' ? 'monokai' : 'default',
readOnly: this.props.readOnly ? 'nocursor' : false,
extraKeys: {
'Cmd-Enter': () => {
if (this.props.onRun) {
@@ -61,8 +60,6 @@ export default class QueryEditor extends React.Component {
this.props.onSave();
}
},
'Cmd-F': 'findPersistent',
'Ctrl-F': 'findPersistent',
Tab: function (cm) {
cm.replaceSelection(' ', 'end');
}
@@ -90,10 +87,6 @@ export default class QueryEditor extends React.Component {
this.editor.setValue(this.props.value);
this.editor.setOption('mode', this.props.mode);
}
if (this.props.theme !== prevProps.theme && this.editor) {
this.editor.setOption('theme', this.props.theme === 'dark' ? 'monokai' : 'default');
}
this.ignoreChangeEvent = false;
}

View File

@@ -9,11 +9,11 @@ const Wrapper = styled.div`
.tippy-box {
min-width: 135px;
background-color: white;
font-size: 0.8125rem;
color: ${(props) => props.theme.dropdown.color};
background-color: ${(props) => props.theme.dropdown.bg};
box-shadow: ${(props) => props.theme.dropdown.shadow};
border-radius: 3px;
color: rgb(48 48 48);
background: #fff;
box-shadow: rgb(50 50 93 / 25%) 0px 6px 12px -2px, rgb(0 0 0 / 30%) 0px 3px 7px -3px;
.tippy-content {
padding-left: 0;
@@ -25,7 +25,6 @@ const Wrapper = styled.div`
display: flex;
align-items: center;
padding: 0.35rem 0.6rem;
background-color: ${(props) => props.theme.dropdown.labelBg};
}
.dropdown-item {
@@ -34,16 +33,8 @@ const Wrapper = styled.div`
padding: 0.35rem 0.6rem;
cursor: pointer;
.icon {
color: ${(props) => props.theme.dropdown.iconColor};
}
&:hover {
background-color: ${(props) => props.theme.dropdown.hoverBg};
}
&.border-top {
border-top: solid 1px ${(props) => props.theme.dropdown.seperator};
background-color: #e9e9e9;
}
}
}

View File

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

View File

@@ -65,7 +65,7 @@ const EnvironmentSelector = ({ collection }) => {
>
<span>No Environment</span>
</div>
<div className="dropdown-item border-top" onClick={() => setOpenSettingsModal(true)}>
<div className="dropdown-item" style={{ borderTop: 'solid 1px #e7e7e7' }} onClick={() => setOpenSettingsModal(true)}>
<div className="pr-2 text-gray-600">
<IconSettings size={18} strokeWidth={1.5} />
</div>

View File

@@ -8,11 +8,11 @@ const Wrapper = styled.div`
thead,
td {
border: 1px solid ${(props) => props.theme.collection.environment.settings.gridBorder};
border: 1px solid #efefef;
}
thead {
color: ${(props) => props.theme.table.thead.color};;
color: #616161;
font-size: 0.8125rem;
user-select: none;
}
@@ -29,7 +29,6 @@ const Wrapper = styled.div`
width: 100%;
border: solid 1px transparent;
outline: none !important;
background-color: transparent;
&:focus {
outline: none !important;

View File

@@ -4,11 +4,8 @@ const StyledWrapper = styled.div`
margin-inline: -1rem;
margin-block: -1.5rem;
background-color: ${(props) => props.theme.collection.environment.settings.bg};
.environments-sidebar {
background-color: ${(props) => props.theme.collection.environment.settings.sidebar.bg};
border-right: solid 1px ${(props) => props.theme.collection.environment.settings.sidebar.borderRight};
background-color: #eaeaea;
min-height: 400px;
}
@@ -23,15 +20,15 @@ const StyledWrapper = styled.div`
&:hover {
text-decoration: none;
background-color: ${(props) => props.theme.collection.environment.settings.item.hoverBg};
background-color: #e4e4e4;
}
}
.active {
background-color: ${(props) => props.theme.collection.environment.settings.item.active.bg} !important;
border-left: solid 2px ${(props) => props.theme.collection.environment.settings.item.border};
background-color: #dcdcdc !important;
border-left: solid 2px var(--color-brand);
&:hover {
background-color: ${(props) => props.theme.collection.environment.settings.item.active.hoverBg} !important;
background-color: #dcdcdc !important;
}
}
@@ -39,7 +36,7 @@ const StyledWrapper = styled.div`
padding: 8px 10px;
cursor: pointer;
border-bottom: none;
color: ${(props) => props.theme.textLink};
color: var(--color-text-link);
&:hover {
span {

View File

@@ -9,8 +9,8 @@ const EnvironmentList = ({ collection }) => {
const [openCreateModal, setOpenCreateModal] = useState(false);
useEffect(() => {
setSelectedEnvironment(environments && environments.length ? environments[0] : null);
}, [environments]);
setSelectedEnvironment(environments[0]);
}, []);
if (!selectedEnvironment) {
return null;

View File

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

View File

@@ -66,8 +66,8 @@ const Wrapper = styled.div`
justify-content: space-between;
align-items: center;
text-transform: uppercase;
color: ${(props) => props.theme.modal.title.color};
background-color: ${(props) => props.theme.modal.title.bg};
color: rgb(86 86 86);
background-color: #f1f1f1;
font-size: 0.75rem;
padding: 12px;
font-weight: 600;
@@ -77,7 +77,7 @@ const Wrapper = styled.div`
.close {
font-size: 1.3rem;
line-height: 1;
color: ${(props) => props.theme.modal.iconColor};
color: #000;
text-shadow: 0 1px 0 #fff;
opacity: 0.5;
margin-top: -2px;
@@ -90,30 +90,7 @@ const Wrapper = styled.div`
.bruno-modal-content {
flex-grow: 1;
background-color: ${(props) => props.theme.modal.body.bg};
.textbox {
line-height: 1.42857143;
border: 1px solid #ccc;
padding: 0.45rem;
box-shadow: none;
border-radius: 0px;
outline: none;
box-shadow: none;
transition: border-color ease-in-out .1s;
border-radius: 3px;
background-color: ${(props) => props.theme.modal.input.bg};
border: 1px solid ${(props) => props.theme.modal.input.border};
&:focus {
border: solid 1px ${(props) => props.theme.modal.input.focusBorder} !important;
outline: none !important;
}
}
.bruno-form {
color: ${(props) => props.theme.modal.body.color};
}
background-color: #fff;
}
.bruno-modal-backdrop {
@@ -130,7 +107,7 @@ const Wrapper = styled.div`
height: 100%;
width: 100%;
left: 0;
opacity: ${(props) => props.theme.modal.backdrop.opacity};
opacity: 0.4;
top: 0;
background: black;
position: fixed;
@@ -140,17 +117,10 @@ const Wrapper = styled.div`
}
.bruno-modal-footer {
background-color: ${(props) => props.theme.modal.body.bg};
background-color: white;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
&.modal-footer-none {
.bruno-modal-content {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
}
`;
export default Wrapper;

View File

@@ -64,9 +64,6 @@ const Modal = ({ size, title, confirmText, cancelText, handleCancel, handleConfi
if (isClosing) {
classes += ' modal--animate-out';
}
if(hideFooter) {
classes += ' modal-footer-none';
}
return (
<StyledWrapper className={classes}>
<div className={`bruno-modal-card modal-${size}`}>

View File

@@ -8,11 +8,11 @@ const Wrapper = styled.div`
thead,
td {
border: 1px solid ${(props) => props.theme.table.border};
border: 1px solid #efefef;
}
thead {
color: ${(props) => props.theme.table.thead.color};
color: #616161;
font-size: 0.8125rem;
user-select: none;
}
@@ -29,7 +29,6 @@ const Wrapper = styled.div`
width: 100%;
border: solid 1px transparent;
outline: none !important;
color: ${(props) => props.theme.table.input.color};
&:focus {
outline: none !important;

View File

@@ -1,14 +1,22 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
div.tabs {
div.tab {
.react-tabs__tab-list {
border-bottom: none !important;
padding-top: 0;
padding-left: 0 !important;
display: flex;
align-items: center;
margin: 0;
.react-tabs__tab {
padding: 6px 0px;
border: none;
user-select: none;
border-bottom: solid 2px transparent;
margin-right: 1.25rem;
color: var(--color-tab-inactive);
cursor: pointer;
margin-right: 20px;
color: rgb(125 125 125);
outline: none !important;
&:focus,
&:active,
@@ -19,12 +27,36 @@ const StyledWrapper = styled.div`
box-shadow: none !important;
}
&.active {
color: ${(props) => props.theme.tabs.active.color} !important;
border-bottom: solid 2px ${(props) => props.theme.tabs.active.border} !important;
&:after {
display: none !important;
}
}
}
.react-tabs__tab--selected {
border: none;
color: #322e2c !important;
border-bottom: solid 2px var(--color-tab-active-border) !important;
border-color: var(--color-tab-active-border) !important;
background: inherit;
outline: none !important;
box-shadow: none !important;
&:focus,
&:active,
&:focus-within,
&:focus-visible,
&:target {
border: none;
outline: none !important;
box-shadow: none !important;
border-bottom: solid 2px var(--color-tab-active-border) !important;
border-color: var(--color-tab-active-border) !important;
background: inherit;
outline: none !important;
box-shadow: none !important;
}
}
`;
export default StyledWrapper;

View File

@@ -1,133 +1,26 @@
import React, { useEffect } from 'react';
import find from 'lodash/find';
import get from 'lodash/get';
import classnames from 'classnames';
import { IconRefresh, IconLoader2, IconBook, IconDownload } from '@tabler/icons';
import { useSelector, useDispatch } from 'react-redux';
import { updateRequestPaneTab } from 'providers/ReduxStore/slices/tabs';
import React from 'react';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import QueryEditor from 'components/RequestPane/QueryEditor';
import RequestHeaders from 'components/RequestPane/RequestHeaders';
import { useTheme } from 'providers/Theme';
import { updateRequestGraphqlQuery } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import useGraphqlSchema from './useGraphqlSchema';
import StyledWrapper from './StyledWrapper';
const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, toggleDocs, handleGqlClickReference }) => {
const dispatch = useDispatch();
const tabs = useSelector((state) => state.tabs.tabs);
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
const query = item.draft ? get(item, 'draft.request.body.graphql.query') : get(item, 'request.body.graphql.query');
const url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
const {
storedTheme
} = useTheme();
let {
schema,
loadSchema,
isLoading: isSchemaLoading,
error: schemaError
} = useGraphqlSchema(url);
const loadGqlSchema = () => {
if(!isSchemaLoading) {
loadSchema();
}
};
useEffect(() => {
if(onSchemaLoad) {
onSchemaLoad(schema);
}
}, [schema]);
const onQueryChange = (value) => {
dispatch(
updateRequestGraphqlQuery({
query: value,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
const onRun = () => dispatch(sendRequest(item, collection.uid));
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
const selectTab = (tab) => {
dispatch(
updateRequestPaneTab({
uid: item.uid,
requestPaneTab: tab
})
);
};
const getTabPanel = (tab) => {
switch (tab) {
case 'query': {
return <QueryEditor
theme={storedTheme}
schema={schema}
width={leftPaneWidth}
onSave={onSave}
value={query}
onRun={onRun}
onEdit={onQueryChange}
onClickReference={handleGqlClickReference}
/>;
}
case 'headers': {
return <RequestHeaders item={item} collection={collection} />;
}
default: {
return <div className="mt-4">404 | Not found</div>;
}
}
};
if (!activeTabUid) {
return <div>Something went wrong</div>;
}
const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
if (!focusedTab || !focusedTab.uid || !focusedTab.requestPaneTab) {
return <div className="pb-4 px-4">An error occured!</div>;
}
const getTabClassname = (tabName) => {
return classnames(`tab select-none ${tabName}`, {
active: tabName === focusedTab.requestPaneTab
});
};
const GraphQLRequestPane = ({ onRunQuery, schema, leftPaneWidth, value, onQueryChange }) => {
return (
<StyledWrapper className="flex flex-col h-full relative">
<div className="flex items-center tabs" role="tablist">
<div className={getTabClassname('query')} role="tab" onClick={() => selectTab('query')}>
Query
</div>
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
Headers
</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 ? 'Schema' : 'Load Schema'}</span>
<StyledWrapper className="h-full">
<Tabs className="react-tabs mt-1 flex flex-grow flex-col h-full" forceRenderTabPanel>
<TabList>
<Tab tabIndex="-1">Query</Tab>
<Tab tabIndex="-1">Headers</Tab>
</TabList>
<TabPanel>
<div className="mt-4">
<QueryEditor schema={schema} width={leftPaneWidth} value={value} onRunQuery={onRunQuery} onEdit={onQueryChange} />
</div>
<div
className='flex items-center cursor-pointer hover:underline ml-2'
onClick={toggleDocs}
>
<IconBook size={18} strokeWidth={1.5} /><span className='ml-1'>Docs</span>
</div>
</div>
</div>
<section className="flex w-full mt-5">{getTabPanel(focusedTab.requestPaneTab)}</section>
</TabPanel>
<TabPanel>
<RequestHeaders />
</TabPanel>
</Tabs>
</StyledWrapper>
);
};

View File

@@ -1,70 +0,0 @@
import { useState } from 'react';
import toast from 'react-hot-toast';
import { getIntrospectionQuery, buildClientSchema } from 'graphql';
import { simpleHash } from 'utils/common';
const schemaHashPrefix = 'bruno.graphqlSchema';
const fetchSchema = (endpoint) => {
const introspectionQuery = getIntrospectionQuery();
const queryParams = {
query: introspectionQuery
};
return fetch(endpoint, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(queryParams)
});
}
const useGraphqlSchema = (endpoint) => {
const localStorageKey = `${schemaHashPrefix}.${simpleHash(endpoint)}`;
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [schema, setSchema] = useState(() => {
try {
const saved = localStorage.getItem(localStorageKey);
if(!saved) {
return null;
}
return buildClientSchema(JSON.parse(saved));
} catch {
localStorage.setItem(localStorageKey, null);
return null;
}
});
const loadSchema = () => {
setIsLoading(true);
fetchSchema(endpoint)
.then((res) => res.json())
.then((s) => {
if (s && s.data) {
setSchema(buildClientSchema(s.data));
setIsLoading(false);
localStorage.setItem(localStorageKey, JSON.stringify(s.data));
toast.success('Graphql Schema loaded successfully');
} else {
return Promise.reject(new Error('An error occurred while introspecting schema'));
}
})
.catch((err) => {
setIsLoading(false);
setError(err);
toast.error('Error occured while loading Graphql Schema');
});
};
return {
isLoading,
schema,
loadSchema,
error
};
};
export default useGraphqlSchema;

View File

@@ -20,8 +20,8 @@ const StyledWrapper = styled.div`
}
&.active {
color: ${(props) => props.theme.tabs.active.color} !important;
border-bottom: solid 2px ${(props) => props.theme.tabs.active.border} !important;
color: #322e2c !important;
border-bottom: solid 2px var(--color-tab-active-border) !important;
}
}
}

View File

@@ -8,11 +8,11 @@ const Wrapper = styled.div`
thead,
td {
border: 1px solid ${(props) => props.theme.table.border};
border: 1px solid #efefef;
}
thead {
color: ${(props) => props.theme.table.thead.color};
color: #616161;
font-size: 0.8125rem;
user-select: none;
}
@@ -29,7 +29,6 @@ const Wrapper = styled.div`
width: 100%;
border: solid 1px transparent;
outline: none !important;
color: ${(props) => props.theme.table.input.color};
&:focus {
outline: none !important;

View File

@@ -2,33 +2,14 @@ import styled from 'styled-components';
const StyledWrapper = styled.div`
div.CodeMirror {
background: ${(props) => props.theme.codemirror.bg};
border: solid 1px ${(props) => props.theme.codemirror.border};
border: solid 1px var(--color-codemirror-border);
/* todo: find a better way */
height: calc(100vh - 220px);
height: calc(100vh - 250px);
}
textarea.cm-editor {
position: relative;
}
// Todo: dark mode temporary fix
// Clean this
.cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute {
color: #9cdcfe !important;
}
.cm-s-monokai span.cm-string {
color: #ce9178 !important;
}
.cm-s-monokai span.cm-number{
color: #b5cea8 !important;
}
.cm-s-monokai span.cm-atom{
color: #569cd6 !important;
}
`;
export default StyledWrapper;

View File

@@ -38,7 +38,6 @@ export default class QueryEditor extends React.Component {
tabSize: 2,
mode: 'graphql',
theme: this.props.editorTheme || 'graphiql',
theme: this.props.theme === 'dark' ? 'monokai' : 'default',
keyMap: 'sublime',
autoCloseBrackets: true,
matchBrackets: true,
@@ -76,51 +75,54 @@ export default class QueryEditor extends React.Component {
'Alt-Space': () => editor.showHint({ completeSingle: true, container: this._node }),
'Shift-Space': () => editor.showHint({ completeSingle: true, container: this._node }),
'Shift-Alt-Space': () => editor.showHint({ completeSingle: true, container: this._node }),
'Cmd-Enter': () => {
if (this.props.onRun) {
this.props.onRun();
if (this.props.onRunQuery) {
this.props.onRunQuery();
}
},
'Ctrl-Enter': () => {
if (this.props.onRun) {
this.props.onRun();
if (this.props.onRunQuery) {
this.props.onRunQuery();
}
},
'Shift-Ctrl-C': () => {
if (this.props.onCopyQuery) {
this.props.onCopyQuery();
}
},
'Shift-Ctrl-P': () => {
if (this.props.onPrettifyQuery) {
this.props.onPrettifyQuery();
}
},
/* Shift-Ctrl-P is hard coded in Firefox for private browsing so adding an alternative to Pretiffy */
'Shift-Ctrl-F': () => {
if (this.props.onPrettifyQuery) {
this.props.onPrettifyQuery();
}
},
'Shift-Ctrl-M': () => {
if (this.props.onMergeQuery) {
this.props.onMergeQuery();
}
},
'Cmd-S': () => {
if (this.props.onSave) {
this.props.onSave();
return false;
if (this.props.onRunQuery) {
// empty
}
},
'Ctrl-S': () => {
if (this.props.onSave) {
this.props.onSave();
return false;
if (this.props.onRunQuery) {
// empty
}
},
'Cmd-F': 'findPersistent',
'Ctrl-F': 'findPersistent'
}
}
}));
if (editor) {
@@ -147,10 +149,6 @@ export default class QueryEditor extends React.Component {
this.cachedValue = this.props.value;
this.editor.setValue(this.props.value);
}
if (this.props.theme !== prevProps.theme && this.editor) {
this.editor.setOption('theme', this.props.theme === 'dark' ? 'monokai' : 'default');
}
this.ignoreChangeEvent = false;
}
@@ -166,7 +164,7 @@ export default class QueryEditor extends React.Component {
render() {
return (
<StyledWrapper
className="h-full w-full"
className="h-full"
aria-label="Query Editor"
ref={(node) => {
this._node = node;
@@ -175,11 +173,8 @@ export default class QueryEditor extends React.Component {
);
}
_onKeyUp = (_cm, e) => {
if (e.metaKey || e.ctrlKey || e.altKey) {
return;
}
if (AUTO_COMPLETE_AFTER_KEY.test(e.key) && this.editor) {
_onKeyUp = (_cm, event) => {
if (AUTO_COMPLETE_AFTER_KEY.test(event.key) && this.editor) {
this.editor.execCommand('autocomplete');
}
};

View File

@@ -8,11 +8,11 @@ const Wrapper = styled.div`
thead,
td {
border: 1px solid ${(props) => props.theme.table.border};
border: 1px solid #efefef;
}
thead {
color: ${(props) => props.theme.table.thead.color};;
color: #616161;
font-size: 0.8125rem;
user-select: none;
}
@@ -32,7 +32,6 @@ const Wrapper = styled.div`
width: 100%;
border: solid 1px transparent;
outline: none !important;
background-color: inherit;
&:focus {
outline: none !important;

View File

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

View File

@@ -4,18 +4,18 @@ const Wrapper = styled.div`
height: 2.3rem;
div.method-selector-container {
background-color: ${(props) => props.theme.requestTabPanel.url.bg};
background-color: var(--color-sidebar-background);
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
}
div.input-container {
background-color: ${(props) => props.theme.requestTabPanel.url.bg};
background-color: var(--color-sidebar-background);
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
input {
background-color: ${(props) => props.theme.requestTabPanel.url.bg};
background-color: var(--color-sidebar-background);
outline: none;
box-shadow: none;

View File

@@ -3,15 +3,13 @@ import get from 'lodash/get';
import { useDispatch } from 'react-redux';
import { requestUrlChanged, updateRequestMethod } from 'providers/ReduxStore/slices/collections';
import HttpMethodSelector from './HttpMethodSelector';
import { useTheme } from 'providers/Theme';
import SendIcon from 'components/Icons/Send';
import StyledWrapper from './StyledWrapper';
import SendSvg from 'assets/send.svg';
const QueryUrl = ({ item, collection, handleRun }) => {
const { theme } = useTheme();
const dispatch = useDispatch();
const method = item.draft ? get(item, 'draft.request.method') : get(item, 'request.method');
const url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
let url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
const onUrlChange = (value) => {
dispatch(
@@ -49,8 +47,8 @@ const QueryUrl = ({ item, collection, handleRun }) => {
spellCheck="false"
onChange={(event) => onUrlChange(event.target.value)}
/>
<div className="flex items-center h-full mr-2 cursor-pointer" id="send-request" onClick={handleRun}>
<SendIcon color={theme.requestTabPanel.url.icon} width={22}/>
<div className="flex items-center h-full mr-2 cursor-pointer" onClick={handleRun}>
<img src={SendSvg.src} style={{ width: '22px' }} />
</div>
</div>
</StyledWrapper>

View File

@@ -4,7 +4,7 @@ const Wrapper = styled.div`
font-size: 0.8125rem;
.body-mode-selector {
background: ${(props) => props.theme.requestTabPanel.bodyModeSelect.color};
background: #efefef;
border-radius: 3px;
.dropdown-item {

View File

@@ -4,7 +4,6 @@ import CodeEditor from 'components/CodeEditor';
import FormUrlEncodedParams from 'components/RequestPane/FormUrlEncodedParams';
import MultipartFormParams from 'components/RequestPane/MultipartFormParams';
import { useDispatch } from 'react-redux';
import { useTheme } from 'providers/Theme';
import { updateRequestBody } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
@@ -13,9 +12,6 @@ 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 onEdit = (value) => {
dispatch(
@@ -45,7 +41,7 @@ const RequestBody = ({ item, collection }) => {
return (
<StyledWrapper className="w-full">
<CodeEditor theme={storedTheme} value={bodyContent[bodyMode] || ''} onEdit={onEdit} onRun={onRun} onSave={onSave} mode={codeMirrorMode[bodyMode]} />
<CodeEditor value={bodyContent[bodyMode] || ''} onEdit={onEdit} onRun={onRun} onSave={onSave} mode={codeMirrorMode[bodyMode]} />
</StyledWrapper>
);
}

View File

@@ -8,11 +8,11 @@ const Wrapper = styled.div`
thead,
td {
border: 1px solid ${(props) => props.theme.table.border};
border: 1px solid #efefef;
}
thead {
color: ${(props) => props.theme.table.thead.color};
color: #616161;
font-size: 0.8125rem;
user-select: none;
}
@@ -23,13 +23,14 @@ const Wrapper = styled.div`
.btn-add-header {
font-size: 0.8125rem;
margin-block: 10px;
padding: 5px;
}
input[type='text'] {
width: 100%;
border: solid 1px transparent;
outline: none !important;
background-color: inherit;
&:focus {
outline: none !important;

View File

@@ -124,7 +124,7 @@ const RequestHeaders = ({ item, collection }) => {
: null}
</tbody>
</table>
<button className="btn-add-header text-link pr-2 py-3 mt-2 select-none" onClick={addHeader}>
<button className="btn-add-header select-none" onClick={addHeader}>
+ Add Header
</button>
</StyledWrapper>

View File

@@ -18,30 +18,11 @@ const StyledWrapper = styled.div`
display: flex;
height: 100%;
width: 1px;
border-left: solid 1px ${(props) => props.theme.requestTabPanel.dragbar.border};
border-left: solid 1px var(--color-request-dragbar-background);
}
&:hover div.drag-request-border {
border-left: solid 1px ${(props) => props.theme.requestTabPanel.dragbar.activeBorder};
}
}
div.graphql-docs-explorer-container {
background: white;
outline: none;
box-shadow: rgb(0 0 0 / 15%) 0px 0px 8px;
position: absolute;
right: 0px;
z-index: 2000;
width: 350px;
height: 100%;
div.doc-explorer-title {
text-align: left;
}
div.doc-explorer-rhs {
display: flex;
border-left: solid 1px var(--color-request-dragbar-background-active);
}
}
`;

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect } from 'react';
import find from 'lodash/find';
import toast from 'react-hot-toast';
import { useSelector, useDispatch } from 'react-redux';
@@ -12,14 +12,10 @@ import { sendRequest } from 'providers/ReduxStore/slices/collections/actions';
import RequestNotFound from './RequestNotFound';
import QueryUrl from 'components/RequestPane/QueryUrl';
import NetworkError from 'components/ResponsePane/NetworkError';
import { DocExplorer } from '@usebruno/graphql-docs';
import useGraphqlSchema from '../../hooks/useGraphqlSchema';
import StyledWrapper from './StyledWrapper';
const MIN_LEFT_PANE_WIDTH = 300;
const MIN_RIGHT_PANE_WIDTH = 350;
const DEFAULT_PADDING = 5;
const RequestTabPanel = () => {
if (typeof window == 'undefined') {
return <div></div>;
@@ -33,43 +29,23 @@ 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 [rightPaneWidth, setRightPaneWidth] = useState(screenWidth - asideWidth - leftPaneWidth - DEFAULT_PADDING);
const [rightPaneWidth, setRightPaneWidth] = useState(screenWidth - asideWidth - leftPaneWidth - 5);
const [dragging, setDragging] = useState(false);
// Not a recommended pattern here to have the child component
// make a callback to set state, but treating this as an exception
const docExplorerRef = useRef(null);
const [schema, setSchema] = useState(null);
const [showGqlDocs, setShowGqlDocs] = useState(false);
const onSchemaLoad = (schema) => setSchema(schema);
const toggleDocs = () => setShowGqlDocs((showGqlDocs) => !showGqlDocs);
const handleGqlClickReference = (reference) => {
if(docExplorerRef.current) {
docExplorerRef.current.showDocForReference(reference);
}
if(!showGqlDocs) {
setShowGqlDocs(true);
}
};
useEffect(() => {
const leftPaneWidth = (screenWidth - asideWidth) / 2.2;
setLeftPaneWidth(leftPaneWidth);
}, [screenWidth]);
useEffect(() => {
setRightPaneWidth(screenWidth - asideWidth - leftPaneWidth - DEFAULT_PADDING);
setRightPaneWidth(screenWidth - asideWidth - leftPaneWidth - 5);
}, [screenWidth, asideWidth, leftPaneWidth]);
const handleMouseMove = (e) => {
if (dragging) {
e.preventDefault();
let leftPaneXPosition = e.clientX + 2;
if (leftPaneXPosition < (asideWidth+ DEFAULT_PADDING + MIN_LEFT_PANE_WIDTH) || leftPaneXPosition > (screenWidth - MIN_RIGHT_PANE_WIDTH )) {
return;
}
setLeftPaneWidth(leftPaneXPosition- asideWidth);
setRightPaneWidth(screenWidth - e.clientX - DEFAULT_PADDING);
setLeftPaneWidth(e.clientX - asideWidth - 5);
setRightPaneWidth(screenWidth - e.clientX - 5);
}
};
const handleMouseUp = (e) => {
@@ -79,7 +55,7 @@ const RequestTabPanel = () => {
dispatch(
updateRequestPaneTabWidth({
uid: activeTabUid,
requestPaneWidth: e.clientX - asideWidth - DEFAULT_PADDING
requestPaneWidth: e.clientX - asideWidth - 5
})
);
}
@@ -89,6 +65,11 @@ const RequestTabPanel = () => {
setDragging(true);
};
let schema = null;
// let {
// schema
// } = useGraphqlSchema('https://api.spacex.land/graphql');
useEffect(() => {
document.addEventListener('mouseup', handleMouseUp);
document.addEventListener('mousemove', handleMouseMove);
@@ -124,23 +105,24 @@ const RequestTabPanel = () => {
})
);
};
const onGraphqlQueryChange = (value) => {};
const runQuery = async () => {};
return (
<StyledWrapper className={`flex flex-col flex-grow relative ${dragging ? 'dragging' : ''}`}>
<StyledWrapper className={`flex flex-col flex-grow ${dragging ? 'dragging' : ''}`}>
<div className="pt-4 pb-3 px-4">
<QueryUrl item={item} collection={collection} handleRun={handleRun} />
</div>
<section className="main flex flex-grow pb-4 relative">
<section className="main flex flex-grow pb-4">
<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: `${leftPaneWidth}px`, height: 'calc(100% - 5px)' }}>
{item.type === 'graphql-request' ? (
<GraphQLRequestPane
item={item}
collection={collection}
onRunQuery={runQuery}
schema={schema}
leftPaneWidth={leftPaneWidth}
onSchemaLoad={onSchemaLoad}
toggleDocs={toggleDocs}
handleGqlClickReference={handleGqlClickReference}
value={item.request.body.graphql.query}
onQueryChange={onGraphqlQueryChange}
/>
) : null}
@@ -156,22 +138,8 @@ const RequestTabPanel = () => {
<ResponsePane item={item} collection={collection} rightPaneWidth={rightPaneWidth} response={item.response} />
</section>
</section>
{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"
>
{'\u2715'}
</button>
</DocExplorer>
</div>
): null}
</StyledWrapper>
);
};
export default RequestTabPanel;
export default RequestTabPanel;

View File

@@ -19,7 +19,7 @@ const StyledWrapper = styled.div`
.close-icon {
display: none;
color: ${(props) => props.theme.requestTabs.icon.color};
color: #9f9f9f;
width: 8px;
padding-bottom: 6px;
padding-top: 6px;
@@ -27,8 +27,8 @@ const StyledWrapper = styled.div`
&:hover,
&:hover .close-icon {
color: ${(props) => props.theme.requestTabs.icon.hoverColor};
background-color: ${(props) => props.theme.requestTabs.icon.hoverBg};
background-color: #eaeaea;
color: rgb(76 76 76);
}
.has-changes-icon {

View File

@@ -85,7 +85,7 @@ const RequestTab = ({ tab, collection }) => {
<span className="tab-method uppercase" style={{ color: getMethodColor(method), fontSize: 12 }}>
{method}
</span>
<span className="ml-1 tab-name" title={item.name}>
<span className="text-gray-700 ml-1 tab-name" title={item.name}>
{item.name}
</span>
</div>

View File

@@ -1,7 +1,7 @@
import styled from 'styled-components';
const Wrapper = styled.div`
border-bottom: 1px solid ${(props) => props.theme.requestTabs.borromBorder};
border-bottom: 1px solid var(--color-request-dragbar-background);
ul {
padding: 0;
@@ -28,8 +28,7 @@ const Wrapper = styled.div`
height: 38px;
margin-right: 6px;
color: ${(props) => props.theme.requestTabs.color};
background: ${(props) => props.theme.requestTabs.bg};
background: #f7f7f7;
border-radius: 0;
.tab-container {
@@ -37,7 +36,7 @@ const Wrapper = styled.div`
}
&.active {
background: ${(props) => props.theme.requestTabs.active.bg};
background: #e7e7e7;
font-weight: 500;
}
@@ -61,9 +60,9 @@ const Wrapper = styled.div`
padding: 3px 0px;
display: inline-flex;
justify-content: center;
color: ${(props) => props.theme.requestTabs.shortTab.color};
background-color: ${(props) => props.theme.requestTabs.shortTab.bg};
color: rgb(117 117 117);
position: relative;
background-color: white;
top: -1px;
> div {
@@ -86,8 +85,8 @@ const Wrapper = styled.div`
&:hover {
> div {
background-color: ${(props) => props.theme.requestTabs.shortTab.hoverBg};
color: ${(props) => props.theme.requestTabs.shortTab.hoverColor};
background-color: #eaeaea;
color: rgb(76 76 76);
border-radius: 3px;
}
}

View File

@@ -119,7 +119,7 @@ const RequestTabs = () => {
</div>
</li>
) : null}
<li className="select-none short-tab" id="create-new-tab" onClick={createNewTab}>
<li className="select-none short-tab" onClick={createNewTab}>
<div className="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z" />

View File

@@ -23,7 +23,7 @@ const ResponseLoadingOverlay = ({ item, collection }) => {
<IconRefresh size={24} className="animate-spin" />
<button
onClick={handleCancelRequest}
className="mt-4 uppercase btn-md rounded btn-secondary ease-linear transition-all duration-150"
className="mt-4 uppercase bg-gray-200 active:bg-blueGray-600 text-xs px-4 py-2 rounded shadow hover:shadow-md outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150"
type="button"
>
Cancel Request

View File

@@ -3,10 +3,6 @@ import styled from 'styled-components';
const StyledWrapper = styled.div`
padding-top: 20%;
width: 100%;
.send-icon {
color: ${(props) => props.theme.requestTabPanel.responseSendIcon};
}
`;
export default StyledWrapper;

View File

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

View File

@@ -1,17 +1,12 @@
import React from 'react';
import CodeEditor from 'components/CodeEditor';
import { useTheme } from 'providers/Theme';
import StyledWrapper from './StyledWrapper';
const QueryResult = ({ value, width }) => {
const {
storedTheme
} = useTheme();
return (
<StyledWrapper className="px-3 w-full" style={{ maxWidth: width }}>
<div className="h-full">
<CodeEditor theme={storedTheme} value={value || ''} readOnly />
<CodeEditor value={value || ''} readOnly />
</div>
</StyledWrapper>
);

View File

@@ -22,7 +22,7 @@ const Wrapper = styled.div`
tbody {
tr:nth-child(odd) {
background-color: ${(props) => props.theme.table.striped};
background-color: var(--color-table-stripe);
}
}
}

View File

@@ -3,7 +3,7 @@ import styled from 'styled-components';
const Wrapper = styled.div`
font-size: 0.75rem;
font-weight: 600;
color: ${(props) => props.theme.requestTabPanel.responseStatus};
color: rgb(117 117 117);
`;
export default Wrapper;

View File

@@ -3,7 +3,7 @@ import styled from 'styled-components';
const Wrapper = styled.div`
font-size: 0.75rem;
font-weight: 600;
color: ${(props) => props.theme.requestTabPanel.responseStatus};
color: rgb(117 117 117);
`;
export default Wrapper;

View File

@@ -3,14 +3,6 @@ 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};
}
`;
export default Wrapper;

View File

@@ -3,20 +3,19 @@ import classnames from 'classnames';
import statusCodePhraseMap from './get-status-code-phrase';
import StyledWrapper from './StyledWrapper';
// Todo: text-error class is not getting pulled in for 500 errors
const StatusCode = ({ status }) => {
const getTabClassname = (status) => {
const getTabClassname = () => {
return classnames('', {
'text-ok': status >= 100 && status < 200,
'text-ok': status >= 200 && status < 300,
'text-error': status >= 300 && status < 400,
'text-error': status >= 400 && status < 500,
'text-error': status >= 500 && status < 600
'text-blue-700': status >= 100 && status < 200,
'text-green-700': status >= 200 && status < 300,
'text-purple-700': status >= 300 && status < 400,
'text-yellow-700': status >= 400 && status < 500,
'text-red-700': status >= 500 && status < 600
});
};
return (
<StyledWrapper className={getTabClassname(status)}>
<StyledWrapper className={getTabClassname()}>
{status} {statusCodePhraseMap[status]}
</StyledWrapper>
);

View File

@@ -20,8 +20,8 @@ const StyledWrapper = styled.div`
}
&.active {
color: ${(props) => props.theme.tabs.active.color} !important;
border-bottom: solid 2px ${(props) => props.theme.tabs.active.border} !important;
color: #322e2c !important;
border-bottom: solid 2px var(--color-tab-active-border) !important;
}
}
}

View File

@@ -13,25 +13,25 @@ const Wrapper = styled.div`
}
.method-get {
color: ${(props) => props.theme.request.methods.get};
color: var(--color-method-get);
}
.method-post {
color: ${(props) => props.theme.request.methods.post};
color: var(--color-method-post);
}
.method-put {
color: ${(props) => props.theme.request.methods.put};
color: var(--color-method-put);
}
.method-delete {
color: ${(props) => props.theme.request.methods.delete};
color: var(--color-method-delete);
}
.method-patch {
color: ${(props) => props.theme.request.methods.put};
color: var(--color-method-patch);
}
.method-options {
color: ${(props) => props.theme.request.methods.put};
color: var(--color-method-options);
}
.method-head {
color: ${(props) => props.theme.request.methods.put};
color: var(--color-method-head);
}
`;

View File

@@ -2,7 +2,7 @@ import styled from 'styled-components';
const Wrapper = styled.div`
.menu-icon {
color: ${(props) => props.theme.sidebar.dropdownIcon.color};
color: rgb(110 110 110);
.dropdown {
div[aria-expanded='true'] {
@@ -15,7 +15,7 @@ const Wrapper = styled.div`
}
.indent-block {
border-right: ${(props) => props.theme.sidebar.collection.item.indentBorder};
border-right: solid 1px #e1e1e1;
}
.collection-item-name {
@@ -34,7 +34,7 @@ const Wrapper = styled.div`
}
&:hover {
background: ${(props) => props.theme.sidebar.collection.item.hoverBg};
background: #e7e7e7;
.menu-icon {
.dropdown {
div[aria-expanded='false'] {
@@ -45,14 +45,14 @@ const Wrapper = styled.div`
}
&.item-focused-in-tab {
background: ${(props) => props.theme.sidebar.collection.item.bg};
background: var(--color-sidebar-collection-item-active-background);
&:hover {
background: ${(props) => props.theme.sidebar.collection.item.bg} !important;
background: var(--color-sidebar-collection-item-active-background) !important;
}
.indent-block {
border-right: ${(props) => props.theme.sidebar.collection.item.active.indentBorder} !important;
border-right: solid 1px var(--color-sidebar-collection-item-active-indent-border);
}
}
@@ -62,15 +62,15 @@ const Wrapper = styled.div`
}
div.dropdown-item.delete-item {
color: ${(props) => props.theme.colors.danger};
color: var(--color-text-danger);
&:hover {
background-color: ${(props) => props.theme.colors.bg.danger};
background-color: var(--color-background-danger);
color: white;
}
}
}
&.is-sidebar-dragging .collection-item-name {
&.is-dragging .collection-item-name {
cursor: inherit;
}
`;

View File

@@ -2,12 +2,10 @@ import React, { useState, useRef, forwardRef, useEffect } from 'react';
import range from 'lodash/range';
import filter from 'lodash/filter';
import classnames from 'classnames';
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 } from 'providers/ReduxStore/slices/collections';
import { moveItem } from 'providers/ReduxStore/slices/collections/actions';
import Dropdown from 'components/Dropdown';
import NewRequest from 'components/Sidebar/NewRequest';
import NewFolder from 'components/Sidebar/NewFolder';
@@ -17,7 +15,6 @@ import CloneCollectionItem from './CloneCollectionItem';
import DeleteCollectionItem from './DeleteCollectionItem';
import { isItemARequest, isItemAFolder, itemIsOpenedInTabs } from 'utils/tabs';
import { doesRequestMatchSearchText, doesFolderHaveItemsMatchSearchText } from 'utils/collections/search';
import { getDefaultRequestPaneTab } from 'utils/collections';
import { hideHomePage } from 'providers/ReduxStore/slices/app';
import StyledWrapper from './StyledWrapper';
@@ -25,7 +22,7 @@ import StyledWrapper from './StyledWrapper';
const CollectionItem = ({ item, collection, searchText }) => {
const tabs = useSelector((state) => state.tabs.tabs);
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
const isSidebarDragging = useSelector((state) => state.app.isDragging);
const isDragging = useSelector((state) => state.app.isDragging);
const dispatch = useDispatch();
const [renameItemModalOpen, setRenameItemModalOpen] = useState(false);
@@ -35,29 +32,6 @@ const CollectionItem = ({ item, collection, searchText }) => {
const [newFolderModalOpen, setNewFolderModalOpen] = useState(false);
const [itemIsCollapsed, setItemisCollapsed] = useState(item.collapsed);
// const [{ isDragging }, drag] = useDrag({
// type: 'COLLECTION_ITEM',
// item: item,
// collect: (monitor) => ({
// isDragging: monitor.isDragging()
// })
// });
// const [{ isOver }, drop] = useDrop({
// accept: 'COLLECTION_ITEM',
// drop: (draggedItem) => {
// if (draggedItem.uid !== item.uid) {
// dispatch(moveItem(collection.uid, draggedItem.uid, item.uid));
// }
// },
// canDrop: (draggedItem) => {
// return draggedItem.uid !== item.uid;
// },
// collect: (monitor) => ({
// isOver: monitor.isOver()
// })
// });
useEffect(() => {
if (searchText && searchText.length) {
setItemisCollapsed(false);
@@ -95,8 +69,7 @@ const CollectionItem = ({ item, collection, searchText }) => {
dispatch(
addTab({
uid: item.uid,
collectionUid: collection.uid,
requestPaneTab: getDefaultRequestPaneTab(item)
collectionUid: collection.uid
})
);
}
@@ -116,7 +89,7 @@ const CollectionItem = ({ item, collection, searchText }) => {
const isFolder = isItemAFolder(item);
const className = classnames('flex flex-col w-full', {
'is-sidebar-dragging': isSidebarDragging
'is-dragging': isDragging
});
if (searchText && searchText.length) {

View File

@@ -20,6 +20,7 @@ const RemoveCollectionFromWorkspace = ({ onClose, collection }) => {
})
);
})
.then(() => dispatch(removeLocalCollection(collection.uid)))
.then(() => toast.success('Collection removed from workspace'))
.catch((err) => console.log(err) && toast.error('An error occured while removing the collection'));
};

View File

@@ -24,7 +24,7 @@ const Wrapper = styled.div`
svg {
height: 22px;
color: ${(props) => props.theme.sidebar.dropdownIcon.color};
color: rgb(110 110 110);
}
}
@@ -45,9 +45,9 @@ const Wrapper = styled.div`
}
div.dropdown-item.delete-collection {
color: ${(props) => props.theme.colors.text.danger};
color: var(--color-text-danger);
&:hover {
background-color: ${(props) => props.theme.colors.bg.danger};
background-color: var(--color-background-danger);
color: white;
}
}

View File

@@ -2,11 +2,9 @@ import React, { useState, forwardRef, useRef, useEffect } from 'react';
import classnames from 'classnames';
import filter from 'lodash/filter';
import cloneDeep from 'lodash/cloneDeep';
import { useDrop } from 'react-dnd';
import { IconChevronRight, IconDots } from '@tabler/icons';
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 NewRequest from 'components/Sidebar/NewRequest';
import NewFolder from 'components/Sidebar/NewFolder';
@@ -73,21 +71,6 @@ const Collection = ({ collection, searchText }) => {
const isLocal = isLocalCollection(collection);
// const [{ isOver }, drop] = useDrop({
// accept: 'COLLECTION_ITEM',
// drop: (draggedItem) => {
// console.log('drop', draggedItem);
// dispatch(moveItemToRootOfCollection(collection.uid, draggedItem.uid));
// },
// canDrop: (draggedItem) => {
// // todo need to make sure that draggedItem belongs to the collection
// return true;
// },
// collect: (monitor) => ({
// isOver: monitor.isOver()
// })
// });
return (
<StyledWrapper className="flex flex-col">
{showNewRequestModal && <NewRequest collection={collection} onClose={() => setShowNewRequestModal(false)} />}
@@ -99,7 +82,7 @@ const Collection = ({ collection, searchText }) => {
<div className="flex py-1 collection-name items-center">
<div className="flex flex-grow items-center" onClick={handleClick}>
<IconChevronRight size={16} strokeWidth={2} className={iconClassName} style={{ width: 16, color: 'rgb(160 160 160)' }} />
<div className="ml-1" id="sidebar-collection-name">{collection.name}</div>
<div className="ml-1">{collection.name}</div>
</div>
<div className="collection-actions">
<Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement="bottom-start">

View File

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

View File

@@ -1,21 +1,12 @@
import { useState } from 'react';
import { useTheme } from '../../../../providers/Theme';
import React, { useState } from 'react';
import toast from 'react-hot-toast';
import { useSelector, useDispatch } from 'react-redux';
import CreateCollection from 'components/Sidebar/CreateCollection';
import SelectCollection from 'components/Sidebar/Collections/SelectCollection';
import { createCollection } from 'providers/ReduxStore/slices/collections/actions';
import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
import toast from 'react-hot-toast';
import styled from 'styled-components';
import CreateCollection from 'components/Sidebar/CreateCollection';
import SelectCollection from 'components/Sidebar/Collections/SelectCollection';
import StyledWrapper from './StyledWrapper';
const LinkStyle = styled.span`
color: ${(props) => props.theme['text-link']};
`;
const CreateOrAddCollection = () => {
const { theme } = useTheme();
const dispatch = useDispatch();
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
const [addCollectionToWSModalOpen, setAddCollectionToWSModalOpen] = useState(false);
@@ -40,18 +31,18 @@ const CreateOrAddCollection = () => {
};
const CreateLink = () => (
<LinkStyle className="underline text-link cursor-pointer" theme={theme} onClick={() => setCreateCollectionModalOpen(true)}>
<span className="underline text-link cursor-pointer" onClick={() => setCreateCollectionModalOpen(true)}>
Create
</LinkStyle>
</span>
);
const AddLink = () => (
<LinkStyle className="underline text-link cursor-pointer" theme={theme} onClick={() => setAddCollectionToWSModalOpen(true)}>
<span className="underline text-link cursor-pointer" onClick={() => setAddCollectionToWSModalOpen(true)}>
Add
</LinkStyle>
</span>
);
return (
<StyledWrapper className="px-2 mt-4">
<div className="px-2 mt-4 text-gray-600">
{createCollectionModalOpen ? <CreateCollection onClose={() => setCreateCollectionModalOpen(false)} handleConfirm={handleCreateCollection} /> : null}
{addCollectionToWSModalOpen ? (
@@ -64,7 +55,7 @@ const CreateOrAddCollection = () => {
<CreateLink /> or <AddLink /> Collection to Workspace.
</div>
</div>
</StyledWrapper>
</div>
);
};

View File

@@ -10,7 +10,7 @@ const StyledWrapper = styled.div`
cursor: pointer;
&:hover {
background-color: ${(props) => props.theme.plainGrid.hoverBg};;
background-color: #f4f4f4;
}
}
`;

View File

@@ -1,7 +1,5 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import find from 'lodash/find';
import filter from 'lodash/filter';
import Collection from './Collection';
@@ -28,11 +26,7 @@ const Collections = ({ searchText }) => {
<div className="mt-4 flex flex-col">
{collectionToDisplay && collectionToDisplay.length
? collectionToDisplay.map((c) => {
return (
<DndProvider backend={HTML5Backend} key={c.uid}>
<Collection searchText={searchText} collection={c} />
</DndProvider>
);
return <Collection searchText={searchText} collection={c} key={c.uid} />;
})
: null}
</div>

View File

@@ -1,57 +0,0 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { collectionImported } from 'providers/ReduxStore/slices/collections';
import importBrunoCollection from 'utils/importers/bruno-collection';
import importPostmanCollection from 'utils/importers/postman-collection';
import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
import { toastError } from 'utils/common/error';
import toast from 'react-hot-toast';
import Modal from 'components/Modal';
const ImportCollection = ({ onClose }) => {
const dispatch = useDispatch();
const { activeWorkspaceUid } = useSelector((state) => state.workspaces);
const handleImportBrunoCollection = () => {
importBrunoCollection()
.then((collection) => {
dispatch(collectionImported({ collection: collection }));
dispatch(addCollectionToWorkspace(activeWorkspaceUid, collection.uid));
toast.success('Collection imported successfully');
onClose();
})
.catch((err) => toastError(err, 'Import collection failed'));
};
const handleImportPostmanCollection = () => {
importPostmanCollection()
.then((collection) => {
dispatch(collectionImported({ collection: collection }));
dispatch(addCollectionToWorkspace(activeWorkspaceUid, collection.uid));
toast.success('Postman Collection imported successfully');
onClose();
})
.catch((err) => toastError(err, 'Postman Import collection failed'));
};
return (
<Modal size="sm" title="Import Collection" hideFooter={true} handleConfirm={onClose} handleCancel={onClose}>
<div>
<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}
>
Postman Collection
</div>
</div>
</Modal>
);
};
export default ImportCollection;

View File

@@ -3,7 +3,7 @@ import styled from 'styled-components';
const Wrapper = styled.div`
.current-workspace {
margin-inline: 0.5rem;
background-color: ${(props) => props.theme.sidebar.workspace.bg};
background: #e1e1e1;
border-radius: 5px;
.caret {
@@ -13,11 +13,6 @@ const Wrapper = styled.div`
}
}
.muted-message {
color: ${(props) => props.theme.sidebar.muted};
border-top: solid 1px ${(props) => props.theme.dropdown.seperator};
}
div[data-tippy-root] {
width: calc(100% - 1rem);
}

View File

@@ -60,7 +60,7 @@ const LocalCollections = ({ searchText }) => {
<span>Open Collection</span>
</div>
<div className="px-2 pt-2 muted-message" style={{ fontSize: 10 }}>
<div className="px-2 pt-2 text-gray-600" style={{ fontSize: 10, borderTop: 'solid 1px #e7e7e7' }}>
Note: Local collections are not tied to a workspace
</div>
</Dropdown>

View File

@@ -1,8 +1,8 @@
import styled from 'styled-components';
const Wrapper = styled.div`
background-color: ${(props) => props.theme.menubar.bg};
color: rgba(255, 255, 255, 0.4);
background-color: rgb(44, 44, 44);
color: rgba(255, 255, 255, 0.5);
min-height: 100vh;
.menu-item {

View File

@@ -1,19 +1,16 @@
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 React, { useState } from 'react';
import Link from 'next/link';
import StyledWrapper from './StyledWrapper';
import { useRouter } from 'next/router';
import { IconCode, IconFiles, IconUser, IconUsers, IconSettings, IconChevronsLeft, IconLifebuoy } from '@tabler/icons';
import { useDispatch } from 'react-redux';
import { toggleLeftMenuBar } from 'providers/ReduxStore/slices/app';
import BrunoSupport from 'components/BrunoSupport';
import SwitchTheme from 'components/SwitchTheme';
import { isElectron } from 'utils/common/platform';
import StyledWrapper from './StyledWrapper';
const MenuBar = () => {
const router = useRouter();
const dispatch = useDispatch();
const [openTheme, setOpenTheme] = useState(false);
const [openBrunoSupport, setOpenBrunoSupport] = useState(false);
const isPlatformElectron = isElectron();
@@ -23,9 +20,6 @@ const MenuBar = () => {
return (
<StyledWrapper className="h-full flex flex-col">
{openBrunoSupport && <BrunoSupport onClose={() => setOpenBrunoSupport(false)} />}
{openTheme && <SwitchTheme onClose={() => setOpenTheme(false)} />}
<div className="flex flex-col">
<Link href="/">
<div className={getClassName('/')}>
@@ -49,16 +43,14 @@ const MenuBar = () => {
<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 className="menu-item">
<IconLifebuoy size={28} strokeWidth={1.5} onClick={() => setOpenBrunoSupport(true)} />
</div>
<div className="menu-item" onClick={() => dispatch(toggleLeftMenuBar())}>
<IconChevronsLeft size={28} strokeWidth={1.5} />
</div>
</div>
{openBrunoSupport && <BrunoSupport onClose={() => setOpenBrunoSupport(false)} />}
</StyledWrapper>
);
};

View File

@@ -2,9 +2,9 @@ import styled from 'styled-components';
const StyledWrapper = styled.div`
div.method-selector-container {
border: solid 1px ${(props) => props.theme.modal.input.border};
border: solid 1px var(--color-layout-border);
border-right: none;
background-color: ${(props) => props.theme.modal.input.bg};
background-color: var(--color-sidebar-background);
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
@@ -15,17 +15,15 @@ const StyledWrapper = styled.div`
div.method-selector-container,
div.input-container {
background-color: ${(props) => props.theme.modal.input.bg};
height: 2.3rem;
}
div.input-container {
border: solid 1px ${(props) => props.theme.modal.input.border};
border: solid 1px var(--color-layout-border);
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
input {
background-color: ${(props) => props.theme.modal.input.bg};
outline: none;
box-shadow: none;

View File

@@ -9,7 +9,6 @@ import { newEphermalHttpRequest } from 'providers/ReduxStore/slices/collections'
import { newHttpRequest } from 'providers/ReduxStore/slices/collections/actions';
import { addTab } from 'providers/ReduxStore/slices/tabs';
import HttpMethodSelector from 'components/RequestPane/QueryUrl/HttpMethodSelector';
import { getDefaultRequestPaneTab } from 'utils/collections';
import StyledWrapper from './StyledWrapper';
const NewRequest = ({ collection, item, isEphermal, onClose }) => {
@@ -43,8 +42,7 @@ const NewRequest = ({ collection, item, isEphermal, onClose }) => {
dispatch(
addTab({
uid: uid,
collectionUid: collection.uid,
requestPaneTab: getDefaultRequestPaneTab({type: values.requestType})
collectionUid: collection.uid
})
);
onClose();
@@ -79,27 +77,27 @@ const NewRequest = ({ collection, item, isEphermal, onClose }) => {
<StyledWrapper>
<Modal size="md" title="New Request" confirmText="Create" handleConfirm={onSubmit} handleCancel={onClose}>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
<div>
<div className="hidden">
<label htmlFor="requestName" className="block font-semibold">
Type
</label>
<div className="flex items-center mt-2">
<input
id="http-request"
id="http"
className="cursor-pointer"
type="radio"
name="requestType"
onChange={formik.handleChange}
value="http-request"
value="http"
checked={formik.values.requestType === 'http-request'}
/>
<label htmlFor="http-request" className="ml-1 cursor-pointer select-none">
<label htmlFor="http" className="ml-1 cursor-pointer select-none">
Http
</label>
<input
id="graphql-request"
id="graphql"
className="ml-4 cursor-pointer"
type="radio"
name="requestType"
@@ -107,16 +105,16 @@ const NewRequest = ({ collection, item, isEphermal, onClose }) => {
formik.setFieldValue('requestMethod', 'POST');
formik.handleChange(event);
}}
value="graphql-request"
value="graphql"
checked={formik.values.requestType === 'graphql-request'}
/>
<label htmlFor="graphql-request" className="ml-1 cursor-pointer select-none">
<label htmlFor="graphql" className="ml-1 cursor-pointer select-none">
Graphql
</label>
</div>
</div>
<div className="mt-4">
<div>
<label htmlFor="requestName" className="block font-semibold">
Name
</label>

View File

@@ -1,10 +1,8 @@
import styled from 'styled-components';
const Wrapper = styled.div`
color: ${(props) => props.theme.sidebar.color};
aside {
background-color: ${(props) => props.theme.sidebar.bg};
background-color: var(--color-sidebar-background);
.collection-title {
line-height: 1.5;
@@ -27,18 +25,6 @@ const Wrapper = styled.div`
top: -0.625rem;
}
}
.collection-filter {
input {
border: ${(props) => props.theme.sidebar.search.border};
border-radius: 2px;
background-color: ${(props) => props.theme.sidebar.search.bg};
&:focus {
outline: none;
}
}
}
}
div.drag-sidebar {
@@ -54,7 +40,7 @@ const Wrapper = styled.div`
&:hover div.drag-request-border {
width: 2px;
height: 100%;
border-left: solid 1px ${(props) => props.theme.sidebar.dragbar};
border-left: solid 1px var(--color-request-dragbar-background-active);
}
}
`;

View File

@@ -1,14 +1,17 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
.local-collection-label {
background-color: var(--color-sidebar-background);
}
.local-collections-unavailable {
padding: 0.35rem 0.6rem;
color: ${(props) => props.theme.sidebar.muted};
border-top: solid 1px ${(props) => props.theme.dropdown.seperator};
border-top: solid 1px #ddd;
font-size: 11px;
}
.collection-dropdown {
color: ${(props) => props.theme.sidebar.dropdownIcon.color};
color: rgb(110 110 110);
&:hover {
color: inherit;

View File

@@ -1,23 +1,22 @@
import React, { useState, forwardRef, useRef } from 'react';
import toast from 'react-hot-toast';
import Bruno from 'components/Bruno';
import Dropdown from 'components/Dropdown';
import CreateCollection from '../CreateCollection';
import SelectCollection from 'components/Sidebar/Collections/SelectCollection';
import ImportCollection from 'components/Sidebar/ImportCollection';
import { IconDots } from '@tabler/icons';
import Bruno from 'components/Bruno';
import { IconFolders } from '@tabler/icons';
import { isElectron } from 'utils/common/platform';
import { useState, forwardRef, useRef } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { showHomePage } from 'providers/ReduxStore/slices/app';
import { collectionImported } from 'providers/ReduxStore/slices/collections';
import { openLocalCollection } from 'providers/ReduxStore/slices/collections/actions';
import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
import { showHomePage } from 'providers/ReduxStore/slices/app';
import { IconDots } from '@tabler/icons';
import CreateCollection from '../CreateCollection';
import SelectCollection from 'components/Sidebar/Collections/SelectCollection';
import importCollection from 'utils/collections/import';
import { isElectron } from 'utils/common/platform';
import StyledWrapper from './StyledWrapper';
const TitleBar = () => {
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
const [importCollectionModalOpen, setImportCollectionModalOpen] = useState(false);
const [addCollectionToWSModalOpen, setAddCollectionToWSModalOpen] = useState(false);
const { activeWorkspaceUid } = useSelector((state) => state.workspaces);
const isPlatformElectron = isElectron();
@@ -48,10 +47,18 @@ const TitleBar = () => {
.catch(() => toast.error('An error occured while adding collection to workspace'));
};
const handleImportCollection = () => {
importCollection()
.then((collection) => {
dispatch(collectionImported({ collection: collection }));
dispatch(addCollectionToWorkspace(activeWorkspaceUid, collection.uid));
})
.catch((err) => console.log(err));
};
return (
<StyledWrapper className="px-2 py-2">
{createCollectionModalOpen ? <CreateCollection isLocal={createCollectionModalOpen === 'local' ? true : false} onClose={() => setCreateCollectionModalOpen(false)} /> : null}
{importCollectionModalOpen ? <ImportCollection onClose={() => setImportCollectionModalOpen(false)} /> : null}
{addCollectionToWSModalOpen ? (
<SelectCollection title="Add Collection to Workspace" onClose={() => setAddCollectionToWSModalOpen(false)} onSelect={handleAddCollectionToWorkspace} />
@@ -63,7 +70,7 @@ const TitleBar = () => {
</div>
<div
onClick={handleTitleClick}
className="flex items-center font-medium select-none cursor-pointer"
className=" flex items-center font-medium select-none cursor-pointer"
style={{ fontSize: 14, paddingLeft: 6, position: 'relative', top: -1 }}
>
bruno
@@ -83,7 +90,7 @@ const TitleBar = () => {
className="dropdown-item"
onClick={(e) => {
menuDropdownTippyRef.current.hide();
setImportCollectionModalOpen(true);
handleImportCollection();
}}
>
Import Collection
@@ -127,7 +134,7 @@ const TitleBar = () => {
</div>
</>
) : (
<div className="flex items-center select-none text-xs local-collections-unavailable">
<div className="flex items-center select-none text-gray-400 text-xs local-collections-unavailable">
Note: Local collections are only available on the desktop app.
</div>
)}

View File

@@ -1,14 +1,13 @@
import MenuBar from './MenuBar';
import TitleBar from './TitleBar';
import React, { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import Collections from './Collections';
import LocalCollections from './LocalCollections';
import StyledWrapper, { BottomWrapper, VersionNumber } from './StyledWrapper';
import WorkspaceSelector from 'components/Workspaces/WorkspaceSelector';
import { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import TitleBar from './TitleBar';
import MenuBar from './MenuBar';
import { IconSearch, IconChevronsRight } from '@tabler/icons';
import { updateLeftSidebarWidth, updateIsDragging, toggleLeftMenuBar } from 'providers/ReduxStore/slices/app';
import StyledWrapper from './StyledWrapper';
import WorkspaceSelector from 'components/Workspaces/WorkspaceSelector';
const MIN_LEFT_SIDEBAR_WIDTH = 222;
const MAX_LEFT_SIDEBAR_WIDTH = 600;
@@ -107,8 +106,7 @@ const Sidebar = () => {
<Collections searchText={searchText} />
<LocalCollections searchText={searchText} />
</div>
<div className="footer flex px-1 py-2 items-center cursor-pointer select-none">
<div className="flex px-1 py-2 items-center cursor-pointer text-gray-500 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"/> */}
@@ -124,7 +122,7 @@ const Sidebar = () => {
title="GitHub"
></iframe>
</div>
<div className="flex flex-grow items-center justify-end text-xs mr-2">v0.3.0</div>
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.25.2</div>
</div>
</div>
</div>

View File

@@ -1,20 +0,0 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
color: var(--color-text);
.collection-options {
svg {
position: relative;
top: -1px;
}
.label {
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
}
`;
export default StyledWrapper;

View File

@@ -1,67 +0,0 @@
import React from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import Modal from 'components/Modal/index';
import StyledWrapper from './StyledWrapper';
import { useTheme } from 'providers/Theme';
const SwitchTheme = ({ onClose }) => {
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>
<Modal size="sm" title={'Switch Theme'} handleCancel={onClose} hideFooter={true}>
<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>
</Modal>
</StyledWrapper>
);
};
export default SwitchTheme;

View File

@@ -1,24 +1,19 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
.heading {
color: ${(props) => props.theme.welcome.heading};
.create-request {
color: #737373;
font-size: 0.75rem;
}
.muted {
color: ${(props) => props.theme.welcome.muted};
}
.collection-options {
cursor: pointer;
svg {
position: relative;
top: -1px;
}
.label {
cursor: pointer;
&:hover {
text-decoration: underline;
}

View File

@@ -1,24 +1,22 @@
import { useState } from 'react';
import React, { useState } from 'react';
import toast from 'react-hot-toast';
import { isElectron } from 'utils/common/platform';
import { IconPlus, IconUpload, IconFiles, IconFolders, IconPlayerPlay, IconBrandChrome, IconSpeakerphone, IconDeviceDesktop } from '@tabler/icons';
import { useSelector, useDispatch } from 'react-redux';
import { collectionImported } from 'providers/ReduxStore/slices/collections';
import { openLocalCollection } from 'providers/ReduxStore/slices/collections/actions';
import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
import { IconBrandGithub, IconPlus, IconUpload, IconFiles, IconFolders, IconPlayerPlay, IconBrandChrome, IconSpeakerphone, IconDeviceDesktop } from '@tabler/icons';
import Bruno from 'components/Bruno';
import CreateCollection from 'components/Sidebar/CreateCollection';
import SelectCollection from 'components/Sidebar/Collections/SelectCollection';
import { importSampleCollection } from 'utils/importers/bruno-collection';
import ImportCollection from 'components/Sidebar/ImportCollection';
import importCollection, { importSampleCollection } from 'utils/collections/import';
import { isElectron } from 'utils/common/platform';
import GithubSvg from 'assets/github.svg';
import StyledWrapper from './StyledWrapper';
const Welcome = () => {
const dispatch = useDispatch();
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
const [addCollectionToWSModalOpen, setAddCollectionToWSModalOpen] = useState(false);
const [importCollectionModalOpen, setImportCollectionModalOpen] = useState(false);
const { activeWorkspaceUid } = useSelector((state) => state.workspaces);
const isPlatformElectron = isElectron();
@@ -31,6 +29,15 @@ const Welcome = () => {
.catch(() => toast.error('An error occured while adding collection to workspace'));
};
const handleImportCollection = () => {
importCollection()
.then((collection) => {
dispatch(collectionImported({ collection: collection }));
dispatch(addCollectionToWorkspace(activeWorkspaceUid, collection.uid));
})
.catch((err) => console.log(err));
};
const handleImportSampleCollection = () => {
importSampleCollection()
.then((collection) => {
@@ -51,7 +58,6 @@ const Welcome = () => {
return (
<StyledWrapper className="pb-4 px-6 mt-6">
{createCollectionModalOpen ? <CreateCollection isLocal={createCollectionModalOpen === 'local' ? true : false} onClose={() => setCreateCollectionModalOpen(false)} /> : null}
{importCollectionModalOpen ? <ImportCollection onClose={() => setImportCollectionModalOpen(false)} /> : null}
{addCollectionToWSModalOpen ? (
<SelectCollection title="Add Collection to Workspace" onClose={() => setAddCollectionToWSModalOpen(false)} onSelect={handleAddCollectionToWorkspace} />
@@ -61,33 +67,33 @@ const Welcome = () => {
<Bruno width={50} />
</div>
<div className="text-xl font-semibold select-none">bruno</div>
<div className="mt-4">Opensource IDE for exploring and testing api's</div>
<div className="mt-4">Opensource API Client.</div>
<div className="uppercase font-semibold heading mt-10">Collections</div>
<div className="uppercase font-semibold create-request mt-10">Collections</div>
<div className="mt-4 flex items-center collection-options select-none">
<div className="flex items-center" onClick={() => setCreateCollectionModalOpen(true)}>
<div className="flex items-center">
<IconPlus size={18} strokeWidth={2} />
<span className="label ml-2" id="create-collection">
<span className="label ml-2" onClick={() => setCreateCollectionModalOpen(true)}>
Create Collection
</span>
</div>
<div className="flex items-center ml-6" onClick={() => setAddCollectionToWSModalOpen(true)}>
<div className="flex items-center ml-6">
<IconFiles size={18} strokeWidth={2} />
<span className="label ml-2" id="add-collection">
<span className="label ml-2" onClick={() => setAddCollectionToWSModalOpen(true)}>
Add Collection to Workspace
</span>
</div>
<div className="flex items-center ml-6" onClick={() => setImportCollectionModalOpen(true)}>
<div className="flex items-center ml-6" onClick={handleImportCollection}>
<IconUpload size={18} strokeWidth={2} />
<span className="label ml-2" id="import-collection">Import Collection</span>
<span className="label ml-2">Import Collection</span>
</div>
<div className="flex items-center ml-6" onClick={handleImportSampleCollection}>
<IconPlayerPlay size={18} strokeWidth={2} />
<span className="label ml-2" id="load-sample-collection">Load Sample Collection</span>
<span className="label ml-2">Load Sample Collection</span>
</div>
</div>
<div className="uppercase font-semibold heading mt-10 pt-6">Local Collections</div>
<div className="uppercase font-semibold create-request mt-10 pt-6">Local Collections</div>
{isPlatformElectron ? (
<div className="mt-4 flex items-center collection-options select-none">
<div className="flex items-center">
@@ -104,10 +110,10 @@ const Welcome = () => {
</div>
</div>
) : (
<div className="muted mt-4 flex items-center collection-options select-none text-gray-600 text-xs">Local collections are only available on the desktop app.</div>
<div className="mt-4 flex items-center collection-options select-none text-gray-600 text-xs">Local collections are only available on the desktop app.</div>
)}
<div className="uppercase font-semibold heading mt-10 pt-6">Links</div>
<div className="uppercase font-semibold create-request mt-10 pt-6">Links</div>
<div className="mt-4 flex flex-col collection-options select-none">
<div>
<a href="https://www.usebruno.com/downloads" target="_blank" className="flex items-center">
@@ -132,7 +138,7 @@ const Welcome = () => {
</div> */}
<div className="mt-2">
<a href="https://github.com/usebruno/bruno" target="_blank" className="flex items-center">
<IconBrandGithub size={18} strokeWidth={2} />
<img src={GithubSvg.src} style={{ width: '18px' }} />
<span className="label ml-2">Github</span>
</a>
</div>

View File

@@ -10,7 +10,7 @@ const Wrapper = styled.div`
}
div:hover {
background-color: ${(props) => props.theme.plainGrid.hoverBg};
background-color: #f4f4f4;
}
`;

View File

@@ -3,7 +3,7 @@ import styled from 'styled-components';
const Wrapper = styled.div`
.current-workspace {
margin-inline: 0.5rem;
background-color: ${(props) => props.theme.sidebar.workspace.bg};
background: #e1e1e1;
border-radius: 5px;
.caret {

View File

@@ -49,14 +49,14 @@ const WorkspaceSelector = () => {
<div className="items-center cursor-pointer relative">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
<div className="dropdown-item" onClick={() => setOpenSwitchWorkspaceModal(true)}>
<div className="pr-2 icon">
<div className="pr-2 text-gray-600">
<IconSwitch3 size={18} strokeWidth={1.5} />
</div>
<span>Switch Workspace</span>
</div>
<div className="dropdown-item" onClick={() => setOpenWorkspacesModal(true)}>
<div className="pr-2 icon">
<div className="pr-2 text-gray-600">
<IconSettings size={18} strokeWidth={1.5} />
</div>
<span>Configure Workspaces</span>

View File

@@ -2,82 +2,28 @@ import { createGlobalStyle } from 'styled-components';
const GlobalStyle = createGlobalStyle`
.CodeMirror-gutters {
background-color: ${(props) => props.theme.codemirror.gutter.bg} !important;
border-right: solid 1px ${(props) => props.theme.codemirror.border};
background-color: var(--color-codemirror-background);
border-right: solid 1px var(--color-codemirror-border);
}
.text-link {
color: ${(props) => props.theme.textLink};
}
.btn {
text-align: center;
white-space: nowrap;
outline: none;
box-shadow: none;
border-radius: 3px;
}
.btn-sm {
padding: .215rem .6rem .215rem .6rem;
}
.btn-xs {
padding: .2rem .4rem .2rem .4rem;
}
.btn-md {
padding: .4rem 1.1rem;
line-height: 1.47;
}
.btn-default {
&:active,
&:hover,
&:focus {
.bruno-form {
.textbox {
line-height: 1.42857143;
background-color: #fff;
background-image: none;
border: 1px solid #ccc;
padding: 0.45rem;
box-shadow: none;
border-radius: 0px;
outline: none;
box-shadow: none;
}
}
transition: border-color ease-in-out .1s;
border-radius: 3px;
.btn-close {
color: ${(props) => props.theme.button.close.color};
background: ${(props) => props.theme.button.close.bg};
border: solid 1px ${(props) => props.theme.button.close.border};;
&:hover,
&:focus {
outline: none;
box-shadow: none;
border: solid 1px #696969;
}
}
.btn-secondary {
color: ${(props) => props.theme.button.secondary.color};
background: ${(props) => props.theme.button.secondary.bg};
border: solid 1px ${(props) => props.theme.button.secondary.border};
.btn-icon {
color: #3f3f3f;
}
&:hover,
&:focus {
border-color: ${(props) => props.theme.button.secondary.hoverBorder};
outline: none;
box-shadow: none;
}
&:disabled {
color: ${(props) => props.theme.button.disabled.color};
background: ${(props) => props.theme.button.disabled.bg};
border: solid 1px ${(props) => props.theme.button.disabled.border};
cursor: not-allowed;
}
&:disabled.btn-icon {
color: #545454;
&:focus {
border: solid 1px #8b8b8b !important;
outline: none !important;
}
}
}

View File

@@ -0,0 +1,44 @@
import { useState, useEffect } from 'react';
import { getIntrospectionQuery, buildClientSchema } from 'graphql';
const useGraphqlSchema = (endpoint) => {
const [isLoaded, setIsLoaded] = useState(false);
const [schema, setSchema] = useState(null);
const [error, setError] = useState(null);
const introspectionQuery = getIntrospectionQuery();
const queryParams = {
query: introspectionQuery
};
useEffect(() => {
fetch(endpoint, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(queryParams)
})
.then((res) => res.json())
.then((s) => {
if (s && s.data) {
setSchema(buildClientSchema(s.data));
setIsLoaded(true);
} else {
return Promise.reject(new Error('An error occurred while introspecting schema'));
}
})
.catch((err) => {
setError(err);
});
}, []);
return {
isLoaded,
schema,
error
};
};
export default useGraphqlSchema;

View File

@@ -17,7 +17,7 @@ const Wrapper = styled.div`
border-radius: 3px;
&:hover {
background-color: ${(props) => props.theme.plainGrid.hoverBg};
background-color: #f4f4f4;
margin-left: -8px;
margin-right: -8px;
padding-left: 8px;

View File

@@ -21,6 +21,28 @@ const Wrapper = styled.div`
.fw-600 {
font-weight: 600;
}
.react-tabs {
.react-tabs__tab-list {
padding-left: 1rem;
border-bottom: 1px solid #cfcfcf;
.react-tabs__tab--selected {
border-color: #cfcfcf;
}
}
}
.collection-filter {
input {
border: 1px solid rgb(211 211 211);
border-radius: 2px;
&:focus {
outline: none;
}
}
}
`;
export default Wrapper;

View File

@@ -6,8 +6,6 @@ import RequestTabPanel from 'components/RequestTabPanel';
import Sidebar from 'components/Sidebar';
import { useSelector } from 'react-redux';
import StyledWrapper from './StyledWrapper';
import 'codemirror/theme/material.css';
import 'codemirror/theme/monokai.css';
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
if (!SERVER_RENDERED) {

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