Compare commits

..

7 Commits

Author SHA1 Message Date
Anoop M D
00e98451d4 Merge branch 'main' into fix/json-serialization-issues 2024-08-27 14:09:10 +05:30
lohit
df2e18bedd bru disableParsingResponseJson function rename (#2785)
* disable response json parse flag

* fix: pr review comments

* update bru req function name
2024-08-08 19:07:12 +05:30
Anoop M D
9a88db7e56 fix(#2767): fix failing test 2024-08-08 18:46:35 +05:30
lohit
4a4439f48e disable response json parse flag (#2782)
* disable response json parse flag

* fix: pr review comments
2024-08-08 18:36:00 +05:30
Anoop M D
4710928407 fix(#2767): addressing review comments 2024-08-08 12:21:04 +05:30
Anoop M D
33804f4c7b fix(#2767): addressing review comments 2024-08-08 12:02:00 +05:30
Anoop M D
e0858d1c99 fix(#2767): Fix serilization issues of bigint in json body 2024-08-07 20:00:41 +05:30
146 changed files with 1306 additions and 2682 deletions

View File

@@ -15,7 +15,6 @@
| [正體中文](docs/contributing/contributing_zhtw.md)
| [日本語](docs/contributing/contributing_ja.md)
| [हिंदी](docs/contributing/contributing_hi.md)
| [Nederlands](docs/contributing/contributing_nl.md)
## Let's make Bruno better, together!!

View File

@@ -1,4 +1,20 @@
[English](../../contributing.md)
| [Українська](./contributing_ua.md)
| [Русский](./contributing_ru.md)
| [Türkçe](./contributing_tr.md)
| [Deutsch](./contributing_de.md)
| [Français](./contributing_fr.md)
| [Português (BR)](./contributing_pt_br.md)
| [한국어](./contributing_kr.md)
| **বাংলা**
| [Español](./contributing_es.md)
| [Italiano](./contributing_it.md)
| [Română](./contributing_ro.md)
| [Polski](./contributing_pl.md)
| [简体中文](./contributing_cn.md)
| [正體中文](./contributing_zhtw.md)
| [日本語](./contributing_ja.md)
| [हिंदी](./contributing_hi.md)
## আসুন ব্রুনোকে আরও ভালো করি, একসাথে!!

View File

@@ -1,4 +1,20 @@
[English](../../contributing.md)
| [Українська](./contributing_ua.md)
| [Русский](./contributing_ru.md)
| [Türkçe](./contributing_tr.md)
| [Deutsch](./contributing_de.md)
| [Français](./contributing_fr.md)
| [Português (BR)](./contributing_pt_br.md)
| [한국어](./contributing_kr.md)
| [বাংলা](./contributing_bn.md)
| [Español](./contributing_es.md)
| [Italiano](./contributing_it.md)
| [Română](./contributing_ro.md)
| [Polski](./contributing_pl.md)
| **简体中文**
| [正體中文](./contributing_zhtw.md)
| [日本語](./contributing_ja.md)
| [हिंदी](./contributing_hi.md)
## 让我们一起改进 Bruno

View File

@@ -1,4 +1,20 @@
[English](../../contributing.md)
| [Українська](./contributing_ua.md)
| [Русский](./contributing_ru.md)
| [Türkçe](./contributing_tr.md)
| **Deutsch**
| [Français](./contributing_fr.md)
| [Português (BR)](./contributing_pt_br.md)
| [한국어](./contributing_kr.md)
| [বাংলা](./contributing_bn.md)
| [Español](./contributing_es.md)
| [Italiano](./contributing_it.md)
| [Română](./contributing_ro.md)
| [Polski](./contributing_pl.md)
| [简体中文](./contributing_cn.md)
| [正體中文](./contributing_zhtw.md)
| [日本語](./contributing_ja.md)
| [हिंदी](./contributing_hi.md)
## Lass uns Bruno noch besser machen, gemeinsam!!

View File

@@ -1,4 +1,20 @@
[English](../../contributing.md)
| [Українська](./contributing_ua.md)
| [Русский](./contributing_ru.md)
| [Türkçe](./contributing_tr.md)
| [Deutsch](./contributing_de.md)
| [Français](./contributing_fr.md)
| [Português (BR)](./contributing_pt_br.md)
| [한국어](./contributing_kr.md)
| [বাংলা](./contributing_bn.md)
| **Español**
| [Italiano](./contributing_it.md)
| [Română](./contributing_ro.md)
| [Polski](./contributing_pl.md)
| [简体中文](./contributing_cn.md)
| [正體中文](./contributing_zhtw.md)
| [日本語](./contributing_ja.md)
| [हिंदी](./contributing_hi.md)
## ¡Juntos, hagamos a Bruno mejor!

View File

@@ -1,4 +1,20 @@
[English](../../contributing.md)
| [Українська](./contributing_ua.md)
| [Русский](./contributing_ru.md)
| [Türkçe](./contributing_tr.md)
| [Deutsch](./contributing_de.md)
| **Français**
| [Português (BR)](./contributing_pt_br.md)
| [한국어](./contributing_kr.md)
| [বাংলা](./contributing_bn.md)
| [Español](./contributing_es.md)
| [Italiano](./contributing_it.md)
| [Română](./contributing_ro.md)
| [Polski](./contributing_pl.md)
| [简体中文](./contributing_cn.md)
| [正體中文](./contributing_zhtw.md)
| [日本語](./contributing_ja.md)
| [हिंदी](./contributing_hi.md)
## Ensemble, améliorons Bruno !

View File

@@ -1,4 +1,20 @@
[English](../../contributing.md)
| [Українська](./contributing_ua.md)
| [Русский](./contributing_ru.md)
| [Türkçe](./contributing_tr.md)
| [Deutsch](./contributing_de.md)
| [Français](./contributing_fr.md)
| [Português (BR)](./contributing_pt_br.md)
| [한국어](./contributing_kr.md)
| [বাংলা](./contributing_bn.md)
| [Español](./contributing_es.md)
| [Italiano](./contributing_it.md)
| [Română](./contributing_ro.md)
| [Polski](./contributing_pl.md)
| [简体中文](./contributing_cn.md)
| [正體中文](./contributing_zhtw.md)
| [日本語](./contributing_ja.md)
| **हिंदी**
## आइए मिलकर Bruno को बेहतर बनाएं !!

View File

@@ -1,4 +1,20 @@
[English](../../contributing.md)
| [Українська](./contributing_ua.md)
| [Русский](./contributing_ru.md)
| [Türkçe](./contributing_tr.md)
| [Deutsch](./contributing_de.md)
| [Français](./contributing_fr.md)
| [Português (BR)](./contributing_pt_br.md)
| [한국어](./contributing_kr.md)
| [বাংলা](./contributing_bn.md)
| [Español](./contributing_es.md)
| **Italiano**
| [Română](./contributing_ro.md)
| [Polski](./contributing_pl.md)
| [简体中文](./contributing_cn.md)
| [正體中文](./contributing_zhtw.md)
| [日本語](./contributing_ja.md)
| [हिंदी](./contributing_hi.md)
## Insieme, miglioriamo Bruno!

View File

@@ -1,4 +1,20 @@
[English](../../contributing.md)
| [Українська](./contributing_ua.md)
| [Русский](./contributing_ru.md)
| [Türkçe](./contributing_tr.md)
| [Deutsch](./contributing_de.md)
| [Français](./contributing_fr.md)
| [Português (BR)](./contributing_pt_br.md)
| [한국어](./contributing_kr.md)
| [বাংলা](./contributing_bn.md)
| [Español](./contributing_es.md)
| [Italiano](./contributing_it.md)
| [Română](./contributing_ro.md)
| [Polski](./contributing_pl.md)
| [简体中文](./contributing_cn.md)
| [正體中文](./contributing_zhtw.md)
| **日本語**
| [हिंदी](./contributing_hi.md)
## 一緒に Bruno をよりよいものにしていきましょう!!

View File

@@ -1,4 +1,20 @@
[English](../../contributing.md)
| [Українська](./contributing_ua.md)
| [Русский](./contributing_ru.md)
| [Türkçe](./contributing_tr.md)
| [Deutsch](./contributing_de.md)
| [Français](./contributing_fr.md)
| [Português (BR)](./contributing_pt_br.md)
| **한국어**
| [বাংলা](./contributing_bn.md)
| [Español](./contributing_es.md)
| [Italiano](./contributing_it.md)
| [Română](./contributing_ro.md)
| [Polski](./contributing_pl.md)
| [简体中文](./contributing_cn.md)
| [正體中文](./contributing_zhtw.md)
| [日本語](./contributing_ja.md)
| [हिंदी](./contributing_hi.md)
## 함께 Bruno를 더 좋게 만들어요!!

View File

@@ -1,82 +0,0 @@
[English](../../contributing.md)
## Laten we Bruno samen beter maken !!
We zijn blij dat je Bruno wilt verbeteren. Hieronder staan de richtlijnen om Bruno op je computer op te zetten.
### Technologiestack
Bruno is gebouwd met Next.js en React. We gebruiken ook Electron om een desktopversie te leveren (die lokale collecties ondersteunt).
Bibliotheken die we gebruiken:
- CSS - Tailwind
- Code Editors - Codemirror
- State Management - Redux
- Iconen - Tabler Icons
- Formulieren - formik
- Schema Validatie - Yup
- Request Client - axios
- Bestandsysteem Watcher - chokidar
### Afhankelijkheden
Je hebt [Node v18.x of de nieuwste LTS-versie](https://nodejs.org/en/) en npm 8.x nodig. We gebruiken npm workspaces in het project.
## Ontwikkeling
Bruno wordt ontwikkeld als een desktop-app. Je moet de app laden door de Next.js app in één terminal te draaien en daarna de Electron app in een andere terminal te draaien.
### Lokale Ontwikkeling
```bash
# gebruik voorgeschreven node versie
nvm use
# installeer afhankelijkheden
npm i --legacy-peer-deps
# build pakketten
npm run build:graphql-docs
npm run build:bruno-query
npm run build:bruno-common
# draai next app (terminal 1)
npm run dev:web
# draai electron app (terminal 2)
npm run dev:electron
```
### Problemen oplossen
Je kunt een `Unsupported platform`-fout tegenkomen wanneer je `npm install` uitvoert. Om dit te verhelpen, moet je `node_modules` en `package-lock.json` verwijderen en `npm install` uitvoeren. Dit zou alle benodigde afhankelijkheden moeten installeren om de app te draaien.
```shell
# Verwijder node_modules in subdirectories
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
rm -rf "$dir"
done
# Verwijder package-lock in subdirectories
find . -type f -name "package-lock.json" -delete
```
### Testen
```bash
# bruno-schema
npm test --workspace=packages/bruno-schema
# bruno-lang
npm test --workspace=packages/bruno-lang
```
### Pull Requests indienen
- Houd de PR's klein en gefocust op één ding
- Volg het formaat voor het aanmaken van branches
- feature/[feature naam]: Deze branch moet wijzigingen voor een specifieke functie bevatten
- Voorbeeld: feature/dark-mode
- bugfix/[bug naam]: Deze branch moet alleen bugfixes voor een specifieke bug bevatten
- Voorbeeld: bugfix/bug-1

View File

@@ -1,4 +1,20 @@
[English](../../contributing.md)
| [Українська](./contributing_ua.md)
| [Русский](./contributing_ru.md)
| [Türkçe](./contributing_tr.md)
| [Deutsch](./contributing_de.md)
| [Français](./contributing_fr.md)
| [Português (BR)](./contributing_pt_br.md)
| [한국어](./contributing_kr.md)
| [বাংলা](./contributing_bn.md)
| [Español](./contributing_es.md)
| [Italiano](./contributing_it.md)
| [Română](./contributing_ro.md)
| **Polski**
| [简体中文](./contributing_cn.md)
| [正體中文](./contributing_zhtw.md)
| [日本語](./contributing_ja.md)
| [हिंदी](./contributing_hi.md)
## Wspólnie uczynijmy Bruno lepszym !!

View File

@@ -1,4 +1,20 @@
[English](../../contributing.md)
| [Українська](./contributing_ua.md)
| [Русский](./contributing_ru.md)
| [Türkçe](./contributing_tr.md)
| [Deutsch](./contributing_de.md)
| [Français](./contributing_fr.md)
| **Português (BR)**
| [한국어](./contributing_kr.md)
| [বাংলা](./contributing_bn.md)
| [Español](./contributing_es.md)
| [Italiano](./contributing_it.md)
| [Română](./contributing_ro.md)
| [Polski](./contributing_pl.md)
| [简体中文](./contributing_cn.md)
| [正體中文](./contributing_zhtw.md)
| [日本語](./contributing_ja.md)
| [हिंदी](./contributing_hi.md)
## Vamos tornar o Bruno melhor, juntos!!

View File

@@ -1,4 +1,20 @@
[English](../../contributing.md)
| [Українська](./contributing_ua.md)
| [Русский](./contributing_ru.md)
| [Türkçe](./contributing_tr.md)
| [Deutsch](./contributing_de.md)
| [Français](./contributing_fr.md)
| [Português (BR)](./contributing_pt_br.md)
| [한국어](./contributing_kr.md)
| [বাংলা](./contributing_bn.md)
| [Español](./contributing_es.md)
| [Italiano](./contributing_it.md)
| **Română**
| [Polski](./contributing_pl.md)
| [简体中文](./contributing_cn.md)
| [正體中文](./contributing_zhtw.md)
| [日本語](./contributing_ja.md)
| [हिंदी](./contributing_hi.md)
## Haideţi să îmbunătățim Bruno, împreună!!

View File

@@ -1,4 +1,20 @@
[English](../../contributing.md)
| [Українська](./contributing_ua.md)
| **Русский**
| [Türkçe](./contributing_tr.md)
| [Deutsch](./contributing_de.md)
| [Français](./contributing_fr.md)
| [Português (BR)](./contributing_pt_br.md)
| [한국어](./contributing_kr.md)
| [বাংলা](./contributing_bn.md)
| [Español](./contributing_es.md)
| [Italiano](./contributing_it.md)
| [Română](./contributing_ro.md)
| [Polski](./contributing_pl.md)
| [简体中文](./contributing_cn.md)
| [正體中文](./contributing_zhtw.md)
| [日本語](./contributing_ja.md)
| [हिंदी](./contributing_hi.md)
## Давайте вместе сделаем Бруно лучше!!!

View File

@@ -1,4 +1,20 @@
[English](../../contributing.md)
| [Українська](./contributing_ua.md)
| [Русский](./contributing_ru.md)
| **Türkçe**
| [Deutsch](./contributing_de.md)
| [Français](./contributing_fr.md)
| [Português (BR)](./contributing_pt_br.md)
| [한국어](./contributing_kr.md)
| [বাংলা](./contributing_bn.md)
| [Español](./contributing_es.md)
| [Italiano](./contributing_it.md)
| [Română](./contributing_ro.md)
| [Polski](./contributing_pl.md)
| [简体中文](./contributing_cn.md)
| [正體中文](./contributing_zhtw.md)
| [日本語](./contributing_ja.md)
| [हिंदी](./contributing_hi.md)
## Bruno'yu birlikte daha iyi hale getirelim!!!

View File

@@ -1,4 +1,20 @@
[English](../../contributing.md)
| **Українська**
| [Русский](./contributing_ru.md)
| [Türkçe](./contributing_tr.md)
| [Deutsch](./contributing_de.md)
| [Français](./contributing_fr.md)
| [Português (BR)](./contributing_pt_br.md)
| [한국어](./contributing_kr.md)
| [বাংলা](./contributing_bn.md)
| [Español](./contributing_es.md)
| [Italiano](./contributing_it.md)
| [Română](./contributing_ro.md)
| [Polski](./contributing_pl.md)
| [简体中文](./contributing_cn.md)
| [正體中文](./contributing_zhtw.md)
| [日本語](./contributing_ja.md)
| [हिंदी](./contributing_hi.md)
## Давайте зробимо Bruno краще, разом !!

View File

@@ -1,4 +1,20 @@
[English](../../contributing.md)
| [Українська](./contributing_ua.md)
| [Русский](./contributing_ru.md)
| [Türkçe](./contributing_tr.md)
| [Deutsch](./contributing_de.md)
| [Français](./contributing_fr.md)
| [Português (BR)](./contributing_pt_br.md)
| [한국어](./contributing_kr.md)
| [বাংলা](./contributing_bn.md)
| [Español](./contributing_es.md)
| [Italiano](./contributing_it.md)
| [Română](./contributing_ro.md)
| [Polski](./contributing_pl.md)
| [简体中文](./contributing_cn.md)
| **正體中文**
| [日本語](./contributing_ja.md)
| [हिंदी](./contributing_hi.md)
## 讓我們一起來讓 Bruno 變得更好!

View File

@@ -1,7 +0,0 @@
[English](../../publishing.md)
### Bruno publiceren naar een nieuwe pakketbeheerder
Hoewel onze code open source is en beschikbaar voor iedereen, verzoeken we je vriendelijk om contact met ons op te nemen voordat je publicatie overweegt op nieuwe pakketbeheerders. Als de maker van Bruno houd ik het handelsmerk `Bruno` voor dit project en wil ik het distributieproces beheren. Als je Bruno op een nieuwe pakketbeheerder wilt zien, dien dan een GitHub-issue in.
Hoewel de meerderheid van onze functies gratis en open source zijn (die REST en GraphQL API's dekken), streven we ernaar een harmonieuze balans te vinden tussen open-source principes en duurzaamheid - https://github.com/usebruno/bruno/discussions/269

View File

@@ -1,4 +1,14 @@
[English](../../publishing.md)
| [Türkçe](./publishing_tr.md)
| [Deutsch](./publishing_de.md)
| [Français](./publishing_fr.md)
| [Português (BR)](./publishing_pt_br.md)
| **বাংলা**
| [Română](./publishing_ro.md)
| [Polski](./publishing_pl.md)
| [简体中文](./publishing_cn.md)
| [正體中文](./publishing_zhtw.md)
| [日本語](./publishing_ja.md)
### ব্রুনোকে নতুন প্যাকেজ ম্যানেজারে প্রকাশ করা

View File

@@ -1,4 +1,14 @@
[English](../../publishing.md)
| [Türkçe](./publishing_tr.md)
| [Deutsch](./publishing_de.md)
| [Français](./publishing_fr.md)
| [Português (BR)](./publishing_pt_br.md)
| [বাংলা](./publishing_bn.md)
| [Română](./publishing_ro.md)
| [Polski](./publishing_pl.md)
| **简体中文**
| [正體中文](./publishing_zhtw.md)
| [日本語](./publishing_ja.md)
### 将 Bruno 发布到新的包管理器

View File

@@ -1,4 +1,14 @@
[English](../../publishing.md)
| [Türkçe](./publishing_tr.md)
| **Deutsch**
| [Français](./publishing_fr.md)
| [Português (BR)](./publishing_pt_br.md)
| [বাংলা](./publishing_bn.md)
| [Română](./publishing_ro.md)
| [Polski](./publishing_pl.md)
| [简体中文](./publishing_cn.md)
| [正體中文](./publishing_zhtw.md)
| [日本語](./publishing_ja.md)
### Veröffentlichung von Bruno über neue Paket-Manager

View File

@@ -1,4 +1,14 @@
[English](../../publishing.md)
| [Türkçe](./publishing_tr.md)
| [Deutsch](./publishing_de.md)
| **Français**
| [Português (BR)](./publishing_pt_br.md)
| [বাংলা](./publishing_bn.md)
| [Română](./publishing_ro.md)
| [Polski](./publishing_pl.md)
| [简体中文](./publishing_cn.md)
| [正體中文](./publishing_zhtw.md)
| [日本語](./publishing_ja.md)
### Publier Bruno dans un nouveau gestionnaire de paquets

View File

@@ -1,4 +1,14 @@
[English](../../publishing.md)
| [Türkçe](./publishing_tr.md)
| [Deutsch](./publishing_de.md)
| [Français](./publishing_fr.md)
| [Português (BR)](./publishing_pt_br.md)
| [বাংলা](./publishing_bn.md)
| [Română](./publishing_ro.md)
| [Polski](./publishing_pl.md)
| [简体中文](./publishing_cn.md)
| [正體中文](./publishing_zhtw.md)
| **日本語**
### Bruno を新しいパッケージマネージャに公開する場合の注意

View File

@@ -1,4 +1,14 @@
[English](../../publishing.md)
| [Türkçe](./publishing_tr.md)
| [Deutsch](./publishing_de.md)
| [Français](./publishing_fr.md)
| [Português (BR)](./publishing_pt_br.md)
| [বাংলা](./publishing_bn.md)
| [Română](./publishing_ro.md)
| **Polski**
| [简体中文](./publishing_cn.md)
| [正體中文](./publishing_zhtw.md)
| [日本語](./publishing_ja.md)
### Publikowanie Bruno w nowym menedżerze pakietów

View File

@@ -1,4 +1,14 @@
[English](../../publishing.md)
| [Türkçe](./publishing_tr.md)
| [Deutsch](./publishing_de.md)
| [Français](./publishing_fr.md)
| **Português (BR)**
| [বাংলা](./publishing_bn.md)
| [Română](./publishing_ro.md)
| [Polski](./publishing_pl.md)
| [简体中文](./publishing_cn.md)
| [正體中文](./publishing_zhtw.md)
| [日本語](./publishing_ja.md)
### Publicando Bruno em um novo gerenciador de pacotes

View File

@@ -1,4 +1,14 @@
[English](../../publishing.md)
| [Türkçe](./publishing_tr.md)
| [Deutsch](./publishing_de.md)
| [Français](./publishing_fr.md)
| [Português (BR)](./publishing_pt_br.md)
| [বাংলা](./publishing_bn.md)
| **Română**
| [Polski](./publishing_pl.md)
| [简体中文](./publishing_cn.md)
| [正體中文](./publishing_zhtw.md)
| [日本語](./publishing_ja.md)
### Publicarea lui Bruno la un gestionar de pachete nou

View File

@@ -1,4 +1,14 @@
[English](../../publishing.md)
| **Türkçe**
| [Deutsch](./publishing_de.md)
| [Français](./publishing_fr.md)
| [Português (BR)](./publishing_pt_br.md)
| [বাংলা](./publishing_bn.md)
| [Română](./publishing_ro.md)
| [Polski](./publishing_pl.md)
| [简体中文](./publishing_cn.md)
| [正體中文](./publishing_zhtw.md)
| [日本語](./publishing_ja.md)
### Bruno'yu yeni bir paket yöneticisine yayınlama

View File

@@ -1,4 +1,14 @@
[English](../../publishing.md)
| [Türkçe](./publishing_tr.md)
| [Deutsch](./publishing_de.md)
| [Français](./publishing_fr.md)
| [Português (BR)](./publishing_pt_br.md)
| [বাংলা](./publishing_bn.md)
| [Română](./publishing_ro.md)
| [Polski](./publishing_pl.md)
| [简体中文](./publishing_cn.md)
| **正體中文**
| [日本語](./publishing_ja.md)
### 將 Bruno 發佈到新的套件管理器

View File

@@ -39,7 +39,7 @@ Bruno ist ein reines Offline-Tool. Es gibt keine Pläne, Bruno um eine Cloud-Syn
[Download Bruno](https://www.usebruno.com/downloads)
📢 Sieh Dir unseren Vortrag auf der India FOSS 3.0 Conference [hier](https://www.youtube.com/watch?v=7bSMFpbcPiY) an.
📢 Sehen Sie sich unseren Vortrag auf der India FOSS 3.0 Conference [hier](https://www.youtube.com/watch?v=7bSMFpbcPiY) an.
![bruno](/assets/images/landing-2.png) <br /><br />
@@ -48,13 +48,13 @@ Bruno ist ein reines Offline-Tool. Es gibt keine Pläne, Bruno um eine Cloud-Syn
Die meisten unserer Funktionen sind kostenlos und quelloffen.
Wir bemühen uns um ein Gleichgewicht zwischen [Open-Source-Prinzipien und Nachhaltigkeit](https://github.com/usebruno/bruno/discussions/269)
Du kannst die [Golden Edition](https://www.usebruno.com/pricing) bestellen **$19**! <br/>
Sie können die [Golden Edition](https://www.usebruno.com/pricing) vorbestellen ~~$19~~ **$9** ! <br/>
### Installation
Bruno ist als Download [auf unserer Website](https://www.usebruno.com/downloads) für Mac, Windows und Linux verfügbar.
Du kannst Bruno auch über Paketmanager wie Homebrew, Chocolatey, Scoop, Snap, Flatpak und Apt installieren.
Sie können Bruno auch über Paketmanager wie Homebrew, Chocolatey, Scoop, Snap, Flatpak und Apt installieren.
```sh
# Auf Mac via Homebrew
@@ -123,11 +123,11 @@ Oder einer Versionskontrolle deiner Wahl
### Unterstützung ❤️
Wuff! Wenn du dieses Projekt magst, klick auf den ⭐ Button !!
Wuff! Wenn du dieses Projekt magst, klick den ⭐ Button !!
### Teile Erfahrungsberichte 📣
Wenn Bruno dir und in deinem Team bei der Arbeit geholfen hat, vergiss bitte nicht, deine [Erfahrungsberichte in unserer GitHub-Diskussion](https://github.com/usebruno/bruno/discussions/343) zu teilen.
Wenn Bruno dir und in deinen Teams bei der Arbeit geholfen hat, vergiss bitte nicht, deine [Erfahrungsberichte auf unserer GitHub-Diskussion](https://github.com/usebruno/bruno/discussions/343) zu teilen.
### Bereitstellung in neuen Paket-Managern

View File

@@ -1,157 +0,0 @@
<br />
<img src="../../assets/images/logo-transparent.png" width="80"/>
### Bruno - Open source IDE voor het verkennen en testen van API's.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)
[![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
[![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
[English](../../readme.md) | [Українська](docs/readme/readme_ua.md) | [Русский](docs/readme/readme_ru.md) | [Türkçe](docs/readme/readme_tr.md) | [Deutsch](docs/readme/readme_de.md) | ** Nederlands ** | [Français](docs/readme/readme_fr.md) | [Português (BR)](docs/readme/readme_pt_br.md) | [한국어](docs/readme/readme_kr.md) | [বাংলা](docs/readme/readme_bn.md) | [Español](docs/readme/readme_es.md) | [Italiano](docs/readme/readme_it.md) | [Română](docs/readme/readme_ro.md) | [Polski](docs/readme/readme_pl.md) | [简体中文](docs/readme/readme_cn.md) | [正體中文](docs/readme/readme_zhtw.md) | [العربية](docs/readme/readme_ar.md) | [日本語](docs/readme/readme_ja.md)
Bruno is een nieuwe en innovatieve API-client, gericht op het revolutioneren van de status quo die wordt vertegenwoordigd door Postman en vergelijkbare tools.
Bruno slaat je collecties direct op in een map op je bestandssysteem. We gebruiken een platte tekst opmaaktaal, Bru, om informatie over API-verzoeken op te slaan.
Je kunt Git of elke versiebeheertool naar keuze gebruiken om samen te werken aan je API-collecties.
Bruno is uitsluitend offline. Er zijn geen plannen om ooit cloud-synchronisatie aan Bruno toe te voegen. We waarderen je gegevensprivacy en geloven dat deze op je apparaat moet blijven. Lees onze langetermijnvisie [hier](https://github.com/usebruno/bruno/discussions/269)
[Download Bruno](https://www.usebruno.com/downloads)
📢 Bekijk onze recente presentatie op de India FOSS 3.0 Conference [hier](https://www.youtube.com/watch?v=7bSMFpbcPiY)
![bruno](/assets/images/landing-2.png) <br /><br />
### Golden Edition ✨
De meeste van onze functies zijn gratis en open source.
We streven naar een harmonieuze balans tussen [open-source principes en duurzaamheid](https://github.com/usebruno/bruno/discussions/269).
Je kunt de [Golden Edition](https://www.usebruno.com/pricing) kopen voor een eenmalige betaling van **$19**! <br/>
### Installatie
Bruno is beschikbaar als binaire download [op onze website](https://www.usebruno.com/downloads) voor Mac, Windows en Linux.
Je kunt Bruno ook installeren via pakketbeheerders zoals Homebrew, Chocolatey, Scoop, Snap, Flatpak en Apt.
```sh
# Op Mac via Homebrew
brew install bruno
# Op Windows via Chocolatey
choco install bruno
# Op Windows via Scoop
scoop bucket add extras
scoop install bruno
# Op Windows via winget
winget install Bruno.Bruno
# Op Linux via Snap
snap install bruno
# Op Linux via Flatpak
flatpak install com.usebruno.Bruno
# Op Linux via Apt
sudo mkdir -p /etc/apt/keyrings
sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
sudo apt update
sudo apt install bruno
```
### Draai op meerdere platformen 🖥️
![bruno](/assets/images/run-anywhere.png) <br /><br />
### Samenwerken via Git 👩‍💻🧑‍💻
Of elk versiebeheersysteem naar keuze
![bruno](/assets/images/version-control.png) <br /><br />
### Sponsors
#### Gouden Sponsors
<img src="../../assets/images/sponsors/samagata.png" width="150"/>
#### Zilveren Sponsors
<img src="../../assets/images/sponsors/commit-company.png" width="70"/>
#### Bronzen Sponsors
<a href="https://zuplo.link/bruno">
<img src="../../assets/images/sponsors/zuplo.png" width="120"/>
</a>
### Belangrijke Links 📌
- [Onze Langetermijnvisie](https://github.com/usebruno/bruno/discussions/269)
- [Roadmap](https://github.com/usebruno/bruno/discussions/384)
- [Documentatie](https://docs.usebruno.com)
- [Stack Overflow](https://stackoverflow.com/questions/tagged/bruno)
- [Website](https://www.usebruno.com)
- [Prijzen](https://www.usebruno.com/pricing)
- [Download](https://www.usebruno.com/downloads)
- [GitHub Sponsors](https://github.com/sponsors/helloanoop)
### Showcase 🎥
- [Getuigenissen](https://github.com/usebruno/bruno/discussions/343)
- [Kenniscentrum](https://github.com/usebruno/bruno/discussions/386)
- [Scriptmania](https://github.com/usebruno/bruno/discussions/385)
### Ondersteuning ❤️
Als je Bruno leuk vindt en ons open-source werk wilt ondersteunen, overweeg dan om ons te sponsoren via [GitHub Sponsors](https://github.com/sponsors/helloanoop).
### Deel Getuigenissen 📣
Als Bruno je heeft geholpen op je werk en in je teams, deel dan je [getuigenissen op onze GitHub-discussie](https://github.com/usebruno/bruno/discussions/343).
### Blijf in contact 🌐
[𝕏 (Twitter)](https://twitter.com/use_bruno) <br />
[Website](https://www.usebruno.com) <br />
[Discord](https://discord.com/invite/KgcZUncpjq) <br />
[LinkedIn](https://www.linkedin.com/company/usebruno)
### Handelsmerk
**Naam**
`Bruno` is een handelsmerk in bezit van [Anoop M D](https://www.helloanoop.com/).
**Logo**
Het logo is afkomstig van [OpenMoji](https://openmoji.org/library/emoji-1F436/). Licentie: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
### Bijdragen 👩‍💻🧑‍💻
Ik ben blij dat je Bruno wilt verbeteren. Bekijk de [bijdragegids](contributing.md).
Zelfs als je geen bijdragen via code kunt leveren, aarzel dan niet om bugs en functieverzoeken in te dienen die moeten worden geïmplementeerd om jouw gebruiksscenario op te lossen.
### Auteurs
<div align="center">
<a href="https://github.com/usebruno/bruno/graphs/contributors">
<img src="https://contrib.rocks/image?repo=usebruno/bruno" />
</a>
</div>
### Licentie 📄
[MIT](../../license.md)

57
package-lock.json generated
View File

@@ -50,7 +50,6 @@
},
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
@@ -671,7 +670,6 @@
},
"node_modules/@babel/compat-data": {
"version": "7.25.2",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -679,7 +677,6 @@
},
"node_modules/@babel/core": {
"version": "7.25.2",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
@@ -743,7 +740,6 @@
},
"node_modules/@babel/helper-compilation-targets": {
"version": "7.25.2",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.25.2",
@@ -758,7 +754,6 @@
},
"node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": {
"version": "5.1.1",
"dev": true,
"license": "ISC",
"dependencies": {
"yallist": "^3.0.2"
@@ -766,7 +761,6 @@
},
"node_modules/@babel/helper-compilation-targets/node_modules/yallist": {
"version": "3.1.1",
"dev": true,
"license": "ISC"
},
"node_modules/@babel/helper-create-class-features-plugin": {
@@ -845,7 +839,6 @@
},
"node_modules/@babel/helper-module-transforms": {
"version": "7.25.2",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.24.7",
@@ -912,7 +905,6 @@
},
"node_modules/@babel/helper-simple-access": {
"version": "7.24.7",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.24.7",
@@ -950,7 +942,6 @@
},
"node_modules/@babel/helper-validator-option": {
"version": "7.24.8",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -971,7 +962,6 @@
},
"node_modules/@babel/helpers": {
"version": "7.25.0",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/template": "^7.25.0",
@@ -3654,6 +3644,37 @@
"node": ">=12"
}
},
"node_modules/@n8n/vm2": {
"version": "3.9.25",
"resolved": "https://registry.npmjs.org/@n8n/vm2/-/vm2-3.9.25.tgz",
"integrity": "sha512-qoGLFzyHBW7HKpwXkl05QKsIh3GkDw6lOiTOWYlUDnOIQ1b7EgM+O5EMjrMGy7r+kz52+Q7o6GLxBIcxVI8rEg==",
"license": "MIT",
"peer": true,
"dependencies": {
"acorn": "^8.7.0",
"acorn-walk": "^8.2.0"
},
"bin": {
"vm2": "bin/vm2"
},
"engines": {
"node": ">=18.10",
"pnpm": ">=9.6"
}
},
"node_modules/@n8n/vm2/node_modules/acorn": {
"version": "8.12.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/@next/env": {
"version": "12.3.3",
"license": "MIT"
@@ -4730,7 +4751,6 @@
},
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"dev": true,
"license": "MIT"
},
"node_modules/@types/lodash": {
@@ -4739,7 +4759,6 @@
},
"node_modules/@types/markdown-it": {
"version": "12.2.3",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/linkify-it": "*",
@@ -4748,7 +4767,6 @@
},
"node_modules/@types/mdurl": {
"version": "2.0.0",
"dev": true,
"license": "MIT"
},
"node_modules/@types/minimatch": {
@@ -6222,7 +6240,6 @@
},
"node_modules/browserslist": {
"version": "4.23.3",
"dev": true,
"funding": [
{
"type": "opencollective",
@@ -7221,7 +7238,6 @@
},
"node_modules/convert-source-map": {
"version": "2.0.0",
"dev": true,
"license": "MIT"
},
"node_modules/cookie": {
@@ -8461,7 +8477,6 @@
},
"node_modules/electron-to-chromium": {
"version": "1.5.11",
"dev": true,
"license": "ISC"
},
"node_modules/electron-util": {
@@ -9423,7 +9438,6 @@
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -13172,7 +13186,6 @@
},
"node_modules/node-releases": {
"version": "2.0.18",
"dev": true,
"license": "MIT"
},
"node_modules/node-vault": {
@@ -16188,7 +16201,6 @@
},
"node_modules/semver": {
"version": "6.3.1",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -17651,7 +17663,6 @@
},
"node_modules/update-browserslist-db": {
"version": "1.1.0",
"dev": true,
"funding": [
{
"type": "opencollective",
@@ -18567,6 +18578,7 @@
"inquirer": "^9.1.4",
"json-bigint": "^1.0.0",
"lodash": "^4.17.21",
"mustache": "^4.2.0",
"qs": "^6.11.0",
"socks-proxy-agent": "^8.0.2",
"vm2": "^3.9.13",
@@ -18621,7 +18633,7 @@
},
"packages/bruno-electron": {
"name": "bruno",
"version": "v1.28.0",
"version": "v1.26.1",
"dependencies": {
"@aws-sdk/credential-providers": "3.525.0",
"@usebruno/common": "0.1.0",
@@ -18652,6 +18664,7 @@
"json-bigint": "^1.0.0",
"lodash": "^4.17.21",
"mime-types": "^2.1.35",
"mustache": "^4.2.0",
"nanoid": "3.3.4",
"qs": "^6.11.0",
"socks-proxy-agent": "^8.0.2",
@@ -18862,4 +18875,4 @@
}
}
}
}
}

View File

@@ -5,7 +5,6 @@ const StyledWrapper = styled.div`
background: ${(props) => props.theme.codemirror.bg};
border: solid 1px ${(props) => props.theme.codemirror.border};
font-family: ${(props) => (props.font ? props.font : 'default')};
font-size: ${(props) => (props.fontSize ? `${props.fontSize}px` : 'inherit')};
line-break: anywhere;
flex: 1 1 0;
}

View File

@@ -6,7 +6,7 @@
*/
import React from 'react';
import { isEqual, escapeRegExp } from 'lodash';
import isEqual from 'lodash/isEqual';
import { getEnvironmentVariables } from 'utils/collections';
import { defineCodeMirrorBrunoVariablesMode } from 'utils/common/codemirror';
import StyledWrapper from './StyledWrapper';
@@ -61,15 +61,13 @@ if (!SERVER_RENDERED) {
'bru.getProcessEnv(key)',
'bru.hasEnvVar(key)',
'bru.getEnvVar(key)',
'bru.getFolderVar(key)',
'bru.getCollectionVar(key)',
'bru.setEnvVar(key,value)',
'bru.hasVar(key)',
'bru.getVar(key)',
'bru.setVar(key,value)',
'bru.deleteVar(key)',
'bru.setNextRequest(requestName)',
'req.disableParsingResponseJson()',
'req.disableParsingResponseJson()'
'bru.getRequestVar(key)',
'bru.sleep(ms)'
];
@@ -335,7 +333,6 @@ export default class CodeEditor extends React.Component {
className="h-full w-full flex flex-col relative"
aria-label="Code Editor"
font={this.props.font}
fontSize={this.props.fontSize}
ref={(node) => {
this._node = node;
}}
@@ -408,8 +405,7 @@ export default class CodeEditor extends React.Component {
const searchInput = document.querySelector('.CodeMirror-search-field');
if (searchInput && searchInput.value.length > 0) {
// Escape special characters in search input to prevent RegExp crashes. Fixes #3051
const text = new RegExp(escapeRegExp(searchInput.value), 'gi');
const text = new RegExp(searchInput.value, 'gi');
const matches = this.editor.getValue().match(text);
count = matches ? matches.length : 0;
}

View File

@@ -10,9 +10,8 @@ import StyledWrapper from './StyledWrapper';
import { useRef } from 'react';
import path from 'path';
import slash from 'utils/common/slash';
import { isWindowsOS } from 'utils/common/platform';
const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
const ClientCertSettings = ({ clientCertConfig, onUpdate, onRemove }) => {
const certFilePathInputRef = useRef();
const keyFilePathInputRef = useRef();
const pfxFilePathInputRef = useRef();
@@ -68,15 +67,7 @@ const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
});
const getFile = (e) => {
if (e.files?.[0]?.path) {
let relativePath;
if (isWindowsOS()) {
relativePath = slash(path.win32.relative(root, e.files[0].path));
} else {
relativePath = path.posix.relative(root, e.files[0].path);
}
formik.setFieldValue(e.name, relativePath);
}
e.files?.[0]?.path && formik.setFieldValue(e.name, e.files?.[0]?.path);
};
const resetFileInputFields = () => {
@@ -111,14 +102,10 @@ const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
: clientCertConfig.map((clientCert) => (
<li key={uuid()} className="flex items-center available-certificates p-2 rounded-lg mb-2">
<div className="flex items-center w-full justify-between">
<div className="flex w-full items-center">
<div className="flex items-center">
<IconWorld className="mr-2" size={18} strokeWidth={1.5} />
{clientCert.domain}
</div>
<div className="flex w-full items-center">
<IconCertificate className="mr-2 flex-shrink-0" size={18} strokeWidth={1.5} />
{clientCert.type === 'cert' ? clientCert.certFilePath : clientCert.pfxFilePath}
</div>
<button onClick={() => onRemove(clientCert)} className="remove-certificate ml-2">
<IconTrash size={18} strokeWidth={1.5} />
</button>

View File

@@ -46,7 +46,6 @@ const Docs = ({ collection }) => {
onSave={onSave}
mode="application/text"
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
/>
) : (
<Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={docs} />

View File

@@ -1,6 +1,6 @@
import React, { useEffect } from 'react';
import { useFormik } from 'formik';
import InfoTip from 'components/InfoTip';
import Tooltip from 'components/Tooltip';
import StyledWrapper from './StyledWrapper';
import * as Yup from 'yup';
import toast from 'react-hot-toast';
@@ -104,7 +104,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
<div className="mb-3 flex items-center">
<label className="settings-label flex items-center" htmlFor="enabled">
Config
<InfoTip
<Tooltip
text={`
<div>
<ul>
@@ -114,7 +114,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
</ul>
</div>
`}
infotipId="request-var"
tooltipId="request-var"
/>
</label>
<div className="flex items-center">
@@ -336,4 +336,4 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
);
};
export default ProxySettings;
export default ProxySettings;

View File

@@ -52,7 +52,6 @@ const Script = ({ collection }) => {
mode="javascript"
onSave={handleSave}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
/>
</div>
<div className="flex-1 mt-6">
@@ -65,7 +64,6 @@ const Script = ({ collection }) => {
mode="javascript"
onSave={handleSave}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
/>
</div>

View File

@@ -36,7 +36,6 @@ const Tests = ({ collection }) => {
mode="javascript"
onSave={handleSave}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
/>
<div className="mt-6">

View File

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

View File

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

View File

@@ -1,162 +0,0 @@
import React from 'react';
import cloneDeep from 'lodash/cloneDeep';
import { IconTrash } from '@tabler/icons';
import { useDispatch } from 'react-redux';
import { useTheme } from 'providers/Theme';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import SingleLineEditor from 'components/SingleLineEditor';
import InfoTip from 'components/InfoTip';
import StyledWrapper from './StyledWrapper';
import toast from 'react-hot-toast';
import { variableNameRegex } from 'utils/common/regex';
import {
addCollectionVar,
deleteCollectionVar,
updateCollectionVar
} from 'providers/ReduxStore/slices/collections/index';
const VarsTable = ({ collection, vars, varType }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const addVar = () => {
dispatch(
addCollectionVar({
collectionUid: collection.uid,
type: varType
})
);
};
const onSave = () => dispatch(saveCollectionRoot(collection.uid));
const handleVarChange = (e, v, type) => {
const _var = cloneDeep(v);
switch (type) {
case 'name': {
const value = e.target.value;
if (variableNameRegex.test(value) === false) {
toast.error(
'Variable contains invalid characters! Variables must only contain alpha-numeric characters, "-", "_", "."'
);
return;
}
_var.name = value;
break;
}
case 'value': {
_var.value = e.target.value;
break;
}
case 'enabled': {
_var.enabled = e.target.checked;
break;
}
}
dispatch(
updateCollectionVar({
type: varType,
var: _var,
collectionUid: collection.uid
})
);
};
const handleRemoveVar = (_var) => {
dispatch(
deleteCollectionVar({
type: varType,
varUid: _var.uid,
collectionUid: collection.uid
})
);
};
return (
<StyledWrapper className="w-full">
<table>
<thead>
<tr>
<td>Name</td>
{varType === 'request' ? (
<td>
<div className="flex items-center">
<span>Value</span>
<InfoTip text="You can write any valid JS Template Literal here" infotipId="request-var" />
</div>
</td>
) : (
<td>
<div className="flex items-center">
<span>Expr</span>
<InfoTip text="You can write any valid JS Template Literal here" infotipId="request-var" />
</div>
</td>
)}
<td></td>
</tr>
</thead>
<tbody>
{vars && vars.length
? vars.map((_var) => {
return (
<tr key={_var.uid}>
<td>
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={_var.name}
className="mousetrap"
onChange={(e) => handleVarChange(e, _var, 'name')}
/>
</td>
<td>
<SingleLineEditor
value={_var.value}
theme={storedTheme}
onSave={onSave}
onChange={(newValue) =>
handleVarChange(
{
target: {
value: newValue
}
},
_var,
'value'
)
}
collection={collection}
/>
</td>
<td>
<div className="flex items-center">
<input
type="checkbox"
checked={_var.enabled}
tabIndex="-1"
className="mr-3 mousetrap"
onChange={(e) => handleVarChange(e, _var, 'enabled')}
/>
<button tabIndex="-1" onClick={() => handleRemoveVar(_var)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>
</td>
</tr>
);
})
: null}
</tbody>
</table>
<button className="btn-add-var text-link pr-2 py-3 mt-2 select-none" onClick={addVar}>
+ Add
</button>
</StyledWrapper>
);
};
export default VarsTable;

View File

@@ -1,32 +0,0 @@
import React from 'react';
import get from 'lodash/get';
import VarsTable from './VarsTable';
import StyledWrapper from './StyledWrapper';
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
import { useDispatch } from 'react-redux';
const Vars = ({ collection }) => {
const dispatch = useDispatch();
const requestVars = get(collection, 'root.request.vars.req', []);
const responseVars = get(collection, 'root.request.vars.res', []);
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
return (
<StyledWrapper className="w-full flex flex-col">
<div className="flex-1 mt-2">
<div className="mb-1 title text-xs">Pre Request</div>
<VarsTable collection={collection} vars={requestVars} varType="request" />
</div>
<div className="flex-1">
<div className="mt-1 mb-1 title text-xs">Post Response</div>
<VarsTable collection={collection} vars={responseVars} varType="response" />
</div>
<div className="mt-6">
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
Save
</button>
</div>
</StyledWrapper>
);
};
export default Vars;

View File

@@ -16,7 +16,6 @@ import Docs from './Docs';
import Presets from './Presets';
import Info from './Info';
import StyledWrapper from './StyledWrapper';
import Vars from './Vars/index';
const CollectionSettings = ({ collection }) => {
const dispatch = useDispatch();
@@ -78,9 +77,6 @@ const CollectionSettings = ({ collection }) => {
case 'headers': {
return <Headers collection={collection} />;
}
case 'vars': {
return <Vars collection={collection} />;
}
case 'auth': {
return <Auth collection={collection} />;
}
@@ -99,7 +95,6 @@ const CollectionSettings = ({ collection }) => {
case 'clientCert': {
return (
<ClientCertSettings
root={collection.pathname}
clientCertConfig={clientCertConfig}
onUpdate={onClientCertSettingsUpdate}
onRemove={onClientCertSettingsRemove}
@@ -127,9 +122,6 @@ const CollectionSettings = ({ collection }) => {
<div className={getTabClassname('headers')} role="tab" onClick={() => setTab('headers')}>
Headers
</div>
<div className={getTabClassname('vars')} role="tab" onClick={() => setTab('vars')}>
Vars
</div>
<div className={getTabClassname('auth')} role="tab" onClick={() => setTab('auth')}>
Auth
</div>

View File

@@ -47,7 +47,6 @@ const Documentation = ({ item, collection }) => {
collection={collection}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
value={docs || ''}
onEdit={onEdit}
onSave={onSave}

View File

@@ -1,5 +1,4 @@
import React, { useRef, useEffect } from 'react';
import cloneDeep from 'lodash/cloneDeep';
import React from 'react';
import { IconTrash } from '@tabler/icons';
import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux';
@@ -10,12 +9,12 @@ import { useFormik } from 'formik';
import * as Yup from 'yup';
import { variableNameRegex } from 'utils/common/regex';
import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions';
import cloneDeep from 'lodash/cloneDeep';
import toast from 'react-hot-toast';
const EnvironmentVariables = ({ environment, collection, setIsModified, originalEnvironmentVariables }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const addButtonRef = useRef(null);
const formik = useFormik({
enableReinitialize: true,
@@ -86,12 +85,6 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
formik.setValues(formik.values.filter((variable) => variable.uid !== id));
};
useEffect(() => {
if (formik.dirty) {
addButtonRef.current?.scrollIntoView({ behavior: 'smooth' });
}
}, [formik.values, formik.dirty]);
const handleReset = () => {
formik.resetForm({ originalEnvironmentVariables });
};
@@ -166,15 +159,11 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
))}
</tbody>
</table>
<div>
<button
ref={addButtonRef}
className="btn-add-param text-link pr-2 py-3 mt-2 select-none"
onClick={addVariable}
>
+ Add Variable
</button>
</div>
</div>
<div>
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={addVariable}>
+ Add Variable
</button>
</div>
<div>

View File

@@ -116,7 +116,6 @@ const Headers = ({ collection, folder }) => {
)
}
collection={collection}
item={folder}
/>
</td>
<td>

View File

@@ -54,7 +54,6 @@ const Script = ({ collection, folder }) => {
mode="javascript"
onSave={handleSave}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
/>
</div>
<div className="flex flex-col flex-1 mt-2 gap-y-2">
@@ -67,7 +66,6 @@ const Script = ({ collection, folder }) => {
mode="javascript"
onSave={handleSave}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
/>
</div>

View File

@@ -37,7 +37,6 @@ const Tests = ({ collection, folder }) => {
mode="javascript"
onSave={handleSave}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
/>
<div className="mt-6">

View File

@@ -5,7 +5,7 @@ import { useDispatch } from 'react-redux';
import { useTheme } from 'providers/Theme';
import { saveFolderRoot } from 'providers/ReduxStore/slices/collections/actions';
import SingleLineEditor from 'components/SingleLineEditor';
import InfoTip from 'components/InfoTip';
import Tooltip from 'components/Tooltip';
import StyledWrapper from './StyledWrapper';
import toast from 'react-hot-toast';
import { variableNameRegex } from 'utils/common/regex';
@@ -82,14 +82,14 @@ const VarsTable = ({ folder, collection, vars, varType }) => {
<td>
<div className="flex items-center">
<span>Value</span>
<InfoTip text="You can write any valid JS Template Literal here" infotipId="request-var" />
<Tooltip text="You can write any valid JS Template Literal here" tooltipId="request-var" />
</div>
</td>
) : (
<td>
<div className="flex items-center">
<span>Expr</span>
<InfoTip text="You can write any valid JS expression here" infotipId="response-var" />
<Tooltip text="You can write any valid JS expression here" tooltipId="response-var" />
</div>
</td>
)}
@@ -130,7 +130,6 @@ const VarsTable = ({ folder, collection, vars, varType }) => {
)
}
collection={collection}
item={folder}
/>
</td>
<td>

View File

@@ -2,7 +2,6 @@ import MarkdownIt from 'markdown-it';
import * as MarkdownItReplaceLink from 'markdown-it-replace-link';
import StyledWrapper from './StyledWrapper';
import React from 'react';
import { isValidUrl } from 'utils/url/index';
const Markdown = ({ collectionPath, onDoubleClick, content }) => {
const markdownItOptions = {
@@ -16,7 +15,7 @@ const Markdown = ({ collectionPath, onDoubleClick, content }) => {
if (target.tagName === 'A') {
event.preventDefault();
const href = target.getAttribute('href');
if (href && isValidUrl(href)) {
if (href) {
window.open(href, '_blank');
return;
}

View File

@@ -1,9 +1,5 @@
import React, { useEffect, useState, useRef } from 'react';
import React, { useEffect, useState } from 'react';
import StyledWrapper from './StyledWrapper';
import useFocusTrap from 'hooks/useFocusTrap';
const ESC_KEY_CODE = 27;
const ENTER_KEY_CODE = 13;
const ModalHeader = ({ title, handleCancel, customHeader, hideClose }) => (
<div className="bruno-modal-header">
@@ -73,24 +69,14 @@ const Modal = ({
onClick,
closeModalFadeTimeout = 500
}) => {
const modalRef = useRef(null);
const [isClosing, setIsClosing] = useState(false);
const handleKeydown = ({ keyCode }) => {
switch (keyCode) {
case ESC_KEY_CODE: {
return closeModal({ type: 'esc' });
}
case ENTER_KEY_CODE: {
if(handleConfirm) {
return handleConfirm();
}
}
const escFunction = (event) => {
const escKeyCode = 27;
if (event.keyCode === escKeyCode) {
closeModal({ type: 'esc' });
}
};
useFocusTrap(modalRef);
const closeModal = (args) => {
setIsClosing(true);
setTimeout(() => handleCancel(args), closeModalFadeTimeout);
@@ -98,9 +84,10 @@ const Modal = ({
useEffect(() => {
if (disableEscapeKey) return;
document.addEventListener('keydown', handleKeydown, false);
document.addEventListener('keydown', escFunction, false);
return () => {
document.removeEventListener('keydown', handleKeydown);
document.removeEventListener('keydown', escFunction, false);
};
}, [disableEscapeKey, document]);
@@ -113,13 +100,7 @@ const Modal = ({
}
return (
<StyledWrapper className={classes} onClick={onClick ? (e) => onClick(e) : null}>
<div
className={`bruno-modal-card modal-${size}`}
ref={modalRef}
role="dialog"
aria-labelledby="modal-title"
aria-describedby="modal-description"
>
<div className={`bruno-modal-card modal-${size}`}>
<ModalHeader
title={title}
hideClose={hideClose}

View File

@@ -10,8 +10,6 @@ import {
} from 'providers/ReduxStore/slices/notifications';
import { useDispatch, useSelector } from 'react-redux';
import { humanizeDate, relativeDate } from 'utils/common';
import ToolHint from 'components/ToolHint';
import { useTheme } from 'providers/Theme';
const PAGE_SIZE = 5;
@@ -22,7 +20,6 @@ const Notifications = () => {
const [showNotificationsModal, setShowNotificationsModal] = useState(false);
const [selectedNotification, setSelectedNotification] = useState(null);
const [pageNumber, setPageNumber] = useState(1);
const { storedTheme } = useTheme();
const notificationsStartIndex = (pageNumber - 1) * PAGE_SIZE;
const notificationsEndIndex = pageNumber * PAGE_SIZE;
@@ -88,22 +85,21 @@ const Notifications = () => {
return (
<StyledWrapper>
<a
title="Notifications"
className="relative cursor-pointer"
onClick={() => {
dispatch(fetchNotifications());
setShowNotificationsModal(true);
}}
>
<ToolHint text="Notifications" toolhintId="Notifications" offset={8} >
<IconBell
size={18}
strokeWidth={1.5}
className={`mr-2 ${unreadNotifications?.length > 0 ? 'bell' : ''}`}
/>
{unreadNotifications.length > 0 && (
<span className="notification-count text-xs">{unreadNotifications.length}</span>
)}
</ToolHint>
<IconBell
size={18}
strokeWidth={1.5}
className={`mr-2 hover:text-gray-700 ${unreadNotifications?.length > 0 ? 'bell' : ''}`}
/>
{unreadNotifications.length > 0 && (
<span className="notification-count text-xs">{unreadNotifications.length}</span>
)}
</a>
{showNotificationsModal && (
@@ -133,8 +129,9 @@ const Notifications = () => {
{notifications?.slice(notificationsStartIndex, notificationsEndIndex)?.map((notification) => (
<li
key={notification.id}
className={`p-4 flex flex-col justify-center ${selectedNotification?.id == notification.id ? 'active' : notification.read ? 'read' : ''
}`}
className={`p-4 flex flex-col justify-center ${
selectedNotification?.id == notification.id ? 'active' : notification.read ? 'read' : ''
}`}
onClick={handleNotificationItemClick(notification)}
>
<div className="notification-title w-full">{notification?.title}</div>
@@ -144,8 +141,9 @@ const Notifications = () => {
</ul>
<div className="w-full pagination flex flex-row gap-4 justify-center p-2 items-center text-xs">
<button
className={`pl-2 pr-2 py-3 select-none ${pageNumber <= 1 ? 'opacity-50' : 'text-link cursor-pointer hover:underline'
}`}
className={`pl-2 pr-2 py-3 select-none ${
pageNumber <= 1 ? 'opacity-50' : 'text-link cursor-pointer hover:underline'
}`}
onClick={handlePrev}
>
{'Prev'}
@@ -161,8 +159,9 @@ const Notifications = () => {
</div>
</div>
<button
className={`pl-2 pr-2 py-3 select-none ${pageNumber == totalPages ? 'opacity-50' : 'text-link cursor-pointer hover:underline'
}`}
className={`pl-2 pr-2 py-3 select-none ${
pageNumber == totalPages ? 'opacity-50' : 'text-link cursor-pointer hover:underline'
}`}
onClick={handleNext}
>
{'Next'}

View File

@@ -9,25 +9,17 @@ const Font = ({ close }) => {
const preferences = useSelector((state) => state.app.preferences);
const [codeFont, setCodeFont] = useState(get(preferences, 'font.codeFont', 'default'));
const [codeFontSize, setCodeFontSize] = useState(get(preferences, 'font.codeFontSize', '14'));
const handleCodeFontChange = (event) => {
const handleInputChange = (event) => {
setCodeFont(event.target.value);
};
const handleCodeFontSizeChange = (event) => {
// Restrict to min/max value
const clampedSize = Math.max(1, Math.min(event.target.value, 32));
setCodeFontSize(clampedSize);
};
const handleSave = () => {
dispatch(
savePreferences({
...preferences,
font: {
codeFont,
codeFontSize
codeFont
}
})
).then(() => {
@@ -37,33 +29,17 @@ const Font = ({ close }) => {
return (
<StyledWrapper>
<div className="flex flex-row gap-2 w-full">
<div className="w-4/5">
<label className="block font-medium">Code Editor Font</label>
<input
type="text"
className="block textbox mt-2 w-full"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={handleCodeFontChange}
defaultValue={codeFont}
/>
</div>
<div className="w-1/5">
<label className="block font-medium">Font Size</label>
<input
type="number"
className="block textbox mt-2 w-full"
autoComplete="off"
autoCorrect="off"
inputMode="numeric"
onChange={handleCodeFontSizeChange}
defaultValue={codeFontSize}
/>
</div>
</div>
<label className="block font-medium">Code Editor Font</label>
<input
type="text"
className="block textbox mt-2 w-full"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={handleInputChange}
defaultValue={codeFont}
/>
<div className="mt-10">
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>

View File

@@ -2,7 +2,7 @@ import styled from 'styled-components';
const StyledWrapper = styled.div`
.settings-label {
width: 100px;
width: 80px;
}
.textbox {
@@ -20,12 +20,6 @@ const StyledWrapper = styled.div`
outline: none !important;
}
}
.system-proxy-settings {
label {
color: ${(props) => props.theme.colors.text.yellow};
}
}
`;
export default StyledWrapper;

View File

@@ -11,17 +11,14 @@ import { useState } from 'react';
const ProxySettings = ({ close }) => {
const preferences = useSelector((state) => state.app.preferences);
const systemProxyEnvVariables = useSelector((state) => state.app.systemProxyEnvVariables);
const { http_proxy, https_proxy, no_proxy } = systemProxyEnvVariables || {};
const dispatch = useDispatch();
console.log(preferences);
const proxySchema = Yup.object({
mode: Yup.string().oneOf(['off', 'on', 'system']),
enabled: Yup.boolean(),
protocol: Yup.string().required().oneOf(['http', 'https', 'socks4', 'socks5']),
hostname: Yup.string()
.when('enabled', {
is: 'on',
is: true,
then: (hostname) => hostname.required('Specify the hostname for your proxy.'),
otherwise: (hostname) => hostname.nullable()
})
@@ -34,7 +31,7 @@ const ProxySettings = ({ close }) => {
.transform((_, val) => (val ? Number(val) : null)),
auth: Yup.object()
.when('enabled', {
is: 'on',
is: true,
then: Yup.object({
enabled: Yup.boolean(),
username: Yup.string()
@@ -57,7 +54,7 @@ const ProxySettings = ({ close }) => {
const formik = useFormik({
initialValues: {
mode: preferences.proxy.mode,
enabled: preferences.proxy.enabled || false,
protocol: preferences.proxy.protocol || 'http',
hostname: preferences.proxy.hostname || '',
port: preferences.proxy.port || 0,
@@ -97,7 +94,7 @@ const ProxySettings = ({ close }) => {
useEffect(() => {
formik.setValues({
mode: preferences.proxy.mode,
enabled: preferences.proxy.enabled || false,
protocol: preferences.proxy.protocol || 'http',
hostname: preferences.proxy.hostname || '',
port: preferences.proxy.port || '',
@@ -112,256 +109,188 @@ const ProxySettings = ({ close }) => {
return (
<StyledWrapper>
<h1 className="font-medium mb-3">Global Proxy Settings</h1>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="enabled">
Enabled
</label>
<input type="checkbox" name="enabled" checked={formik.values.enabled} onChange={formik.handleChange} />
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="protocol">
Mode
Protocol
</label>
<div className="flex items-center">
<label className="flex items-center cursor-pointer">
<label className="flex items-center">
<input
type="radio"
name="mode"
value="false"
checked={formik.values.mode === 'off'}
onChange={(e) => {
formik.setFieldValue('mode', 'off');
}}
className="mr-1 cursor-pointer"
/>
Off
</label>
<label className="flex items-center ml-4 cursor-pointer">
<input
type="radio"
name="mode"
value="true"
checked={formik.values.mode === 'on'}
onChange={(e) => {
formik.setFieldValue('mode', 'on');
}}
className="mr-1 cursor-pointer"
/>
On
</label>
<label className="flex items-center ml-4 cursor-pointer">
<input
type="radio"
name="mode"
value="system"
checked={formik.values.mode === 'system'}
name="protocol"
value="http"
checked={formik.values.protocol === 'http'}
onChange={formik.handleChange}
className="mr-1 cursor-pointer"
className="mr-1"
/>
System Proxy
HTTP
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="https"
checked={formik.values.protocol === 'https'}
onChange={formik.handleChange}
className="mr-1"
/>
HTTPS
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks4"
checked={formik.values.protocol === 'socks4'}
onChange={formik.handleChange}
className="mr-1"
/>
SOCKS4
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks5"
checked={formik.values.protocol === 'socks5'}
onChange={formik.handleChange}
className="mr-1"
/>
SOCKS5
</label>
</div>
</div>
{formik?.values?.mode === 'system' ? (
<div className="mb-3 pt-1 text-muted system-proxy-settings">
<small>
Below values are sourced from your system environment variables and cannot be directly updated in Bruno.<br/>
Please refer to your OS documentation to change these values.
</small>
<div className="flex flex-col justify-start items-start pt-2">
<div className="mb-1 flex items-center">
<label className="settings-label" htmlFor="http_proxy">
http_proxy
</label>
<div className="opacity-80">{http_proxy || '-'}</div>
</div>
<div className="mb-1 flex items-center">
<label className="settings-label" htmlFor="https_proxy">
https_proxy
</label>
<div className="opacity-80">{https_proxy || '-'}</div>
</div>
<div className="mb-1 flex items-center">
<label className="settings-label" htmlFor="no_proxy">
no_proxy
</label>
<div className="opacity-80">{no_proxy || '-'}</div>
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="hostname">
Hostname
</label>
<input
id="hostname"
type="text"
name="hostname"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.hostname || ''}
/>
{formik.touched.hostname && formik.errors.hostname ? (
<div className="ml-3 text-red-500">{formik.errors.hostname}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="port">
Port
</label>
<input
id="port"
type="number"
name="port"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.port}
/>
{formik.touched.port && formik.errors.port ? (
<div className="ml-3 text-red-500">{formik.errors.port}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.enabled">
Auth
</label>
<input
type="checkbox"
name="auth.enabled"
checked={formik.values.auth.enabled}
onChange={formik.handleChange}
/>
</div>
<div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.username">
Username
</label>
<input
id="auth.username"
type="text"
name="auth.username"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={formik.values.auth.username}
onChange={formik.handleChange}
/>
{formik.touched.auth?.username && formik.errors.auth?.username ? (
<div className="ml-3 text-red-500">{formik.errors.auth.username}</div>
) : null}
</div>
) : null}
{formik?.values?.mode === 'on' ? (
<>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="protocol">
Protocol
</label>
<div className="flex items-center">
<label className="flex items-center">
<input
type="radio"
name="protocol"
value="http"
checked={formik.values.protocol === 'http'}
onChange={formik.handleChange}
className="mr-1"
/>
HTTP
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="https"
checked={formik.values.protocol === 'https'}
onChange={formik.handleChange}
className="mr-1"
/>
HTTPS
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks4"
checked={formik.values.protocol === 'socks4'}
onChange={formik.handleChange}
className="mr-1"
/>
SOCKS4
</label>
<label className="flex items-center ml-4">
<input
type="radio"
name="protocol"
value="socks5"
checked={formik.values.protocol === 'socks5'}
onChange={formik.handleChange}
className="mr-1"
/>
SOCKS5
</label>
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="hostname">
Hostname
</label>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.password">
Password
</label>
<div className="textbox flex flex-row items-center w-[13.2rem] h-[2.25rem] relative">
<input
id="hostname"
type="text"
name="hostname"
className="block textbox"
id="auth.password"
type={passwordVisible ? `text` : 'password'}
name="auth.password"
className="outline-none w-[10.5rem] bg-transparent"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.hostname || ''}
/>
{formik.touched.hostname && formik.errors.hostname ? (
<div className="ml-3 text-red-500">{formik.errors.hostname}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="port">
Port
</label>
<input
id="port"
type="number"
name="port"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.port}
/>
{formik.touched.port && formik.errors.port ? (
<div className="ml-3 text-red-500">{formik.errors.port}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.enabled">
Auth
</label>
<input
type="checkbox"
name="auth.enabled"
checked={formik.values.auth.enabled}
value={formik.values.auth.password}
onChange={formik.handleChange}
/>
<button
type="button"
className="btn btn-sm absolute right-0"
onClick={() => setPasswordVisible(!passwordVisible)}
>
{passwordVisible ? <IconEyeOff size={18} strokeWidth={2} /> : <IconEye size={18} strokeWidth={2} />}
</button>
</div>
<div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.username">
Username
</label>
<input
id="auth.username"
type="text"
name="auth.username"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={formik.values.auth.username}
onChange={formik.handleChange}
/>
{formik.touched.auth?.username && formik.errors.auth?.username ? (
<div className="ml-3 text-red-500">{formik.errors.auth.username}</div>
) : null}
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="auth.password">
Password
</label>
<div className="textbox flex flex-row items-center w-[13.2rem] h-[2.25rem] relative">
<input
id="auth.password"
type={passwordVisible ? `text` : 'password'}
name="auth.password"
className="outline-none w-[10.5rem] bg-transparent"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={formik.values.auth.password}
onChange={formik.handleChange}
/>
<button
type="button"
className="btn btn-sm absolute right-0"
onClick={() => setPasswordVisible(!passwordVisible)}
>
{passwordVisible ? <IconEyeOff size={18} strokeWidth={2} /> : <IconEye size={18} strokeWidth={2} />}
</button>
</div>
{formik.touched.auth?.password && formik.errors.auth?.password ? (
<div className="ml-3 text-red-500">{formik.errors.auth.password}</div>
) : null}
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="bypassProxy">
Proxy Bypass
</label>
<input
id="bypassProxy"
type="text"
name="bypassProxy"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.bypassProxy || ''}
/>
{formik.touched.bypassProxy && formik.errors.bypassProxy ? (
<div className="ml-3 text-red-500">{formik.errors.bypassProxy}</div>
) : null}
</div>
</>
) : null}
{formik.touched.auth?.password && formik.errors.auth?.password ? (
<div className="ml-3 text-red-500">{formik.errors.auth.password}</div>
) : null}
</div>
</div>
<div className="mb-3 flex items-center">
<label className="settings-label" htmlFor="bypassProxy">
Proxy Bypass
</label>
<input
id="bypassProxy"
type="text"
name="bypassProxy"
className="block textbox"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.bypassProxy || ''}
/>
{formik.touched.bypassProxy && formik.errors.bypassProxy ? (
<div className="ml-3 text-red-500">{formik.errors.bypassProxy}</div>
) : null}
</div>
<div className="mt-6">
<button type="submit" className="submit btn btn-md btn-secondary">
Save

View File

@@ -1,112 +0,0 @@
import React, { useEffect, useRef, useState, useCallback } from 'react';
import { IconGripVertical, IconMinusVertical } from '@tabler/icons';
/**
* ReorderTable Component
*
* A table component that allows rows to be reordered via drag-and-drop.
*
* @param {Object} props - The component props
* @param {React.ReactNode[]} props.children - The table rows as children
* @param {function} props.updateReorderedItem - Callback function to handle reordered rows
*/
const ReorderTable = ({ children, updateReorderedItem }) => {
const tbodyRef = useRef();
const [rowsOrder, setRowsOrder] = useState(React.Children.toArray(children));
const [hoveredRow, setHoveredRow] = useState(null);
const [dragStart, setDragStart] = useState(null);
/**
* useEffect hook to update the rows order and handle row hover states
*/
useEffect(() => {
setRowsOrder(React.Children.toArray(children));
handleRowHover(null, false);
}, [children, dragStart]);
const handleRowHover = (index, hoverstatus = true) => {
setHoveredRow(hoverstatus ? index : null);
};
const handleDragStart = (e, index) => {
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', index);
setDragStart(index);
};
const handleDragOver = (e, index) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
handleRowHover(index);
};
const handleDrop = (e, toIndex) => {
e.preventDefault();
const fromIndex = parseInt(e.dataTransfer.getData('text/plain'), 10);
if (fromIndex !== toIndex) {
const updatedRowsOrder = [...rowsOrder];
const [movedRow] = updatedRowsOrder.splice(fromIndex, 1);
updatedRowsOrder.splice(toIndex, 0, movedRow);
setRowsOrder(updatedRowsOrder);
updateReorderedItem({
updateReorderedItem: updatedRowsOrder.map((row) => row.props['data-uid'])
});
setTimeout(() => {
handleRowHover(toIndex);
}, 0);
}
};
return (
<tbody ref={tbodyRef}>
{rowsOrder.map((row, index) => (
<tr
key={row.props['data-uid']}
data-uid={row.props['data-uid']}
draggable
onDragStart={(e) => handleDragStart(e, index)}
onDragOver={(e) => handleDragOver(e, index)}
onDrop={(e) => handleDrop(e, index)}
onMouseEnter={() => handleRowHover(index)}
onMouseLeave={() => handleRowHover(index, false)}
>
{React.Children.map(row.props.children, (child, childIndex) => {
if (childIndex === 0) {
return React.cloneElement(child, {
children: (
<>
<div
draggable
className="group drag-handle absolute z-10 left-[-17px] p-3.5 py-3.5 px-2.5 top-[3px] cursor-grab"
>
{hoveredRow === index && (
<>
<IconGripVertical
size={14}
className="z-10 icon-grip rounded-md absolute hidden group-hover:block"
/>
<IconMinusVertical
size={14}
className="z-10 icon-minus rounded-md absolute block group-hover:hidden"
/>
</>
)}
</div>
{child.props.children}
</>
)
});
} else {
return child;
}
})}
</tr>
))}
</tbody>
);
};
export default ReorderTable;

View File

@@ -191,7 +191,6 @@ const AssertionRow = ({
}
onRun={handleRun}
collection={collection}
item={item}
/>
) : (
<input type="text" className="cursor-default" disabled />

View File

@@ -31,7 +31,6 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
: get(item, 'request.body.graphql.variables');
const { displayedTheme } = useTheme();
const [schema, setSchema] = useState(null);
const preferences = useSelector((state) => state.app.preferences);
useEffect(() => {
onSchemaLoad(schema);
@@ -72,8 +71,6 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
onRun={onRun}
onEdit={onQueryChange}
onClickReference={handleGqlClickReference}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
/>
);
}

View File

@@ -6,9 +6,6 @@ import { updateRequestGraphqlVariables } from 'providers/ReduxStore/slices/colle
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import { useTheme } from 'providers/Theme';
import StyledWrapper from './StyledWrapper';
import { format, applyEdits } from 'jsonc-parser';
import { IconWand } from '@tabler/icons';
import toast from 'react-hot-toast';
const GraphQLVariables = ({ variables, item, collection }) => {
const dispatch = useDispatch();
@@ -16,25 +13,6 @@ const GraphQLVariables = ({ variables, item, collection }) => {
const { displayedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const onPrettify = () => {
if (!variables) return;
try {
const edits = format(variables, undefined, { tabSize: 2, insertSpaces: true });
const prettyVariables = applyEdits(variables, edits);
dispatch(
updateRequestGraphqlVariables({
variables: prettyVariables,
itemUid: item.uid,
collectionUid: collection.uid
})
);
toast.success('Variables prettified');
} catch (error) {
console.error(error);
toast.error('Error occurred while prettifying GraphQL variables');
}
};
const onEdit = (value) => {
dispatch(
updateRequestGraphqlVariables({
@@ -49,20 +27,12 @@ const GraphQLVariables = ({ variables, item, collection }) => {
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
return (
<StyledWrapper className="w-full relative">
<button
className="btn-add-param text-link px-4 py-4 select-none absolute top-0 right-0 z-10"
onClick={onPrettify}
title={'Prettify'}
>
<IconWand size={20} strokeWidth={1.5} />
</button>
<StyledWrapper className="w-full">
<CodeEditor
collection={collection}
value={variables || ''}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
onEdit={onEdit}
mode="javascript"
onRun={onRun}

View File

@@ -97,7 +97,6 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
const script = getPropertyFromDraftOrRequest('request.script');
const assertions = getPropertyFromDraftOrRequest('request.assertions');
const tests = getPropertyFromDraftOrRequest('request.tests');
const docs = getPropertyFromDraftOrRequest('request.docs');
const requestVars = getPropertyFromDraftOrRequest('request.vars.req');
const responseVars = getPropertyFromDraftOrRequest('request.vars.res');
@@ -140,11 +139,10 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
</div>
<div className={getTabClassname('tests')} role="tab" onClick={() => selectTab('tests')}>
Tests
{tests && tests.length > 0 && <ContentIndicator />}
{tests && <ContentIndicator />}
</div>
<div className={getTabClassname('docs')} role="tab" onClick={() => selectTab('docs')}>
Docs
{docs && docs.length > 0 && <ContentIndicator />}
</div>
{focusedTab.requestPaneTab === 'body' ? (
<div className="flex flex-grow justify-end items-center">

View File

@@ -4,8 +4,6 @@ const StyledWrapper = styled.div`
div.CodeMirror {
background: ${(props) => props.theme.codemirror.bg};
border: solid 1px ${(props) => props.theme.codemirror.border};
font-family: ${(props) => (props.font ? props.font : 'default')};
font-size: ${(props) => (props.fontSize ? `${props.fontSize}px` : 'inherit')};
flex: 1 1 0;
}

View File

@@ -211,8 +211,6 @@ export default class QueryEditor extends React.Component {
<StyledWrapper
className="h-full w-full flex flex-col relative"
aria-label="Query Editor"
font={this.props.font}
fontSize={this.props.fontSize}
ref={(node) => {
this._node = node;
}}

View File

@@ -22,12 +22,14 @@ const Wrapper = styled.div`
}
td {
padding: 6px 10px;
}
}
td {
&:nth-child(1) {
padding: 0 0 0 8px;
&:nth-child(1) {
width: 30%;
}
&:nth-child(3) {
width: 70px;
}
}
}

View File

@@ -1,23 +1,20 @@
import React from 'react';
import get from 'lodash/get';
import cloneDeep from 'lodash/cloneDeep';
import InfoTip from 'components/InfoTip';
import Tooltip from 'components/Tooltip';
import { IconTrash } from '@tabler/icons';
import { useDispatch } from 'react-redux';
import { useTheme } from 'providers/Theme';
import {
addQueryParam,
updateQueryParam,
deleteQueryParam,
moveQueryParam,
updatePathParam
updatePathParam,
updateQueryParam
} from 'providers/ReduxStore/slices/collections';
import SingleLineEditor from 'components/SingleLineEditor';
import { saveRequest, sendRequest } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
import Table from 'components/Table/index';
import ReorderTable from 'components/ReorderTable';
const QueryParams = ({ item, collection }) => {
const dispatch = useDispatch();
@@ -103,81 +100,82 @@ const QueryParams = ({ item, collection }) => {
);
};
const handleParamDrag = ({ updateReorderedItem }) => {
dispatch(
moveQueryParam({
collectionUid: collection.uid,
itemUid: item.uid,
updateReorderedItem
})
);
};
return (
<StyledWrapper className="w-full flex flex-col absolute">
<StyledWrapper className="w-full flex flex-col">
<div className="flex-1 mt-2">
<div className="mb-1 title text-xs">Query</div>
<Table
headers={[
{ name: 'Name', accessor: 'name', width: '31%' },
{ name: 'Path', accessor: 'path', width: '56%' },
{ name: '', accessor: '', width: '13%' }
]}
>
<ReorderTable updateReorderedItem={handleParamDrag}>
<div className="mb-2 title text-xs">Query</div>
<table>
<thead>
<tr>
<td>Name</td>
<td>Value</td>
<td></td>
</tr>
</thead>
<tbody>
{queryParams && queryParams.length
? queryParams.map((param, index) => (
<tr key={param.uid} data-uid={param.uid}>
<td className="flex relative">
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={param.name}
className="mousetrap"
onChange={(e) => handleQueryParamChange(e, param, 'name')}
/>
</td>
<td>
<SingleLineEditor
value={param.value}
theme={storedTheme}
onSave={onSave}
onChange={(newValue) => handleQueryParamChange({ target: { value: newValue } }, param, 'value')}
onRun={handleRun}
collection={collection}
variablesAutocomplete={true}
/>
</td>
<td>
<div className="flex items-center">
? queryParams.map((param, index) => {
return (
<tr key={param.uid}>
<td>
<input
type="checkbox"
checked={param.enabled}
tabIndex="-1"
className="mr-3 mousetrap"
onChange={(e) => handleQueryParamChange(e, param, 'enabled')}
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={param.name}
className="mousetrap"
onChange={(e) => handleQueryParamChange(e, param, 'name')}
/>
<button tabIndex="-1" onClick={() => handleRemoveQueryParam(param)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>
</td>
</tr>
))
</td>
<td>
<SingleLineEditor
value={param.value}
theme={storedTheme}
onSave={onSave}
onChange={(newValue) =>
handleQueryParamChange(
{
target: {
value: newValue
}
},
param,
'value'
)
}
onRun={handleRun}
collection={collection}
item={item}
/>
</td>
<td>
<div className="flex items-center">
<input
type="checkbox"
checked={param.enabled}
tabIndex="-1"
className="mr-3 mousetrap"
onChange={(e) => handleQueryParamChange(e, param, 'enabled')}
/>
<button tabIndex="-1" onClick={() => handleRemoveQueryParam(param)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>
</td>
</tr>
);
})
: null}
</ReorderTable>
</Table>
</tbody>
</table>
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={handleAddQueryParam}>
+&nbsp;<span>Add Param</span>
</button>
<div className="mb-2 title text-xs flex items-stretch">
<span>Path</span>
<InfoTip
<Tooltip
text={`
<div>
Path variables are automatically added whenever the
@@ -188,7 +186,7 @@ const QueryParams = ({ item, collection }) => {
</code>
</div>
`}
infotipId="path-param-InfoTip"
tooltipId="path-param-tooltip"
/>
</div>
<table>

View File

@@ -33,18 +33,18 @@ const Wrapper = styled.div`
top: 1px;
}
.infotip {
.tooltip {
position: relative;
display: inline-block;
cursor: pointer;
}
.infotip:hover .infotiptext {
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
.infotiptext {
.tooltiptext {
visibility: hidden;
width: auto;
background-color: ${(props) => props.theme.requestTabs.active.bg};
@@ -62,7 +62,7 @@ const Wrapper = styled.div`
white-space: nowrap;
}
.infotiptext::after {
.tooltiptext::after {
content: '';
position: absolute;
top: 100%;

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect } from 'react';
import get from 'lodash/get';
import { useDispatch } from 'react-redux';
import { requestUrlChanged, updateRequestMethod } from 'providers/ReduxStore/slices/collections';
@@ -17,7 +17,6 @@ const QueryUrl = ({ item, collection, handleRun }) => {
const url = item.draft ? get(item, 'draft.request.url', '') : get(item, 'request.url', '');
const isMac = isMacOS();
const saveShortcut = isMac ? 'Cmd + S' : 'Ctrl + S';
const editorRef = useRef(null);
const [methodSelectorWidth, setMethodSelectorWidth] = useState(90);
@@ -26,33 +25,18 @@ const QueryUrl = ({ item, collection, handleRun }) => {
setMethodSelectorWidth(el.offsetWidth);
}, [method]);
const onSave = (finalValue) => {
const onSave = () => {
dispatch(saveRequest(item.uid, collection.uid));
};
const onUrlChange = (value) => {
if (!editorRef.current?.editor) return;
const editor = editorRef.current.editor;
const cursor = editor.getCursor();
const finalUrl = value?.trim() ?? value;
dispatch(
requestUrlChanged({
itemUid: item.uid,
collectionUid: collection.uid,
url: finalUrl
url: value && typeof value === 'string' ? value.trim() : value
})
);
// Restore cursor position only if URL was trimmed
if (finalUrl !== value) {
setTimeout(() => {
if (editor) {
editor.setCursor(cursor);
}
}, 0);
}
};
const onMethodSelect = (verb) => {
@@ -79,9 +63,8 @@ const QueryUrl = ({ item, collection, handleRun }) => {
}}
>
<SingleLineEditor
ref={editorRef}
value={url}
onSave={(finalValue) => onSave(finalValue)}
onSave={onSave}
theme={storedTheme}
onChange={(newValue) => onUrlChange(newValue)}
onRun={handleRun}
@@ -91,7 +74,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
/>
<div className="flex items-center h-full mr-2 cursor-pointer" id="send-request" onClick={handleRun}>
<div
className="infotip mr-3"
className="tooltip mx-3"
onClick={(e) => {
e.stopPropagation();
if (!item.draft) return;
@@ -104,7 +87,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
size={22}
className={`${item.draft ? 'cursor-pointer' : 'cursor-default'}`}
/>
<span className="infotiptext text-xs">
<span className="tooltiptext text-xs">
Save <span className="shortcut">({saveShortcut})</span>
</span>
</div>

View File

@@ -50,7 +50,6 @@ const RequestBody = ({ item, collection }) => {
collection={collection}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
value={bodyContent[bodyMode] || ''}
onEdit={onEdit}
onRun={onRun}

View File

@@ -47,7 +47,6 @@ const Script = ({ item, collection }) => {
value={requestScript || ''}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
onEdit={onRequestScriptEdit}
mode="javascript"
onRun={onRun}
@@ -61,7 +60,6 @@ const Script = ({ item, collection }) => {
value={responseScript || ''}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
onEdit={onResponseScriptEdit}
mode="javascript"
onRun={onRun}

View File

@@ -34,7 +34,6 @@ const Tests = ({ item, collection }) => {
value={tests || ''}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
onEdit={onEdit}
mode="javascript"
onRun={onRun}

View File

@@ -6,7 +6,7 @@ import { useTheme } from 'providers/Theme';
import { addVar, updateVar, deleteVar } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import SingleLineEditor from 'components/SingleLineEditor';
import InfoTip from 'components/InfoTip';
import Tooltip from 'components/Tooltip';
import StyledWrapper from './StyledWrapper';
import toast from 'react-hot-toast';
import { variableNameRegex } from 'utils/common/regex';
@@ -83,14 +83,14 @@ const VarsTable = ({ item, collection, vars, varType }) => {
<td>
<div className="flex items-center">
<span>Value</span>
<InfoTip text="You can write any valid JS Template Literal here" infotipId="request-var" />
<Tooltip text="You can write any valid JS Template Literal here" tooltipId="request-var" />
</div>
</td>
) : (
<td>
<div className="flex items-center">
<span>Expr</span>
<InfoTip text="You can write any valid JS expression here" infotipId="response-var" />
<Tooltip text="You can write any valid JS expression here" tooltipId="response-var" />
</div>
</td>
)}
@@ -132,7 +132,6 @@ const VarsTable = ({ item, collection, vars, varType }) => {
}
onRun={handleRun}
collection={collection}
item={item}
/>
</td>
<td>

View File

@@ -2,4 +2,4 @@ import styled from 'styled-components';
const StyledWrapper = styled.div``;
export default StyledWrapper;
export default StyledWrapper;

View File

@@ -4,7 +4,6 @@ import { IconFiles, IconRun, IconEye, IconSettings } from '@tabler/icons';
import EnvironmentSelector from 'components/Environments/EnvironmentSelector';
import { addTab } from 'providers/ReduxStore/slices/tabs';
import { useDispatch } from 'react-redux';
import ToolHint from 'components/ToolHint';
import StyledWrapper from './StyledWrapper';
import JsSandboxMode from 'components/SecuritySettings/JsSandboxMode';
@@ -52,20 +51,14 @@ const CollectionToolBar = ({ collection }) => {
<span className="mr-2">
<JsSandboxMode collection={collection} />
</span>
<span className="mr-3">
<ToolHint text="Runner" toolhintId="RunnnerToolhintId" place='bottom'>
<IconRun className="cursor-pointer" size={18} strokeWidth={1.5} onClick={handleRun} />
</ToolHint>
<span className="mr-2">
<IconRun className="cursor-pointer" size={20} strokeWidth={1.5} onClick={handleRun} />
</span>
<span className="mr-3">
<ToolHint text="Variables" toolhintId="VariablesToolhintId">
<IconEye className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewVariables} />
</ToolHint>
<IconEye className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewVariables} />
</span>
<span className="mr-3">
<ToolHint text="Collection Settings" toolhintId="CollectionSettingsToolhintId">
<IconSettings className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewCollectionSettings} />
</ToolHint>
<IconSettings className="cursor-pointer" size={18} strokeWidth={1.5} onClick={viewCollectionSettings} />
</span>
<EnvironmentSelector collection={collection} />
</div>

View File

@@ -1,10 +0,0 @@
const CloseTabIcon = () => (
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
<path
fill="currentColor"
d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"
></path>
</svg>
);
export default CloseTabIcon;

View File

@@ -1,15 +0,0 @@
const DraftTabIcon = () => (
<svg
focusable="false"
xmlns="http://www.w3.org/2000/svg"
width="8"
height="16"
fill="#cc7b1b"
className="has-changes-icon"
viewBox="0 0 8 8"
>
<circle cx="4" cy="4" r="3" />
</svg>
);
export default DraftTabIcon;

View File

@@ -1,6 +1,5 @@
import React, { useState, useEffect } from 'react';
import { IconAlertTriangle } from '@tabler/icons';
import CloseTabIcon from './CloseTabIcon';
const RequestTabNotFound = ({ handleCloseClick }) => {
const [showErrorMessage, setShowErrorMessage] = useState(false);
@@ -29,7 +28,12 @@ const RequestTabNotFound = ({ handleCloseClick }) => {
) : null}
</div>
<div className="flex px-2 close-icon-container" onClick={(e) => handleCloseClick(e)}>
<CloseTabIcon />
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
<path
fill="currentColor"
d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"
></path>
</svg>
</div>
</>
);

View File

@@ -1,5 +1,4 @@
import React from 'react';
import CloseTabIcon from './CloseTabIcon';
import { IconVariable, IconSettings, IconRun, IconFolder, IconShieldLock } from '@tabler/icons';
const SpecialTab = ({ handleCloseClick, type, tabName }) => {
@@ -52,7 +51,12 @@ const SpecialTab = ({ handleCloseClick, type, tabName }) => {
<>
<div className="flex items-center tab-label pl-2">{getTabInfo(type, tabName)}</div>
<div className="flex px-2 close-icon-container" onClick={(e) => handleCloseClick(e)}>
<CloseTabIcon />
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
<path
fill="currentColor"
d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"
></path>
</svg>
</div>
</>
);

View File

@@ -15,8 +15,6 @@ import StyledWrapper from './StyledWrapper';
import Dropdown from 'components/Dropdown';
import CloneCollectionItem from 'components/Sidebar/Collections/Collection/CollectionItem/CloneCollectionItem/index';
import NewRequest from 'components/Sidebar/NewRequest/index';
import CloseTabIcon from './CloseTabIcon';
import DraftTabIcon from './DraftTabIcon';
const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUid }) => {
const dispatch = useDispatch();
@@ -51,10 +49,9 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
const handleMouseUp = (e) => {
if (e.button === 1) {
e.preventDefault();
e.stopPropagation();
e.preventDefault();
// Close the tab
dispatch(
closeTabs({
tabUids: [tab.uid]
@@ -71,10 +68,7 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
const folder = folderUid ? findItemInCollection(collection, folderUid) : null;
if (['collection-settings', 'folder-settings', 'variables', 'collection-runner', 'security-settings'].includes(tab.type)) {
return (
<StyledWrapper
className="flex items-center justify-between tab-container px-1"
onMouseUp={handleMouseUp} // Add middle-click behavior here
>
<StyledWrapper className="flex items-center justify-between tab-container px-1">
{tab.type === 'folder-settings' ? (
<SpecialTab handleCloseClick={handleCloseClick} type={tab.type} tabName={folder?.name} />
) : (
@@ -88,17 +82,7 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
if (!item) {
return (
<StyledWrapper
className="flex items-center justify-between tab-container px-1"
onMouseUp={(e) => {
if (e.button === 1) {
e.preventDefault();
e.stopPropagation();
dispatch(closeTabs({ tabUids: [tab.uid] }));
}
}}
>
<StyledWrapper className="flex items-center justify-between tab-container px-1">
<RequestTabNotFound handleCloseClick={handleCloseClick} />
</StyledWrapper>
);
@@ -182,9 +166,24 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
}}
>
{!item.draft ? (
<CloseTabIcon />
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
<path
fill="currentColor"
d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"
></path>
</svg>
) : (
<DraftTabIcon />
<svg
focusable="false"
xmlns="http://www.w3.org/2000/svg"
width="8"
height="16"
fill="#cc7b1b"
className="has-changes-icon"
viewBox="0 0 8 8"
>
<circle cx="4" cy="4" r="3" />
</svg>
)}
</div>
</StyledWrapper>

View File

@@ -2,7 +2,7 @@ import { IconFilter, IconX } from '@tabler/icons';
import React, { useMemo } from 'react';
import { useRef } from 'react';
import { useState } from 'react';
import { Tooltip as ReactInfotip } from 'react-tooltip';
import { Tooltip as ReactTooltip } from 'react-tooltip';
const QueryResultFilter = ({ filter, onChange, mode }) => {
const inputRef = useRef(null);
@@ -19,7 +19,7 @@ const QueryResultFilter = ({ filter, onChange, mode }) => {
}
};
const infotipText = useMemo(() => {
const tooltipText = useMemo(() => {
if (mode.includes('json')) {
return 'Filter with JSONPath';
}
@@ -49,7 +49,7 @@ const QueryResultFilter = ({ filter, onChange, mode }) => {
'response-filter absolute bottom-2 w-full justify-end right-0 flex flex-row items-center gap-2 py-4 px-2 pointer-events-none'
}
>
{infotipText && !isExpanded && <ReactInfotip anchorId={'request-filter-icon'} html={infotipText} />}
{tooltipText && !isExpanded && <ReactTooltip anchorId={'request-filter-icon'} html={tooltipText} />}
<input
ref={inputRef}
type="text"

View File

@@ -29,7 +29,7 @@ const QueryResultPreview = ({
setNumPages(numPages);
}
// Fail safe, so we don't render anything with an invalid tab
if (!allowedPreviewModes.find((previewMode) => previewMode?.uid == previewTab?.uid)) {
if (!allowedPreviewModes.includes(previewTab)) {
return null;
}
@@ -40,7 +40,7 @@ const QueryResultPreview = ({
dispatch(sendRequest(item, collection.uid));
};
switch (previewTab?.mode) {
switch (previewTab) {
case 'preview-web': {
const webViewSrc = data.replace('<head>', `<head><base href="${item.requestSent?.url || ''}">`);
return (
@@ -81,7 +81,6 @@ const QueryResultPreview = ({
<CodeEditor
collection={collection}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
theme={displayedTheme}
onRun={onRun}
value={formattedData}

View File

@@ -12,35 +12,18 @@ import { useState } from 'react';
import { useMemo } from 'react';
import { useEffect } from 'react';
import { useTheme } from 'providers/Theme/index';
import { uuid } from 'utils/common/index';
const formatResponse = (data, mode, filter) => {
if (data === undefined) {
return '';
}
if (data === null) {
return data;
}
if (mode.includes('json')) {
let isValidJSON = false;
try {
isValidJSON = typeof JSON.parse(JSON.stringify(data)) === 'object';
} catch (error) {
console.log('Error parsing JSON: ', error.message);
}
if (!isValidJSON && typeof data === 'string') {
return data;
}
if (filter) {
try {
data = JSONPath({ path: filter, json: data });
} catch (e) {
console.warn('Could not apply JSONPath filter:', e.message);
console.warn('Could not filter with JSONPath.', e.message);
}
}
@@ -52,10 +35,15 @@ const formatResponse = (data, mode, filter) => {
if (typeof parsed === 'string') {
return parsed;
}
return safeStringifyJSON(parsed, true);
}
return data;
if (typeof data === 'string') {
return data;
}
return safeStringifyJSON(data);
};
const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEventListener, headers, error }) => {
@@ -71,18 +59,18 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
const allowedPreviewModes = useMemo(() => {
// Always show raw
const allowedPreviewModes = [{ mode: 'raw', name: 'Raw', uid: uuid() }];
const allowedPreviewModes = ['raw'];
if (mode.includes('html') && typeof data === 'string') {
allowedPreviewModes.unshift({ mode: 'preview-web', name: 'Web', uid: uuid() });
allowedPreviewModes.unshift('preview-web');
} else if (mode.includes('image')) {
allowedPreviewModes.unshift({ mode: 'preview-image', name: 'Image', uid: uuid() });
allowedPreviewModes.unshift('preview-image');
} else if (contentType.includes('pdf')) {
allowedPreviewModes.unshift({ mode: 'preview-pdf', name: 'PDF', uid: uuid() });
allowedPreviewModes.unshift('preview-pdf');
} else if (contentType.includes('audio')) {
allowedPreviewModes.unshift({ mode: 'preview-audio', name: 'Audio', uid: uuid() });
allowedPreviewModes.unshift('preview-audio');
} else if (contentType.includes('video')) {
allowedPreviewModes.unshift({ mode: 'preview-video', name: 'Video', uid: uuid() });
allowedPreviewModes.unshift('preview-video');
}
return allowedPreviewModes;
@@ -91,7 +79,7 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
const [previewTab, setPreviewTab] = useState(allowedPreviewModes[0]);
// Ensure the active Tab is always allowed
useEffect(() => {
if (!allowedPreviewModes.find((previewMode) => previewMode?.uid == previewTab?.uid)) {
if (!allowedPreviewModes.includes(previewTab)) {
setPreviewTab(allowedPreviewModes[0]);
}
}, [previewTab, allowedPreviewModes]);
@@ -103,15 +91,12 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
return allowedPreviewModes.map((previewMode) => (
<div
className={classnames(
'select-none capitalize',
previewMode?.uid === previewTab?.uid ? 'active' : 'cursor-pointer'
)}
className={classnames('select-none capitalize', previewMode === previewTab ? 'active' : 'cursor-pointer')}
role="tab"
onClick={() => setPreviewTab(previewMode)}
key={previewMode?.uid}
key={previewMode}
>
{previewMode?.name}
{previewMode.replace(/-(.*)/, ' ')}
</div>
));
}, [allowedPreviewModes, previewTab]);

View File

@@ -17,8 +17,6 @@ const Timeline = ({ request, response }) => {
});
});
let requestData = typeof request?.data === "string" ? request?.data : safeStringifyJSON(request?.data, true);
return (
<StyledWrapper className="pb-4 w-full">
<div>
@@ -33,10 +31,10 @@ const Timeline = ({ request, response }) => {
);
})}
{requestData ? (
{request.data ? (
<pre className="line request">
<span className="arrow">{'>'}</span> data{' '}
<pre className="text-sm flex flex-wrap whitespace-break-spaces">{requestData}</pre>
<pre className="text-sm flex flex-wrap whitespace-break-spaces">{request.data}</pre>
</pre>
) : null}
</div>

View File

@@ -5,7 +5,7 @@ import * as Yup from 'yup';
import { browseDirectory } from 'providers/ReduxStore/slices/collections/actions';
import { cloneCollection } from 'providers/ReduxStore/slices/collections/actions';
import toast from 'react-hot-toast';
import InfoTip from 'components/InfoTip';
import Tooltip from 'components/Tooltip';
import Modal from 'components/Modal';
const CloneCollection = ({ onClose, collection }) => {
@@ -44,7 +44,7 @@ const CloneCollection = ({ onClose, collection }) => {
toast.success('Collection created');
onClose();
})
.catch((e) => toast.error('An error occurred while creating the collection - ' + e));
.catch(() => toast.error('An error occurred while creating the collection'));
}
});
@@ -126,9 +126,9 @@ const CloneCollection = ({ onClose, collection }) => {
<label htmlFor="collection-folder-name" className="flex items-center mt-3">
<span className="font-semibold">Folder Name</span>
<InfoTip
<Tooltip
text="This folder will be created under the selected location"
infotipId="collection-folder-name-infotip"
tooltipId="collection-folder-name-tooltip"
/>
</label>
<input

View File

@@ -53,7 +53,6 @@ const CodeView = ({ language, item }) => {
collection={collection}
value={snippet}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
theme={displayedTheme}
mode={lang}
/>

View File

@@ -8,9 +8,8 @@ const StyledWrapper = styled.div`
.generate-code-sidebar {
background-color: ${(props) => props.theme.collection.environment.settings.sidebar.bg};
border-right: solid 1px ${(props) => props.theme.collection.environment.settings.sidebar.borderRight};
max-height: 80vh;
min-height: 400px;
height: 100%;
overflow-y: auto;
}
.generate-code-item {

View File

@@ -6,11 +6,56 @@ import { isValidUrl } from 'utils/url';
import { find, get } from 'lodash';
import { findEnvironmentInCollection } from 'utils/collections';
import { interpolateUrl, interpolateUrlPathParams } from 'utils/url/index';
import { getLanguages } from 'utils/codegenerator/targets';
const languages = [
{
name: 'HTTP',
target: 'http',
client: 'http1.1'
},
{
name: 'JavaScript-Fetch',
target: 'javascript',
client: 'fetch'
},
{
name: 'Javascript-jQuery',
target: 'javascript',
client: 'jquery'
},
{
name: 'Javascript-axios',
target: 'javascript',
client: 'axios'
},
{
name: 'Python-Python3',
target: 'python',
client: 'python3'
},
{
name: 'Python-Requests',
target: 'python',
client: 'requests'
},
{
name: 'PHP',
target: 'php',
client: 'curl'
},
{
name: 'Shell-curl',
target: 'shell',
client: 'curl'
},
{
name: 'Shell-httpie',
target: 'shell',
client: 'httpie'
}
];
const GenerateCodeItem = ({ collection, item, onClose }) => {
const languages = getLanguages();
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
let envVars = {};
if (environment) {

View File

@@ -23,53 +23,43 @@ const RunCollectionItem = ({ collection, item, onClose }) => {
onClose();
};
const getRequestsCount = (items) => {
const requestTypes = ['http-request', 'graphql-request']
return items.filter(req => requestTypes.includes(req.type)).length;
}
const runLength = item ? getRequestsCount(item.items) : get(collection, 'items.length', 0);
const flattenedItems = flattenItems(item ? item.items : collection.items);
const recursiveRunLength = getRequestsCount(flattenedItems);
const runLength = item ? get(item, 'items.length', 0) : get(collection, 'items.length', 0);
const items = flattenItems(item ? item.items : collection.items);
const requestItems = items.filter((item) => item.type !== 'folder');
const recursiveRunLength = requestItems.length;
return (
<StyledWrapper>
<Modal size="md" title="Collection Runner" hideFooter={true} handleCancel={onClose}>
{!runLength && !recursiveRunLength ? (
<div className="mb-8">No request found in this folder.</div>
) : (
<div>
<div className="mb-1">
<span className="font-medium">Run</span>
<span className="ml-1 text-xs">({runLength} requests)</span>
</div>
<div className="mb-8">This will only run the requests in this folder.</div>
<div className="mb-1">
<span className="font-medium">Run</span>
<span className="ml-1 text-xs">({runLength} requests)</span>
</div>
<div className="mb-8">This will only run the requests in this folder.</div>
<div className="mb-1">
<span className="font-medium">Recursive Run</span>
<span className="ml-1 text-xs">({recursiveRunLength} requests)</span>
</div>
<div className="mb-8">This will run all the requests in this folder and all its subfolders.</div>
<div className="mb-1">
<span className="font-medium">Recursive Run</span>
<span className="ml-1 text-xs">({recursiveRunLength} requests)</span>
</div>
<div className="mb-8">This will run all the requests in this folder and all its subfolders.</div>
<div className="flex justify-end bruno-modal-footer">
<span className="mr-3">
<button type="button" onClick={onClose} className="btn btn-md btn-close">
Cancel
</button>
</span>
<span>
<button type="submit" disabled={!recursiveRunLength} className="submit btn btn-md btn-secondary mr-3" onClick={() => onSubmit(true)}>
Recursive Run
</button>
</span>
<span>
<button type="submit" disabled={!runLength} className="submit btn btn-md btn-secondary" onClick={() => onSubmit(false)}>
Run
</button>
</span>
</div>
</div>
)}
<div className="flex justify-end bruno-modal-footer">
<span className="mr-3">
<button type="button" onClick={onClose} className="btn btn-md btn-close">
Cancel
</button>
</span>
<span>
<button type="submit" className="submit btn btn-md btn-secondary mr-3" onClick={() => onSubmit(true)}>
Recursive Run
</button>
</span>
<span>
<button type="submit" className="submit btn btn-md btn-secondary" onClick={() => onSubmit(false)}>
Run
</button>
</span>
</div>
</Modal>
</StyledWrapper>
);

View File

@@ -65,7 +65,7 @@ const Wrapper = styled.div`
div.dropdown-item.delete-item {
color: ${(props) => props.theme.colors.danger};
&:hover {
background-color: ${(props) => props.theme.colors.bg.danger} !important;
background-color: ${(props) => props.theme.colors.bg.danger};
color: white;
}
}

View File

@@ -5,7 +5,7 @@ import * as Yup from 'yup';
import { browseDirectory } from 'providers/ReduxStore/slices/collections/actions';
import { createCollection } from 'providers/ReduxStore/slices/collections/actions';
import toast from 'react-hot-toast';
import InfoTip from 'components/InfoTip';
import Tooltip from 'components/Tooltip';
import Modal from 'components/Modal';
const CreateCollection = ({ onClose }) => {
@@ -37,7 +37,7 @@ const CreateCollection = ({ onClose }) => {
toast.success('Collection created');
onClose();
})
.catch((e) => toast.error('An error occurred while creating the collection - ' + e));
.catch(() => toast.error('An error occurred while creating the collection'));
}
});
@@ -119,9 +119,9 @@ const CreateCollection = ({ onClose }) => {
<label htmlFor="collection-folder-name" className="flex items-center mt-3">
<span className="font-semibold">Folder Name</span>
<InfoTip
<Tooltip
text="This folder will be created under the selected location"
infotipId="collection-folder-name-infotip"
tooltipId="collection-folder-name-tooltip"
/>
</label>
<input

View File

@@ -162,7 +162,16 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
return (
<StyledWrapper>
<Modal size="md" title="New Request" confirmText="Create" handleConfirm={onSubmit} handleCancel={onClose}>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
<form
className="bruno-form"
onSubmit={formik.handleSubmit}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
formik.handleSubmit();
}
}}
>
<div>
<label htmlFor="requestName" className="block font-semibold">
Type

View File

@@ -4,7 +4,6 @@ import StyledWrapper from './StyledWrapper';
import GitHubButton from 'react-github-btn';
import Preferences from 'components/Preferences';
import Cookies from 'components/Cookies';
import ToolHint from 'components/ToolHint';
import GoldenEdition from './GoldenEdition';
import { useState, useEffect } from 'react';
@@ -96,30 +95,28 @@ const Sidebar = () => {
<div className="footer flex px-1 py-2 absolute bottom-0 left-0 right-0 items-center select-none">
<div className="flex items-center ml-1 text-xs ">
<a className="mr-2 cursor-pointer" onClick={() => dispatch(showPreferences(true))}>
<ToolHint text="Preferences" toolhintId="Preferences" effect='float' place='top-start' offset={8}>
<IconSettings size={18} strokeWidth={1.5} />
</ToolHint>
<a
title="Preferences"
className="mr-2 cursor-pointer hover:text-gray-700"
onClick={() => dispatch(showPreferences(true))}
>
<IconSettings size={18} strokeWidth={1.5} />
</a>
<a
className="mr-2 cursor-pointer"
title="Cookies"
className="mr-2 cursor-pointer hover:text-gray-700"
onClick={() => setCookiesOpen(true)}
>
<ToolHint text="Cookies" toolhintId="Cookies" offset={8}>
<IconCookie size={18} strokeWidth={1.5} />
</ToolHint>
<IconCookie size={18} strokeWidth={1.5} />
</a>
<a
className="mr-2 cursor-pointer"
title="Golden Edition"
className="mr-2 cursor-pointer hover:text-gray-700"
onClick={() => setGoldenEditonOpen(true)}
>
<ToolHint text="Golden Edition" toolhintId="Golden Edition" offset={8} >
<IconHeart size={18} strokeWidth={1.5} />
</ToolHint>
</a>
<a>
<Notifications />
<IconHeart size={18} strokeWidth={1.5} />
</a>
<Notifications />
</div>
<div className="pl-1" style={{ position: 'relative', top: '3px' }}>
{/* This will get moved to home page */}
@@ -129,10 +126,10 @@ const Sidebar = () => {
data-show-count="true"
aria-label="Star usebruno/bruno on GitHub"
>
Star
Star
</GitHubButton> */}
</div>
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.29.1</div>
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.26.2</div>
</div>
</div>
</div>
@@ -140,7 +137,7 @@ const Sidebar = () => {
<div className="absolute drag-sidebar h-full" onMouseDown={handleDragbarMouseDown}>
<div className="drag-request-border" />
</div>
</StyledWrapper >
</StyledWrapper>
);
};

View File

@@ -1,64 +0,0 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
table {
width: 100%;
display: grid;
overflow-y: hidden;
overflow-x: auto;
// for icon hover
position: inherit;
left: -4px;
padding-left: 4px;
padding-right: 4px;
grid-template-columns: ${({ columns }) =>
columns?.[0]?.width
? columns.map((col) => `${col?.width}`).join(' ')
: columns.map((col) => `${100 / columns.length}%`).join(' ')};
}
table thead,
table tbody,
table tr {
display: contents;
}
table th {
position: relative;
border-bottom: 1px solid ${(props) => props.theme.collection.environment.settings.gridBorder}77;
}
table tr td {
padding: 0.5rem;
text-align: left;
border-top: 1px solid ${(props) => props.theme.collection.environment.settings.gridBorder}77;
border-right: 1px solid ${(props) => props.theme.collection.environment.settings.gridBorder}77;
}
tr {
transition: transform 0.2s ease-in-out;
}
tr.dragging {
opacity: 0.5;
}
tr.hovered {
transform: translateY(10px); /* Adjust the value as needed for the animation effect */
}
table tr th {
padding: 0.5rem;
text-align: left;
border-top: 1px solid ${(props) => props.theme.collection.environment.settings.gridBorder}77;
border-right: 1px solid ${(props) => props.theme.collection.environment.settings.gridBorder}77;
&:nth-child(1) {
border-left: 1px solid ${(props) => props.theme.collection.environment.settings.gridBorder}77;
}
}
`;
export default StyledWrapper;

View File

@@ -1,110 +0,0 @@
import { useState, useRef, useEffect, useCallback } from 'react';
import StyledWrapper from './StyledWrapper';
const Table = ({ minColumnWidth = 1, headers = [], children }) => {
const [activeColumnIndex, setActiveColumnIndex] = useState(null);
const tableRef = useRef(null);
const columns = headers?.map((item) => ({
...item,
ref: useRef()
}));
const updateDivHeights = () => {
if (tableRef.current) {
const height = tableRef.current.offsetHeight;
columns.forEach((col) => {
if (col.ref.current) {
col.ref.current.querySelector('.resizer').style.height = `${height}px`;
}
});
}
};
useEffect(() => {
updateDivHeights();
window.addEventListener('resize', updateDivHeights);
return () => {
window.removeEventListener('resize', updateDivHeights);
};
}, [columns]);
useEffect(() => {
if (tableRef.current) {
const observer = new MutationObserver(updateDivHeights);
observer.observe(tableRef.current, { childList: true, subtree: true });
return () => {
observer.disconnect();
};
}
}, [columns]);
const handleMouseDown = (index) => (e) => {
setActiveColumnIndex(index);
};
const handleMouseMove = useCallback(
(e) => {
const gridColumns = columns.map((col, i) => {
if (i === activeColumnIndex) {
const width = e.clientX - col.ref?.current?.getBoundingClientRect()?.left;
if (width >= minColumnWidth) {
return `${width}px`;
}
}
return `${col.ref.current.offsetWidth}px`;
});
tableRef.current.style.gridTemplateColumns = `${gridColumns.join(' ')}`;
},
[activeColumnIndex, columns, minColumnWidth]
);
const handleMouseUp = useCallback(() => {
setActiveColumnIndex(null);
removeListeners();
}, [removeListeners]);
const removeListeners = useCallback(() => {
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseup', removeListeners);
}, [handleMouseMove]);
useEffect(() => {
if (activeColumnIndex !== null) {
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleMouseUp);
}
return () => {
removeListeners();
};
}, [activeColumnIndex, handleMouseMove, handleMouseUp, removeListeners]);
return (
<StyledWrapper columns={columns}>
<div className="relative">
<table ref={tableRef} className="px-4 inherit left-[4px]">
<thead>
<tr>
{columns.map(({ ref, name }, i) => (
<th ref={ref} key={name} title={name}>
<span>{name}</span>
<div
className="resizer absolute cursor-col-resize w-[4px] right-[-2px] top-0 z-10 opacity-50 hover:bg-blue-500 active:bg-blue-500"
onMouseDown={handleMouseDown(i)}
></div>
</th>
))}
</tr>
</thead>
{children}
</table>
</div>
</StyledWrapper>
);
};
export default Table;

View File

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

View File

@@ -1,47 +0,0 @@
import React from 'react';
import { Tooltip as ReactToolHint } from 'react-tooltip';
import StyledWrapper from './StyledWrapper';
import { useTheme } from 'providers/Theme';
const ToolHint = ({
text,
toolhintId,
children,
tooltipStyle = {},
place = 'top',
offset,
theme = null
}) => {
const { theme: contextTheme } = useTheme();
const appliedTheme = theme || contextTheme;
const toolhintBackgroundColor = appliedTheme?.sidebar.badge.bg || 'black';
const toolhintTextColor = appliedTheme?.text || 'white';
const combinedToolhintStyle = {
...tooltipStyle,
fontSize: '0.75rem',
padding: '0.25rem 0.5rem',
backgroundColor: toolhintBackgroundColor,
color: toolhintTextColor
};
return (
<>
<span id={toolhintId}>{children}</span>
<StyledWrapper theme={appliedTheme}>
<ReactToolHint
anchorId={toolhintId}
html={text}
className="toolhint"
offset={offset}
place={place}
noArrow={true}
style={combinedToolhintStyle}
/>
</StyledWrapper>
</>
);
};
export default ToolHint;

View File

@@ -1,12 +1,12 @@
import React from 'react';
import { Tooltip as ReactInfoTip } from 'react-tooltip';
import { Tooltip as ReactTooltip } from 'react-tooltip';
const InfoTip = ({ text, infotipId }) => {
const Tooltip = ({ text, tooltipId }) => {
return (
<>
<svg
tabIndex="-1"
id={infotipId}
id={tooltipId}
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
@@ -17,9 +17,9 @@ const InfoTip = ({ text, infotipId }) => {
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
<path d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z" />
</svg>
<ReactInfoTip anchorId={infotipId} html={text} />
<ReactTooltip anchorId={tooltipId} html={text} />
</>
);
};
export default InfoTip;
export default Tooltip;

View File

@@ -1,43 +0,0 @@
import { useEffect, useRef } from 'react';
const useFocusTrap = (modalRef) => {
const firstFocusableElementRef = useRef(null);
const lastFocusableElementRef = useRef(null);
useEffect(() => {
const modalElement = modalRef.current;
if (!modalElement) return;
const focusableElements = modalElement.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
if (focusableElements.length === 0) return;
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
firstFocusableElementRef.current = firstElement;
lastFocusableElementRef.current = lastElement;
const handleKeyDown = (event) => {
if (event.key === 'Tab') {
if (event.shiftKey && document.activeElement === firstElement) {
event.preventDefault();
lastElement.focus();
} else if (!event.shiftKey && document.activeElement === lastElement) {
event.preventDefault();
firstElement.focus();
}
}
};
modalElement.addEventListener('keydown', handleKeyDown);
return () => {
modalElement.removeEventListener('keydown', handleKeyDown);
};
}, [modalRef]);
};
export default useFocusTrap;

View File

@@ -1,10 +1,5 @@
import { useEffect } from 'react';
import {
showPreferences,
updateCookies,
updatePreferences,
updateSystemProxyEnvVariables
} from 'providers/ReduxStore/slices/app';
import { showPreferences, updateCookies, updatePreferences } from 'providers/ReduxStore/slices/app';
import {
brunoConfigUpdateEvent,
collectionAddDirectoryEvent,
@@ -141,10 +136,6 @@ const useIpcEvents = () => {
dispatch(updatePreferences(val));
});
const removeSystemProxyEnvUpdatesListener = ipcRenderer.on('main:load-system-proxy-env', (val) => {
dispatch(updateSystemProxyEnvVariables(val));
});
const removeCookieUpdateListener = ipcRenderer.on('main:cookies-update', (val) => {
dispatch(updateCookies(val));
});
@@ -164,7 +155,6 @@ const useIpcEvents = () => {
removeShowPreferencesListener();
removePreferencesUpdatesListener();
removeCookieUpdateListener();
removeSystemProxyEnvUpdatesListener();
};
}, [isElectron]);
};

View File

@@ -60,7 +60,7 @@ const trackStart = () => {
event: 'start',
properties: {
os: platformLib.os.family,
version: '1.29.1'
version: '1.26.2'
}
});
};

View File

@@ -9,7 +9,7 @@ import NetworkError from 'components/ResponsePane/NetworkError';
import NewRequest from 'components/Sidebar/NewRequest';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import { findCollectionByUid, findItemInCollection } from 'utils/collections';
import { closeTabs, switchTab } from 'providers/ReduxStore/slices/tabs';
import { closeTabs } from 'providers/ReduxStore/slices/tabs';
export const HotkeysContext = React.createContext();
@@ -154,41 +154,7 @@ export const HotkeysProvider = (props) => {
};
}, [activeTabUid]);
// Switch to the previous tab
useEffect(() => {
Mousetrap.bind(['command+pageup', 'ctrl+pageup'], (e) => {
dispatch(
switchTab({
direction: 'pageup'
})
);
return false; // this stops the event bubbling
});
return () => {
Mousetrap.unbind(['command+pageup', 'ctrl+pageup']);
};
}, [dispatch]);
// Switch to the next tab
useEffect(() => {
Mousetrap.bind(['command+pagedown', 'ctrl+pagedown'], (e) => {
dispatch(
switchTab({
direction: 'pagedown'
})
);
return false; // this stops the event bubbling
});
return () => {
Mousetrap.unbind(['command+pagedown', 'ctrl+pagedown']);
};
}, [dispatch]);
// Close all tabs
// close all tabs
useEffect(() => {
Mousetrap.bind(['command+shift+w', 'ctrl+shift+w'], (e) => {
const activeTab = find(tabs, (t) => t.uid === activeTabUid);

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