mirror of
https://github.com/usebruno/bruno.git
synced 2025-01-09 15:38:34 +01:00
Merge branch 'main' into feature/1602-multipart-content-type
# Conflicts: # packages/bruno-lang/v2/src/jsonToBru.js
This commit is contained in:
commit
62babef678
29
.github/workflows/tests.yml
vendored
29
.github/workflows/tests.yml
vendored
@ -15,6 +15,8 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: './package-lock.json'
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
@ -23,8 +25,14 @@ jobs:
|
||||
run: |
|
||||
npm run build --workspace=packages/bruno-common
|
||||
npm run build --workspace=packages/bruno-query
|
||||
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
|
||||
|
||||
# tests
|
||||
- name: Test Package bruno-js
|
||||
run: npm run test --workspace=packages/bruno-js
|
||||
- name: Test Package bruno-cli
|
||||
run: npm run test --workspace=packages/bruno-cli
|
||||
|
||||
# test
|
||||
- name: Test Package bruno-query
|
||||
run: npm run test --workspace=packages/bruno-query
|
||||
- name: Test Package bruno-lang
|
||||
@ -33,12 +41,8 @@ jobs:
|
||||
run: npm run test --workspace=packages/bruno-schema
|
||||
- name: Test Package bruno-app
|
||||
run: npm run test --workspace=packages/bruno-app
|
||||
- name: Test Package bruno-js
|
||||
run: npm run test --workspace=packages/bruno-js
|
||||
- name: Test Package bruno-common
|
||||
run: npm run test --workspace=packages/bruno-common
|
||||
- name: Test Package bruno-cli
|
||||
run: npm run test --workspace=packages/bruno-cli
|
||||
- name: Test Package bruno-electron
|
||||
run: npm run test --workspace=packages/bruno-electron
|
||||
|
||||
@ -50,6 +54,8 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: './package-lock.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
@ -58,6 +64,7 @@ jobs:
|
||||
run: |
|
||||
npm run build --workspace=packages/bruno-query
|
||||
npm run build --workspace=packages/bruno-common
|
||||
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
@ -71,15 +78,3 @@ jobs:
|
||||
with:
|
||||
files: packages/bruno-tests/collection/junit.xml
|
||||
comment_mode: always
|
||||
prettier:
|
||||
name: Prettier
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
- name: Run Prettier
|
||||
run: npm run test:prettier:web
|
||||
|
@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx pretty-quick --staged
|
@ -34,10 +34,11 @@ Libraries we use
|
||||
- Schema Validation - Yup
|
||||
- Request Client - axios
|
||||
- Filesystem Watcher - chokidar
|
||||
- i18n - i18next
|
||||
|
||||
### Dependencies
|
||||
|
||||
You would need [Node v18.x or the latest LTS version](https://nodejs.org/en/) and npm 8.x. We use npm workspaces in the project
|
||||
You would need [Node v20.x or the latest LTS version](https://nodejs.org/en/) and npm 8.x. We use npm workspaces in the project
|
||||
|
||||
## Development
|
||||
|
||||
@ -57,6 +58,9 @@ npm run build:graphql-docs
|
||||
npm run build:bruno-query
|
||||
npm run build:bruno-common
|
||||
|
||||
# bundle js sandbox libraries
|
||||
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
|
||||
|
||||
# run next app (terminal 1)
|
||||
npm run dev:web
|
||||
|
||||
|
@ -37,7 +37,7 @@ Bruno 基于 NextJs 和 React 构建。我们使用 Electron 来封装桌面版
|
||||
|
||||
### 依赖项
|
||||
|
||||
您需要 [Node v18.x 或最新的 LTS 版本](https://nodejs.org/en/) 和 npm 8.x。我们在这个项目中也使用 npm 工作区(_npm workspaces_)。
|
||||
您需要 [Node v20.x 或最新的 LTS 版本](https://nodejs.org/en/) 和 npm 8.x。我们在这个项目中也使用 npm 工作区(_npm workspaces_)。
|
||||
|
||||
## 开发
|
||||
|
||||
|
@ -37,7 +37,7 @@ Bibliotheken die wir benutzen
|
||||
|
||||
### Abhängigkeiten
|
||||
|
||||
Du benötigst [Node v18.x oder die neuste LTS Version](https://nodejs.org/en/) und npm 8.x. Wir benutzen npm workspaces in dem Projekt.
|
||||
Du benötigst [Node v20.x oder die neuste LTS Version](https://nodejs.org/en/) und npm 8.x. Wir benutzen npm workspaces in dem Projekt.
|
||||
|
||||
### Lass uns coden
|
||||
|
||||
|
@ -37,7 +37,7 @@ Librerías que utilizamos:
|
||||
|
||||
### Dependencias
|
||||
|
||||
Necesitarás [Node v18.x o la última versión LTS](https://nodejs.org/es) y npm 8.x. Ten en cuenta que utilizamos espacios de trabajo de npm en el proyecto.
|
||||
Necesitarás [Node v20.x o la última versión LTS](https://nodejs.org/es) y npm 8.x. Ten en cuenta que utilizamos espacios de trabajo de npm en el proyecto.
|
||||
|
||||
## Desarrollo
|
||||
|
||||
|
@ -37,7 +37,7 @@ Les librairies que nous utilisons :
|
||||
|
||||
### Dépendances
|
||||
|
||||
Vous aurez besoin de [Node v18.x ou la dernière version LTS](https://nodejs.org/en/) et npm 8.x. Nous utilisons aussi les espaces de travail npm (_npm workspaces_) dans ce projet.
|
||||
Vous aurez besoin de [Node v20.x ou la dernière version LTS](https://nodejs.org/en/) et npm 8.x. Nous utilisons aussi les espaces de travail npm (_npm workspaces_) dans ce projet.
|
||||
|
||||
## Développement
|
||||
|
||||
@ -62,6 +62,9 @@ npm run build:graphql-docs
|
||||
# construction de bruno query
|
||||
npm run build:bruno-query
|
||||
|
||||
# construction de bruno common
|
||||
npm run build:bruno-common
|
||||
|
||||
# démarrage de next (terminal 1)
|
||||
npm run dev:web
|
||||
|
||||
|
@ -37,7 +37,7 @@ Libraries जिनका हम उपयोग करते हैं
|
||||
|
||||
### निर्भरताएँ
|
||||
|
||||
आपको [Node v18.x या नवीनतम LTS संस्करण](https://nodejs.org/en/) और npm 8.x की आवश्यकता होगी। हम प्रोजेक्ट में npm वर्कस्पेस का उपयोग करते हैं
|
||||
आपको [Node v20.x या नवीनतम LTS संस्करण](https://nodejs.org/en/) और npm 8.x की आवश्यकता होगी। हम प्रोजेक्ट में npm वर्कस्पेस का उपयोग करते हैं
|
||||
|
||||
## डेवलपमेंट
|
||||
|
||||
|
@ -37,7 +37,7 @@ Le librerie che utilizziamo sono:
|
||||
|
||||
### Dependences
|
||||
|
||||
Hai bisogno di [Node v18.x o dell'ultima versione LTS](https://nodejs.org/en/) di npm 8.x. Utilizziamo gli spazi di lavoro npm (_npm workspaces_) in questo progetto.
|
||||
Hai bisogno di [Node v20.x o dell'ultima versione LTS](https://nodejs.org/en/) di npm 8.x. Utilizziamo gli spazi di lavoro npm (_npm workspaces_) in questo progetto.
|
||||
|
||||
### Iniziamo a codificare
|
||||
|
||||
|
@ -37,7 +37,7 @@ Bruno は Next.js と React で作られています。デスクトップアプ
|
||||
|
||||
### 依存関係
|
||||
|
||||
[Node v18.x もしくは最新の LTS バージョン](https://nodejs.org/en/)と npm 8.x が必要です。プロジェクトに npm ワークスペースを使用しています。
|
||||
[Node v20.x もしくは最新の LTS バージョン](https://nodejs.org/en/)と npm 8.x が必要です。プロジェクトに npm ワークスペースを使用しています。
|
||||
|
||||
## 開発
|
||||
|
||||
|
@ -37,7 +37,7 @@ Bruno는 Next.js와 React로 구축되었습니다. 또한, (로컬 컬렉션을
|
||||
|
||||
### 의존성
|
||||
|
||||
[Node v18.x 혹은 최신 LTS version](https://nodejs.org/en/)과 npm 8.x 버전이 필요합니다. 우리는 이 프로젝트에서 npm workspaces를 사용합니다.
|
||||
[Node v20.x 혹은 최신 LTS version](https://nodejs.org/en/)과 npm 8.x 버전이 필요합니다. 우리는 이 프로젝트에서 npm workspaces를 사용합니다.
|
||||
|
||||
## 개발
|
||||
|
||||
|
@ -37,7 +37,7 @@ Biblioteki, których używamy
|
||||
|
||||
### Zależności
|
||||
|
||||
Będziesz potrzebować [Node v18.x lub najnowszej wersji LTS](https://nodejs.org/en/) oraz npm 8.x. W projekcie używamy npm workspaces
|
||||
Będziesz potrzebować [Node v20.x lub najnowszej wersji LTS](https://nodejs.org/en/) oraz npm 8.x. W projekcie używamy npm workspaces
|
||||
|
||||
## Rozwój
|
||||
|
||||
|
@ -37,7 +37,7 @@ Bibliotecas que utilizamos:
|
||||
|
||||
### Dependências
|
||||
|
||||
Você precisará do [Node v18.x (ou da versão LTS mais recente)](https://nodejs.org/en/) e do npm na versão 8.x. Nós utilizamos npm workspaces no projeto.
|
||||
Você precisará do [Node v20.x (ou da versão LTS mais recente)](https://nodejs.org/en/) e do npm na versão 8.x. Nós utilizamos npm workspaces no projeto.
|
||||
|
||||
## Desenvolvimento
|
||||
|
||||
|
@ -37,7 +37,7 @@ Bibliotecile pe care le folosim
|
||||
|
||||
### Dependențele
|
||||
|
||||
Veți avea nevoie de [Node v18.x sau cea mai recentă versiune LTS](https://nodejs.org/en/) și npm 8.x. Noi folosim spații de lucru npm în proiect
|
||||
Veți avea nevoie de [Node v20.x sau cea mai recentă versiune LTS](https://nodejs.org/en/) și npm 8.x. Noi folosim spații de lucru npm în proiect
|
||||
|
||||
## Dezvoltarea
|
||||
|
||||
|
@ -37,7 +37,7 @@ Bruno построен с использованием Next.js и React. Мы т
|
||||
|
||||
### Зависимости
|
||||
|
||||
Вам потребуется [Node v18.x или последняя версия LTS](https://nodejs.org/en/) и npm 8.x. В проекте мы используем рабочие пространства npm
|
||||
Вам потребуется [Node v20.x или последняя версия LTS](https://nodejs.org/en/) и npm 8.x. В проекте мы используем рабочие пространства npm
|
||||
|
||||
### Приступим к коду
|
||||
|
||||
|
84
docs/contributing/contributing_sk.md
Normal file
84
docs/contributing/contributing_sk.md
Normal file
@ -0,0 +1,84 @@
|
||||
## Urobme bruno lepším, spoločne !!
|
||||
|
||||
Sme radi, že chcete zlepšiť bruno. Nižšie sú uvedené pokyny, ako začať s výchovou bruno na vašom počítači.
|
||||
|
||||
### Technologický zásobník
|
||||
|
||||
Bruno je vytvorené pomocou Next.js a React. Na dodávanie desktopovej verzie (ktorá podporuje lokálne kolekcie) používame aj electron.
|
||||
|
||||
Balíčky, ktoré používame:
|
||||
|
||||
- CSS - Tailwind
|
||||
- Editory kódu - Codemirror
|
||||
- Správa stavu - Redux
|
||||
- Ikony - Tabler Icons
|
||||
- Formuláre - formik
|
||||
- Overovanie schém - Yup
|
||||
- Klient požiadaviek - axios
|
||||
- Sledovač súborového systému - chokidar
|
||||
|
||||
### Závislosti
|
||||
|
||||
Budete potrebovať [NodeJS v18.x alebo najnovšiu verziu LTS](https://nodejs.org/en/) a npm versiu 8.x. V projekte používame pracovné priestory npm
|
||||
|
||||
## Vývoj
|
||||
|
||||
Bruno sa vyvíja ako desktopová aplikácia. Aplikáciu je potrebné načítať spustením aplikácie Next.js v jednom termináli a potom spustiť aplikáciu electron v inom termináli.
|
||||
|
||||
### Závislosti
|
||||
|
||||
- NodeJS v18
|
||||
|
||||
### Miestny vývoj
|
||||
|
||||
```bash
|
||||
# použite verziu nodejs 18
|
||||
nvm use
|
||||
|
||||
# nainštalovať balíčky
|
||||
npm i --legacy-peer-deps
|
||||
|
||||
# zostaviť balíčky
|
||||
npm run build:graphql-docs
|
||||
npm run build:bruno-query
|
||||
npm run build:bruno-common
|
||||
|
||||
# spustite ďalšiu aplikáciu (terminál 1)
|
||||
npm run dev:web
|
||||
|
||||
# spustite aplikáciu electron (terminál 2)
|
||||
npm run dev:electron
|
||||
```
|
||||
|
||||
### Riešenie problémov
|
||||
|
||||
Pri spustení `npm install` sa môžete stretnúť s chybou `Unsupported platform`. Ak chcete túto chybu odstrániť, musíte odstrániť súbory `node_modules`, `package-lock.json` a spustiť `npm install`. Tým by sa mali nainštalovať všetky potrebné balíky potrebné na spustenie aplikácie.
|
||||
|
||||
```shell
|
||||
# Odstrániť node_modules v podadresároch
|
||||
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
|
||||
rm -rf "$dir"
|
||||
done
|
||||
|
||||
# Odstráňte package-lock v podadresároch
|
||||
find . -type f -name "package-lock.json" -delete
|
||||
```
|
||||
|
||||
### Testovanie
|
||||
|
||||
````bash
|
||||
# bruno-schema
|
||||
npm test --workspace=packages/bruno-schema
|
||||
|
||||
# bruno-lang
|
||||
npm test --workspace=packages/bruno-lang
|
||||
```
|
||||
|
||||
### Vyrobenie Pull Request
|
||||
|
||||
- Prosím, aby PR boli malé a zamerané na jednu vec
|
||||
- Prosím, dodržujte formát vytvárania vetiev
|
||||
- feature/[názov funkcie]: Táto vetva by mala obsahovať zmeny pre konkrétnu funkciu
|
||||
- Príklad: feature/dark-mode
|
||||
- bugfix/[názov chyby]: Táto vetva by mala obsahovať iba opravy konkrétnej chyby
|
||||
- Príklad: bugfix/bug-1
|
@ -37,7 +37,7 @@ Kullandığımız kütüphaneler
|
||||
|
||||
### Bağımlılıklar
|
||||
|
||||
[Node v18.x veya en son LTS sürümüne](https://nodejs.org/en/) ve npm 8.x'e ihtiyacınız olacaktır. Projede npm çalışma alanlarını kullanıyoruz
|
||||
[Node v20.x veya en son LTS sürümüne](https://nodejs.org/en/) ve npm 8.x'e ihtiyacınız olacaktır. Projede npm çalışma alanlarını kullanıyoruz
|
||||
|
||||
## Gelişim
|
||||
|
||||
|
@ -37,7 +37,7 @@ Bruno побудований на Next.js та React. Також для деск
|
||||
|
||||
### Залежності
|
||||
|
||||
Вам знадобиться [Node v18.x або остання LTS версія](https://nodejs.org/en/) та npm 8.x. Ми використовуєм npm workspaces в цьому проекті
|
||||
Вам знадобиться [Node v20.x або остання LTS версія](https://nodejs.org/en/) та npm 8.x. Ми використовуєм npm workspaces в цьому проекті
|
||||
|
||||
### Починаєм писати код
|
||||
|
||||
|
@ -37,7 +37,7 @@ Bruno 使用 Next.js 和 React 構建。我們使用 Electron 來封裝及發佈
|
||||
|
||||
### 依賴關係
|
||||
|
||||
您需要使用 [Node v18.x 或最新的 LTS 版本](https://nodejs.org/en/) 和 npm 8.x。我們在這個專案中使用 npm 工作區(_npm workspaces_)。
|
||||
您需要使用 [Node v20.x 或最新的 LTS 版本](https://nodejs.org/en/) 和 npm 8.x。我們在這個專案中使用 npm 工作區(_npm workspaces_)。
|
||||
|
||||
## 開發
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| **العربية**
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
برونو هو عميل API جديد ومبتكر، يهدف إلى ثورة الحالة الحالية التي يمثلها برنامج Postman وأدوات مماثلة هناك.
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| [العربية](./readme_ar.md)
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
ব্রুনো হল একটি নতুন এবং উদ্ভাবনী API ক্লায়েন্ট, যার লক্ষ্য পোস্টম্যান এবং অনুরূপ সরঞ্জাম দ্বারা প্রতিনিধিত্ব করা স্থিতাবস্থায় বিপ্লব ঘটানো।
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| [العربية](./readme_ar.md)
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
Bruno 是一款全新且创新的 API 客户端,旨在颠覆 Postman 和其他类似工具。
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| [العربية](./readme_ar.md)
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
Bruno ist ein neuer und innovativer API-Client, der den Status Quo von Postman und ähnlichen Tools revolutionieren soll.
|
||||
|
||||
@ -38,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)
|
||||
|
||||
📢 Sehen Sie sich unseren Vortrag auf der India FOSS 3.0 Conference [hier](https://www.youtube.com/watch?v=7bSMFpbcPiY) an.
|
||||
📢 Sieh Dir 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 />
|
||||
|
||||
@ -47,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)
|
||||
|
||||
Sie können die [Golden Edition](https://www.usebruno.com/pricing) vorbestellen ~~$19~~ **$9** ! <br/>
|
||||
Du kannst die [Golden Edition](https://www.usebruno.com/pricing) bestellen **$19**! <br/>
|
||||
|
||||
### Installation
|
||||
|
||||
Bruno ist als Download [auf unserer Website](https://www.usebruno.com/downloads) für Mac, Windows und Linux verfügbar.
|
||||
|
||||
Sie können Bruno auch über Paketmanager wie Homebrew, Chocolatey, Scoop, Snap, Flatpak und Apt installieren.
|
||||
Du kannst Bruno auch über Paketmanager wie Homebrew, Chocolatey, Scoop, Snap, Flatpak und Apt installieren.
|
||||
|
||||
```sh
|
||||
# Auf Mac via Homebrew
|
||||
@ -122,11 +123,11 @@ Oder einer Versionskontrolle deiner Wahl
|
||||
|
||||
### Unterstützung ❤️
|
||||
|
||||
Wuff! Wenn du dieses Projekt magst, klick den ⭐ Button !!
|
||||
Wuff! Wenn du dieses Projekt magst, klick auf den ⭐ Button !!
|
||||
|
||||
### Teile Erfahrungsberichte 📣
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
### Bereitstellung in neuen Paket-Managern
|
||||
|
||||
|
@ -27,6 +27,8 @@
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| [العربية](./readme_ar.md)
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
Bruno es un cliente de APIs nuevo e innovador, creado con el objetivo de revolucionar el panorama actual representado por Postman y otras herramientas similares.
|
||||
|
||||
Bruno almacena tus colecciones directamente en una carpeta de tu sistema de archivos. Usamos un lenguaje de marcado de texto plano, llamado Bru, para guardar información sobre las peticiones a tus APIs.
|
||||
|
@ -27,6 +27,7 @@
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| [العربية](./readme_ar.md)
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
Bruno est un nouveau client API, innovant, qui a pour but de révolutionner le _statu quo_ que représentent Postman et les autres outils.
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| [العربية](./readme_ar.md)
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
Bruno è un nuovo ed innovativo API client, mirato a rivoluzionare lo status quo rappresentato da Postman e strumenti simili disponibili.
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| [العربية](./readme_ar.md)
|
||||
| **日本語**
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
Bruno は革新的な API クライアントです。Postman を代表する API クライアントツールの現状に一石を投じることを目指しています。
|
||||
|
||||
|
176
docs/readme/readme_ka.md
Normal file
176
docs/readme/readme_ka.md
Normal file
@ -0,0 +1,176 @@
|
||||
<br />
|
||||
<img src="../../assets/images/logo-transparent.png" width="80"/>
|
||||
|
||||
### ბრუნო - ღია წყაროების IDE API-ების შესწავლისა და ტესტირებისათვის.
|
||||
|
||||
[![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)
|
||||
| [Українська](./readme_ua.md)
|
||||
| [Русский](./readme_ru.md)
|
||||
| [Türkçe](./readme_tr.md)
|
||||
| [Deutsch](./readme_de.md)
|
||||
| [Français](./readme_fr.md)
|
||||
| [Português (BR)](./readme_pt_br.md)
|
||||
| [한국어](./readme_kr.md)
|
||||
| [বাংলা](./readme_bn.md)
|
||||
| [Español](./readme_es.md)
|
||||
| [Italiano](./readme_it.md)
|
||||
| [Română](./readme_ro.md)
|
||||
| [Polski](./readme_pl.md)
|
||||
| [简体中文](./readme_cn.md)
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| [العربية](./readme_ar.md)
|
||||
| [日本語](./readme_ja.md)
|
||||
| **ქართული**
|
||||
|
||||
ბრუნო არის ახალი და ინოვაციური API კლიენტი, რომელიც მიზნად ისახავს პოსტმანისა და მსგავსი ინსტრუმენტების არსებული მდგომარეობის რევოლუციას.
|
||||
|
||||
ბრუნო თქვენი კოლექციების შენახვას უშუალოდ თქვენს ფაილური სისტემის ერთ-ერთ საქაღალოში ახდენს. ჩვენ ვხმარობთ უბრალო ტექსტურ მარკაპ ენის, Bru-ს, API მოთხოვნების შესახებ ინფორმაციის შენახვისთვის.
|
||||
|
||||
თქვენ შეგიძლიათ გამოიყენოთ Git ან ნებისმიერი ვერსიის კონტროლის სისტემა თქვენი API კოლექციების გასაზიარებლად.
|
||||
|
||||
ბრუნო მხოლოდ ოფლაინ რეჟიმში მუშაობს. ბრუნოში ღრუბლური სინქრონიზაციის დამატების გეგმები არ არის. ჩვენ ვაფასებთ თქვენი მონაცემების პრივატობას და creemos, რომ ისინი თქვენს მოწყობილობაში უნდა დარჩეს. წაიკითხეთ ჩვენი გრძელვადიანი ხედვა [აქ](https://github.com/usebruno/bruno/discussions/269)
|
||||
|
||||
[დამატებით ბრუნო](https://www.usebruno.com/downloads)
|
||||
|
||||
📢 შეიტყვეთ ჩვენი უახლესი საუბრის შესახებ India FOSS 3.0 კონფერენციაზე [აქ](https://www.youtube.com/watch?v=7bSMFpbcPiY)
|
||||
|
||||
![bruno](../../assets/images/landing-2.png) <br /><br />
|
||||
|
||||
### ოქროს გამოცემა ✨
|
||||
|
||||
მთავარი ფუნქციების უმეტესობა უფასოა და ღია წყაროა. ჩვენ ვცდილობთ ჰარმონიული ბალანსის დაცვას [ღია წყაროების პრინციპებსა და მდგრადობას შორის](https://github.com/usebruno/bruno/discussions/269)
|
||||
|
||||
თქვენ შეგიძლიათ შეიძინოთ [ოქროს გამოცემა](https://www.usebruno.com/pricing) ერთჯერადი გადახდით **19 დოლარად**! <br/>
|
||||
|
||||
### ინსტალაცია
|
||||
|
||||
ბრუნო ხელმისაწვდომია როგორც ბინარული ჩამოტვირთვა [ჩვენ的网站上](https://www.usebruno.com/downloads) Mac-ის, Windows-ისა და Linux-ისთვის.
|
||||
|
||||
თქვენ ასევე შეგიძლიათ დააინსტალიროთ ბრუნო პაკეტის მენეჯერების საშუალებით, როგორიცაა Homebrew, Chocolatey, Scoop, Snap, Flatpak და Apt.
|
||||
|
||||
```sh
|
||||
# Mac-ზე Homebrew-ს საშუალებით
|
||||
brew install bruno
|
||||
|
||||
# Windows-ზე Chocolatey-ს საშუალებით
|
||||
choco install bruno
|
||||
|
||||
# Windows-ზე Scoop-ის საშუალებით
|
||||
scoop bucket add extras
|
||||
scoop install bruno
|
||||
|
||||
# Windows-ზე winget-ის საშუალებით
|
||||
winget install Bruno.Bruno
|
||||
|
||||
# Linux-ზე Snap-ის საშუალებით
|
||||
snap install bruno
|
||||
|
||||
# Linux-ზე Flatpak-ის საშუალებით
|
||||
flatpak install com.usebruno.Bruno
|
||||
|
||||
# Linux-ზე 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
|
||||
```
|
||||
|
||||
### პლატფორმებს შორის მუშაობა 🖥️
|
||||
|
||||
![bruno](../../assets/images/run-anywhere.png) <br /><br />
|
||||
|
||||
### თანამშრომლობა Git-ის საშუალებით 👩💻🧑💻
|
||||
|
||||
ან ნებისმიერი ვერსიის კონტროლის სისტემის საშუალებით
|
||||
|
||||
![bruno](../../assets/images/version-control.png) <br /><br />
|
||||
|
||||
### სპონსორები
|
||||
|
||||
#### ოქროს სპონსორები
|
||||
|
||||
<img src="../../assets/images/sponsors/samagata.png" width="150"/>
|
||||
|
||||
#### ვერცხლის სპონსორები
|
||||
|
||||
<img src="../../assets/images/sponsors/commit-company.png" width="70"/>
|
||||
|
||||
#### ბრინჯის სპონსორები
|
||||
|
||||
<a href="https://zuplo.link/bruno">
|
||||
<img src="../../assets/images/sponsors/zuplo.png" width="120"/>
|
||||
</a>
|
||||
|
||||
### მნიშვნელოვანი ბმულები 📌
|
||||
|
||||
- [ჩვენი გრძელვადიანი ხედვა](https://github.com/usebruno/bruno/discussions/269)
|
||||
- [გეგმა](https://github.com/usebruno/bruno/discussions/384)
|
||||
- [დოკუმენტაცია](https://docs.usebruno.com)
|
||||
- [Stack Overflow](https://stackoverflow.com/questions/tagged/bruno)
|
||||
- [ვებსაიტი](https://www.usebruno.com)
|
||||
- [ფასები](https://www.usebruno.com/pricing)
|
||||
- [დამატება](https://www.usebruno.com/downloads)
|
||||
- [GitHub სპონსორები](https://github.com/sponsors/helloanoop).
|
||||
|
||||
### ვიტრინა 🎥
|
||||
|
||||
- [მოწონებები](https://github.com/usebruno/bruno/discussions/343)
|
||||
- [მეცნიერების ჰაბი](https://github.com/usebruno/bruno/discussions/386)
|
||||
- [Scriptmania](https://github.com/usebruno/bruno/discussions/385)
|
||||
|
||||
### მხარდაჭერა ❤️
|
||||
|
||||
თუ გიყვართ ბრუნო და გინდათ მხარი დაუჭიროთ ჩვენს ღია წყაროების მუშაობას, გაითვალისწინეთ ჩვენი დახმარება [GitHub სპონსორების საშუალებით](https://github.com/sponsors/helloanoop).
|
||||
|
||||
### გააზიარეთ მოწმობები 📣
|
||||
|
||||
თუ ბრუნო დაგეხმარათ თქვენს სამუშაოში და გუნდებში, გთხოვთ, არ დაგავიწყდეთ ჩვენი [მოწონებების გაზიარება ჩვენს GitHub განხილვაში](https://github.com/usebruno/bruno/discussions/343)
|
||||
|
||||
### ახალი პაკეტის მენეჯერებში გამოქვეყნება
|
||||
|
||||
იხილეთ [აქ](../../publishing.md) მეტი ინფორმაციისათვის.
|
||||
|
||||
### დაინტერესდით 🌐
|
||||
|
||||
[𝕎 (Twitter)](https://twitter.com/use_bruno) <br />
|
||||
[ვებსაიტი](https://www.usebruno.com) <br />
|
||||
[Discord](https://discord.com/invite/KgcZUncpjq) <br />
|
||||
[LinkedIn](https://www.linkedin.com/company/usebruno)
|
||||
|
||||
### სავაჭრო ნიშანი
|
||||
|
||||
**სახელი**
|
||||
|
||||
`ბრუნო` არის სავაჭრო ნიშანი, რომელსაც ფლობს [ანუპ მ. დ.](https://www.helloanoop.com/)
|
||||
|
||||
**ლოგო**
|
||||
|
||||
ლოგო არის [OpenMoji](https://openmoji.org/library/emoji-1F436/) სურათებიდან. ლიცენზია: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
|
||||
|
||||
### თანამშრომლობა 👩💻🧑💻
|
||||
|
||||
მიხარია, რომ დაინტერესებული ხართ ბრუნოს გაუმჯობესებით. გთხოვთ, გადახედეთ [თანამშრომლობის სახელმძღვანელოს](../../contributing.md)
|
||||
|
||||
თუნდაც ვერ მოახერხოთ კოდის საშუალებით კონტრიბუცია, ნუ ინანებთ პრობლემების და ფუნქციის მოთხოვნების ჩაწერას, რომლებიც უნდა განხორციელდეს თქვენი შემთხვევის გადასაჭრელად.
|
||||
|
||||
### ავტორები
|
||||
|
||||
<div align="center">
|
||||
<a href="https://github.com/usebruno/bruno/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=usebruno/bruno" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
### ლიცენზია 📄
|
||||
|
||||
[MIT](../../license.md)
|
@ -27,6 +27,7 @@
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| [العربية](./readme_ar.md)
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
Bruno는 새롭고 혁신적인 API 클라이언트로, Postman과 유사한 툴들을 혁신하는 것을 목표로 합니다.
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| [العربية](./readme_ar.md)
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
Bruno to nowy i innowacyjny klient API, którego celem jest zrewolucjonizowanie status quo reprezentowanego przez narzędzia takie jak Postman.
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| [العربية](./readme_ar.md)
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
Bruno é um novo e inovador cliente de API, com o objetivo de revolucionar o status quo representado por ferramentas como o Postman e outras semelhantes.
|
||||
|
||||
@ -103,6 +104,12 @@ Ou qualquer sistema de controle de versão de sua escolha.
|
||||
|
||||
<img src="../../assets/images/sponsors/commit-company.png" width="70"/>
|
||||
|
||||
#### Apoiadores Bronze
|
||||
|
||||
<a href="https://zuplo.link/bruno">
|
||||
<img src="../../assets/images/sponsors/zuplo.png" width="120"/>
|
||||
</a>
|
||||
|
||||
### Links Importantes 📌
|
||||
|
||||
- [Nossa Visão de Longo Prazo](https://github.com/usebruno/bruno/discussions/269)
|
||||
|
@ -27,6 +27,7 @@
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| [العربية](./readme_ar.md)
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
Bruno este un client API nou și inovativ, care vizează să revoluționeze status quo-ul reprezentat de Postman și alte instrumente similare.
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| [العربية](./readme_ar.md)
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
Bruno - новый и инновационный клиент API, направленный на революцию в установившейся ситуации, представленной Postman и подобными инструментами.
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| [العربية](./readme_ar.md)
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
Bruno, Postman ve benzeri araçlar tarafından temsil edilen statükoda devrim yaratmayı amaçlayan yeni ve yenilikçi bir API istemcisidir.
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| [العربية](./readme_ar.md)
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
Bruno це новий та іноваційний API клієнт, націлений на революційну зміну статус кво, запровадженого інструментами на кшталт Postman.
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
| **正體中文**
|
||||
| [العربية](./readme_ar.md)
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
Bruno 是一個全新且有創新性的 API 用戶端,目的在徹底改變以 Postman 和其他類似工具的現況。
|
||||
|
||||
|
22613
package-lock.json
generated
22613
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -20,14 +20,17 @@
|
||||
"@jest/globals": "^29.2.0",
|
||||
"@playwright/test": "^1.27.1",
|
||||
"@types/jest": "^29.5.11",
|
||||
"concurrently": "^8.2.2",
|
||||
"fs-extra": "^11.1.1",
|
||||
"husky": "^8.0.3",
|
||||
"jest": "^29.2.0",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"randomstring": "^1.2.2",
|
||||
"rimraf": "^6.0.1",
|
||||
"ts-jest": "^29.0.5"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "concurrently --kill-others \"npm run dev:web\" \"npm run dev:electron\"",
|
||||
"dev:web": "npm run dev --workspace=packages/bruno-app",
|
||||
"build:web": "npm run build --workspace=packages/bruno-app",
|
||||
"prettier:web": "npm run prettier --workspace=packages/bruno-app",
|
||||
@ -47,9 +50,8 @@
|
||||
"test:prettier:web": "npm run test:prettier --workspace=packages/bruno-app",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"rollup": "3.2.5"
|
||||
"rollup":"3.29.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"json-bigint": "^1.0.0",
|
||||
|
@ -12,6 +12,7 @@
|
||||
"prettier": "prettier --write \"./src/**/*.{js,jsx,json,ts,tsx}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/inter": "^5.0.15",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||
"@fortawesome/react-fontawesome": "^0.1.16",
|
||||
@ -35,7 +36,8 @@
|
||||
"graphiql": "^1.5.9",
|
||||
"graphql": "^16.6.0",
|
||||
"graphql-request": "^3.7.0",
|
||||
"httpsnippet": "^3.0.1",
|
||||
"httpsnippet": "^3.0.6",
|
||||
"i18next": "^23.14.0",
|
||||
"idb": "^7.0.0",
|
||||
"immer": "^9.0.15",
|
||||
"jsesc": "^3.0.2",
|
||||
@ -47,6 +49,7 @@
|
||||
"know-your-http-well": "^0.5.0",
|
||||
"lodash": "^4.17.21",
|
||||
"markdown-it": "^13.0.2",
|
||||
"markdown-it-replace-link": "^1.2.0",
|
||||
"mousetrap": "^1.6.5",
|
||||
"nanoid": "3.3.4",
|
||||
"next": "12.3.3",
|
||||
@ -64,6 +67,7 @@
|
||||
"react-dom": "18.2.0",
|
||||
"react-github-btn": "^1.4.0",
|
||||
"react-hot-toast": "^2.4.0",
|
||||
"react-i18next": "^15.0.1",
|
||||
"react-inspector": "^6.0.2",
|
||||
"react-pdf": "^7.5.1",
|
||||
"react-redux": "^7.2.6",
|
||||
|
@ -5,7 +5,9 @@ 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;
|
||||
}
|
||||
|
||||
.CodeMirror-overlayscroll-horizontal div,
|
||||
@ -13,6 +15,24 @@ const StyledWrapper = styled.div`
|
||||
background: #d2d7db;
|
||||
}
|
||||
|
||||
.CodeMirror-dialog {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
#search-results-count {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: calc(100% + 1px);
|
||||
right: 0;
|
||||
border-width: 0 0 1px 1px;
|
||||
border-style: solid;
|
||||
border-color: ${(props) => props.theme.codemirror.border};
|
||||
padding: 0.1em 0.8em;
|
||||
background-color: ${(props) => props.theme.codemirror.bg};
|
||||
color: rgb(102, 102, 102);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
textarea.cm-editor {
|
||||
position: relative;
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import stripJsonComments from 'strip-json-comments';
|
||||
|
||||
let CodeMirror;
|
||||
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
|
||||
const TAB_SIZE = 2;
|
||||
|
||||
if (!SERVER_RENDERED) {
|
||||
CodeMirror = require('codemirror');
|
||||
@ -58,11 +59,17 @@ if (!SERVER_RENDERED) {
|
||||
'bru.cwd()',
|
||||
'bru.getEnvName(key)',
|
||||
'bru.getProcessEnv(key)',
|
||||
'bru.hasEnvVar(key)',
|
||||
'bru.getEnvVar(key)',
|
||||
'bru.setEnvVar(key,value)',
|
||||
'bru.hasVar(key)',
|
||||
'bru.getVar(key)',
|
||||
'bru.setVar(key,value)',
|
||||
'bru.setNextRequest(requestName)'
|
||||
'bru.deleteVar(key)',
|
||||
'bru.setNextRequest(requestName)',
|
||||
'req.disableParsingResponseJson()',
|
||||
'bru.getRequestVar(key)',
|
||||
'bru.sleep(ms)'
|
||||
];
|
||||
CodeMirror.registerHelper('hint', 'brunoJS', (editor, options) => {
|
||||
const cursor = editor.getCursor();
|
||||
@ -105,6 +112,7 @@ export default class CodeEditor extends React.Component {
|
||||
// unnecessary updates during the update lifecycle.
|
||||
this.cachedValue = props.value || '';
|
||||
this.variables = {};
|
||||
this.searchResultsCountElementId = 'search-results-count';
|
||||
|
||||
this.lintOptions = {
|
||||
esversion: 11,
|
||||
@ -118,7 +126,7 @@ export default class CodeEditor extends React.Component {
|
||||
value: this.props.value || '',
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
tabSize: 2,
|
||||
tabSize: TAB_SIZE,
|
||||
mode: this.props.mode || 'application/ld+json',
|
||||
keyMap: 'sublime',
|
||||
autoCloseBrackets: true,
|
||||
@ -151,8 +159,16 @@ export default class CodeEditor extends React.Component {
|
||||
this.props.onSave();
|
||||
}
|
||||
},
|
||||
'Cmd-F': 'findPersistent',
|
||||
'Ctrl-F': 'findPersistent',
|
||||
'Cmd-F': (cm) => {
|
||||
cm.execCommand('findPersistent');
|
||||
this._bindSearchHandler();
|
||||
this._appendSearchResultsCount();
|
||||
},
|
||||
'Ctrl-F': (cm) => {
|
||||
cm.execCommand('findPersistent');
|
||||
this._bindSearchHandler();
|
||||
this._appendSearchResultsCount();
|
||||
},
|
||||
'Cmd-H': 'replace',
|
||||
'Ctrl-H': 'replace',
|
||||
Tab: function (cm) {
|
||||
@ -166,7 +182,33 @@ export default class CodeEditor extends React.Component {
|
||||
'Ctrl-Y': 'foldAll',
|
||||
'Cmd-Y': 'foldAll',
|
||||
'Ctrl-I': 'unfoldAll',
|
||||
'Cmd-I': 'unfoldAll'
|
||||
'Cmd-I': 'unfoldAll',
|
||||
'Cmd-/': (cm) => {
|
||||
// comment/uncomment every selected line(s)
|
||||
const selections = cm.listSelections();
|
||||
selections.forEach((range) => {
|
||||
for (let i = range.from().line; i <= range.to().line; i++) {
|
||||
const selectedLine = cm.getLine(i);
|
||||
// if commented line, remove comment
|
||||
if (selectedLine.trim().startsWith('//')) {
|
||||
cm.replaceRange(
|
||||
selectedLine.replace(/^(\s*)\/\/\s?/, '$1'),
|
||||
{ line: i, ch: 0 },
|
||||
{ line: i, ch: selectedLine.length }
|
||||
);
|
||||
continue;
|
||||
}
|
||||
// otherwise add comment
|
||||
cm.replaceRange(
|
||||
selectedLine.search(/\S|$/) >= TAB_SIZE
|
||||
? ' '.repeat(TAB_SIZE) + '// ' + selectedLine.trim()
|
||||
: '// ' + selectedLine,
|
||||
{ line: i, ch: 0 },
|
||||
{ line: i, ch: selectedLine.length }
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
foldOptions: {
|
||||
widget: (from, to) => {
|
||||
@ -278,6 +320,8 @@ export default class CodeEditor extends React.Component {
|
||||
this.editor.off('change', this._onEdit);
|
||||
this.editor = null;
|
||||
}
|
||||
|
||||
this._unbindSearchHandler();
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -286,9 +330,10 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
return (
|
||||
<StyledWrapper
|
||||
className="h-full w-full"
|
||||
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;
|
||||
}}
|
||||
@ -314,4 +359,62 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Bind handler to search input to count number of search results
|
||||
*/
|
||||
_bindSearchHandler = () => {
|
||||
const searchInput = document.querySelector('.CodeMirror-search-field');
|
||||
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', this._countSearchResults);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Unbind handler to search input to count number of search results
|
||||
*/
|
||||
_unbindSearchHandler = () => {
|
||||
const searchInput = document.querySelector('.CodeMirror-search-field');
|
||||
|
||||
if (searchInput) {
|
||||
searchInput.removeEventListener('input', this._countSearchResults);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Append search results count to search dialog
|
||||
*/
|
||||
_appendSearchResultsCount = () => {
|
||||
const dialog = document.querySelector('.CodeMirror-dialog.CodeMirror-dialog-top');
|
||||
|
||||
if (dialog) {
|
||||
const searchResultsCount = document.createElement('span');
|
||||
searchResultsCount.id = this.searchResultsCountElementId;
|
||||
dialog.appendChild(searchResultsCount);
|
||||
|
||||
this._countSearchResults();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Count search results and update state
|
||||
*/
|
||||
_countSearchResults = () => {
|
||||
let count = 0;
|
||||
|
||||
const searchInput = document.querySelector('.CodeMirror-search-field');
|
||||
|
||||
if (searchInput && searchInput.value.length > 0) {
|
||||
const text = new RegExp(searchInput.value, 'gi');
|
||||
const matches = this.editor.getValue().match(text);
|
||||
count = matches ? matches.length : 0;
|
||||
}
|
||||
|
||||
const searchResultsCountElement = document.querySelector(`#${this.searchResultsCountElementId}`);
|
||||
|
||||
if (searchResultsCountElement) {
|
||||
searchResultsCountElement.innerText = `${count} results`;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -138,6 +138,7 @@ const AwsV4Auth = ({ collection }) => {
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleSecretAccessKeyChange(val)}
|
||||
collection={collection}
|
||||
isSecret={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -62,6 +62,7 @@ const BasicAuth = ({ collection }) => {
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handlePasswordChange(val)}
|
||||
collection={collection}
|
||||
isSecret={true}
|
||||
/>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
|
@ -37,6 +37,7 @@ const BearerAuth = ({ collection }) => {
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleTokenChange(val)}
|
||||
collection={collection}
|
||||
isSecret={true}
|
||||
/>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
|
@ -62,6 +62,7 @@ const DigestAuth = ({ collection }) => {
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handlePasswordChange(val)}
|
||||
collection={collection}
|
||||
isSecret={true}
|
||||
/>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
|
@ -78,7 +78,7 @@ const OAuth2AuthorizationCode = ({ collection }) => {
|
||||
return (
|
||||
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||
{inputsConfig.map((input) => {
|
||||
const { key, label } = input;
|
||||
const { key, label, isSecret } = input;
|
||||
return (
|
||||
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
||||
<label className="block font-medium">{label}</label>
|
||||
@ -90,6 +90,7 @@ const OAuth2AuthorizationCode = ({ collection }) => {
|
||||
onChange={(val) => handleChange(key, val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
isSecret={isSecret}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -17,7 +17,8 @@ const inputsConfig = [
|
||||
},
|
||||
{
|
||||
key: 'clientSecret',
|
||||
label: 'Client Secret'
|
||||
label: 'Client Secret',
|
||||
isSecret: true
|
||||
},
|
||||
{
|
||||
key: 'scope',
|
||||
|
@ -42,7 +42,7 @@ const OAuth2ClientCredentials = ({ collection }) => {
|
||||
return (
|
||||
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||
{inputsConfig.map((input) => {
|
||||
const { key, label } = input;
|
||||
const { key, label, isSecret } = input;
|
||||
return (
|
||||
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
||||
<label className="block font-medium">{label}</label>
|
||||
@ -54,6 +54,7 @@ const OAuth2ClientCredentials = ({ collection }) => {
|
||||
onChange={(val) => handleChange(key, val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
isSecret={isSecret}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -9,7 +9,8 @@ const inputsConfig = [
|
||||
},
|
||||
{
|
||||
key: 'clientSecret',
|
||||
label: 'Client Secret'
|
||||
label: 'Client Secret',
|
||||
isSecret: true
|
||||
},
|
||||
{
|
||||
key: 'scope',
|
||||
|
@ -44,7 +44,7 @@ const OAuth2AuthorizationCode = ({ item, collection }) => {
|
||||
return (
|
||||
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||
{inputsConfig.map((input) => {
|
||||
const { key, label } = input;
|
||||
const { key, label, isSecret } = input;
|
||||
return (
|
||||
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
||||
<label className="block font-medium">{label}</label>
|
||||
@ -56,6 +56,7 @@ const OAuth2AuthorizationCode = ({ item, collection }) => {
|
||||
onChange={(val) => handleChange(key, val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
isSecret={isSecret}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -17,7 +17,8 @@ const inputsConfig = [
|
||||
},
|
||||
{
|
||||
key: 'clientSecret',
|
||||
label: 'Client Secret'
|
||||
label: 'Client Secret',
|
||||
isSecret: true
|
||||
},
|
||||
{
|
||||
key: 'scope',
|
||||
|
@ -10,8 +10,9 @@ 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 = ({ clientCertConfig, onUpdate, onRemove }) => {
|
||||
const ClientCertSettings = ({ root, clientCertConfig, onUpdate, onRemove }) => {
|
||||
const certFilePathInputRef = useRef();
|
||||
const keyFilePathInputRef = useRef();
|
||||
const pfxFilePathInputRef = useRef();
|
||||
@ -67,7 +68,15 @@ const ClientCertSettings = ({ clientCertConfig, onUpdate, onRemove }) => {
|
||||
});
|
||||
|
||||
const getFile = (e) => {
|
||||
e.files?.[0]?.path && formik.setFieldValue(e.name, e.files?.[0]?.path);
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
const resetFileInputFields = () => {
|
||||
@ -102,10 +111,14 @@ const ClientCertSettings = ({ 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 items-center">
|
||||
<div className="flex w-full 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>
|
||||
|
@ -46,9 +46,10 @@ const Docs = ({ collection }) => {
|
||||
onSave={onSave}
|
||||
mode="application/text"
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
) : (
|
||||
<Markdown onDoubleClick={toggleViewMode} content={docs} />
|
||||
<Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={docs} />
|
||||
)}
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
@ -13,6 +13,7 @@ import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/acti
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { headers as StandardHTTPHeaders } from 'know-your-http-well';
|
||||
import { MimeTypes } from 'utils/codemirror/autocompleteConstants';
|
||||
const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header);
|
||||
|
||||
const Headers = ({ collection }) => {
|
||||
@ -117,6 +118,7 @@ const Headers = ({ collection }) => {
|
||||
)
|
||||
}
|
||||
collection={collection}
|
||||
autocomplete={MimeTypes}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -74,6 +74,7 @@ const PresetsSettings = ({ collection }) => {
|
||||
id="request-url"
|
||||
type="text"
|
||||
name="requestUrl"
|
||||
placeholder='Request URL'
|
||||
className="block textbox"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useFormik } from 'formik';
|
||||
import Tooltip from 'components/Tooltip';
|
||||
import InfoTip from 'components/InfoTip';
|
||||
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
|
||||
<Tooltip
|
||||
<InfoTip
|
||||
text={`
|
||||
<div>
|
||||
<ul>
|
||||
@ -114,7 +114,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
||||
</ul>
|
||||
</div>
|
||||
`}
|
||||
tooltipId="request-var"
|
||||
infotipId="request-var"
|
||||
/>
|
||||
</label>
|
||||
<div className="flex items-center">
|
||||
|
@ -52,6 +52,7 @@ 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">
|
||||
@ -64,6 +65,7 @@ const Script = ({ collection }) => {
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -36,6 +36,7 @@ const Tests = ({ collection }) => {
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
|
||||
<div className="mt-6">
|
||||
|
@ -0,0 +1,9 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
div.title {
|
||||
color: var(--color-tab-inactive);
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
@ -0,0 +1,56 @@
|
||||
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;
|
@ -0,0 +1,162 @@
|
||||
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;
|
@ -0,0 +1,32 @@
|
||||
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;
|
@ -16,6 +16,7 @@ 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();
|
||||
@ -77,6 +78,9 @@ const CollectionSettings = ({ collection }) => {
|
||||
case 'headers': {
|
||||
return <Headers collection={collection} />;
|
||||
}
|
||||
case 'vars': {
|
||||
return <Vars collection={collection} />;
|
||||
}
|
||||
case 'auth': {
|
||||
return <Auth collection={collection} />;
|
||||
}
|
||||
@ -95,6 +99,7 @@ const CollectionSettings = ({ collection }) => {
|
||||
case 'clientCert': {
|
||||
return (
|
||||
<ClientCertSettings
|
||||
root={collection.pathname}
|
||||
clientCertConfig={clientCertConfig}
|
||||
onUpdate={onClientCertSettingsUpdate}
|
||||
onRemove={onClientCertSettingsRemove}
|
||||
@ -122,6 +127,9 @@ 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>
|
||||
|
@ -1,14 +1,6 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
div.CodeMirror {
|
||||
/* todo: find a better way */
|
||||
height: calc(100vh - 240px);
|
||||
|
||||
.CodeMirror-scroll {
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
}
|
||||
.editing-mode {
|
||||
cursor: pointer;
|
||||
color: ${(props) => props.theme.colors.text.yellow};
|
||||
|
@ -37,8 +37,8 @@ const Documentation = ({ item, collection }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledWrapper className="mt-1 h-full w-full relative">
|
||||
<div className="editing-mode mb-2" role="tab" onClick={toggleViewMode}>
|
||||
<StyledWrapper className="flex flex-col gap-y-1 h-full w-full relative">
|
||||
<div className="editing-mode" role="tab" onClick={toggleViewMode}>
|
||||
{isEditing ? 'Preview' : 'Edit'}
|
||||
</div>
|
||||
|
||||
@ -47,13 +47,14 @@ 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}
|
||||
mode="application/text"
|
||||
/>
|
||||
) : (
|
||||
<Markdown onDoubleClick={toggleViewMode} content={docs} />
|
||||
<Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={docs} />
|
||||
)}
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
@ -40,10 +40,15 @@ const Wrapper = styled.div`
|
||||
color: ${(props) => props.theme.dropdown.iconColor};
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:hover:not(:disabled) {
|
||||
background-color: ${(props) => props.theme.dropdown.hoverBg};
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
&.border-top {
|
||||
border-top: solid 1px ${(props) => props.theme.dropdown.separator};
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import Portal from 'components/Portal';
|
||||
import Modal from 'components/Modal';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useFormik } from 'formik';
|
||||
import { addEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import * as Yup from 'yup';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import Portal from 'components/Portal';
|
||||
import Modal from 'components/Modal';
|
||||
|
||||
const CreateEnvironment = ({ collection, onClose }) => {
|
||||
const dispatch = useDispatch();
|
||||
@ -27,7 +27,7 @@ const CreateEnvironment = ({ collection, onClose }) => {
|
||||
toast.success('Environment created in collection');
|
||||
onClose();
|
||||
})
|
||||
.catch(() => toast.error('An error occurred while created the environment'));
|
||||
.catch(() => toast.error('An error occurred while creating the environment'));
|
||||
}
|
||||
});
|
||||
|
||||
@ -55,12 +55,13 @@ const CreateEnvironment = ({ collection, onClose }) => {
|
||||
<label htmlFor="name" className="block font-semibold">
|
||||
Environment Name
|
||||
</label>
|
||||
<div className="flex items-center mt-2">
|
||||
<input
|
||||
id="environment-name"
|
||||
type="text"
|
||||
name="name"
|
||||
ref={inputRef}
|
||||
className="block textbox mt-2 w-full"
|
||||
className="block textbox w-full"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
@ -68,6 +69,7 @@ const CreateEnvironment = ({ collection, onClose }) => {
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.name || ''}
|
||||
/>
|
||||
</div>
|
||||
{formik.touched.name && formik.errors.name ? (
|
||||
<div className="text-red-500">{formik.errors.name}</div>
|
||||
) : null}
|
||||
|
@ -5,7 +5,6 @@ import { useDispatch } from 'react-redux';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { uuid } from 'utils/common';
|
||||
import { maskInputValue } from 'utils/collections';
|
||||
import { useFormik } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import { variableNameRegex } from 'utils/common/regex';
|
||||
@ -96,10 +95,10 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Enabled</td>
|
||||
<td className="text-center">Enabled</td>
|
||||
<td>Name</td>
|
||||
<td>Value</td>
|
||||
<td>Secret</td>
|
||||
<td className="text-center">Secret</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -109,7 +108,7 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
||||
<td className="text-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="mr-3 mousetrap"
|
||||
className="mousetrap"
|
||||
name={`${index}.enabled`}
|
||||
checked={variable.enabled}
|
||||
onChange={formik.handleChange}
|
||||
@ -130,23 +129,22 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original
|
||||
/>
|
||||
<ErrorMessage name={`${index}.name`} />
|
||||
</td>
|
||||
<td>
|
||||
{variable.secret ? (
|
||||
<div className="overflow-hidden text-ellipsis">{maskInputValue(variable.value)}</div>
|
||||
) : (
|
||||
<td className="flex flex-row flex-nowrap">
|
||||
<div className="overflow-hidden grow w-full relative">
|
||||
<SingleLineEditor
|
||||
theme={storedTheme}
|
||||
collection={collection}
|
||||
name={`${index}.value`}
|
||||
value={variable.value}
|
||||
isSecret={variable.secret}
|
||||
onChange={(newValue) => formik.setFieldValue(`${index}.value`, newValue, true)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<td className="text-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="mr-3 mousetrap"
|
||||
className="mousetrap"
|
||||
name={`${index}.secret`}
|
||||
checked={variable.secret}
|
||||
onChange={formik.handleChange}
|
||||
|
@ -1,24 +1,38 @@
|
||||
import React from 'react';
|
||||
import Portal from 'components/Portal';
|
||||
import Modal from 'components/Modal';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import importPostmanEnvironment from 'utils/importers/postman-environment';
|
||||
import { importEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { toastError } from 'utils/common/error';
|
||||
import Modal from 'components/Modal';
|
||||
import { IconDatabaseImport } from '@tabler/icons';
|
||||
|
||||
const ImportEnvironment = ({ onClose, collection }) => {
|
||||
const ImportEnvironment = ({ collection, onClose }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleImportPostmanEnvironment = () => {
|
||||
importPostmanEnvironment()
|
||||
.then((environment) => {
|
||||
.then((environments) => {
|
||||
environments
|
||||
.filter((env) =>
|
||||
env.name && env.name !== 'undefined'
|
||||
? true
|
||||
: () => {
|
||||
toast.error('Failed to import environment: env has no name');
|
||||
return false;
|
||||
}
|
||||
)
|
||||
.map((environment) => {
|
||||
dispatch(importEnvironment(environment.name, environment.variables, collection.uid))
|
||||
.then(() => {
|
||||
toast.success('Environment imported successfully');
|
||||
onClose();
|
||||
})
|
||||
.catch(() => toast.error('An error occurred while importing the environment'));
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
onClose();
|
||||
})
|
||||
.catch((err) => toastError(err, 'Postman Import environment failed'));
|
||||
};
|
||||
@ -26,11 +40,14 @@ const ImportEnvironment = ({ onClose, collection }) => {
|
||||
return (
|
||||
<Portal>
|
||||
<Modal size="sm" title="Import Environment" hideFooter={true} handleConfirm={onClose} handleCancel={onClose}>
|
||||
<div>
|
||||
<div className="text-link hover:underline cursor-pointer" onClick={handleImportPostmanEnvironment}>
|
||||
Postman Environment
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleImportPostmanEnvironment}
|
||||
className="flex justify-center flex-col items-center w-full dark:bg-zinc-700 rounded-lg border-2 border-dashed border-zinc-300 dark:border-zinc-400 p-12 text-center hover:border-zinc-400 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:ring-offset-2"
|
||||
>
|
||||
<IconDatabaseImport size={64} />
|
||||
<span className="mt-2 block text-sm font-semibold">Import your Postman environments</span>
|
||||
</button>
|
||||
</Modal>
|
||||
</Portal>
|
||||
);
|
||||
|
@ -4,45 +4,61 @@ import CreateEnvironment from './CreateEnvironment';
|
||||
import EnvironmentList from './EnvironmentList';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import ImportEnvironment from './ImportEnvironment';
|
||||
import { IconFileAlert } from '@tabler/icons';
|
||||
|
||||
export const SharedButton = ({ children, className, onClick }) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className={`rounded bg-transparent px-2.5 py-2 w-fit text-xs font-semibold text-zinc-900 dark:text-zinc-50 shadow-sm ring-1 ring-inset ring-zinc-300 dark:ring-zinc-500 hover:bg-gray-50 dark:hover:bg-zinc-700
|
||||
${className}`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const DefaultTab = ({ setTab }) => {
|
||||
return (
|
||||
<div className="text-center items-center flex flex-col">
|
||||
<IconFileAlert size={64} strokeWidth={1} />
|
||||
<span className="font-semibold mt-2">No environments found</span>
|
||||
<span className="font-extralight mt-2 text-zinc-500 dark:text-zinc-400">
|
||||
Get started by using the following buttons :
|
||||
</span>
|
||||
<div className="flex items-center justify-center mt-6">
|
||||
<SharedButton onClick={() => setTab('create')}>
|
||||
<span>Create Environment</span>
|
||||
</SharedButton>
|
||||
|
||||
<span className="mx-4">Or</span>
|
||||
|
||||
<SharedButton onClick={() => setTab('import')}>
|
||||
<span>Import Environment</span>
|
||||
</SharedButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const EnvironmentSettings = ({ collection, onClose }) => {
|
||||
const [isModified, setIsModified] = useState(false);
|
||||
const { environments } = collection;
|
||||
const [openCreateModal, setOpenCreateModal] = useState(false);
|
||||
const [openImportModal, setOpenImportModal] = useState(false);
|
||||
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
|
||||
|
||||
const [tab, setTab] = useState('default');
|
||||
if (!environments || !environments.length) {
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<Modal
|
||||
size="md"
|
||||
title="Environments"
|
||||
confirmText={'Close'}
|
||||
handleConfirm={onClose}
|
||||
handleCancel={onClose}
|
||||
hideCancel={true}
|
||||
>
|
||||
{openCreateModal && <CreateEnvironment collection={collection} onClose={() => setOpenCreateModal(false)} />}
|
||||
{openImportModal && <ImportEnvironment collection={collection} onClose={() => setOpenImportModal(false)} />}
|
||||
<div className="text-center flex flex-col">
|
||||
<p>No environments found!</p>
|
||||
<button
|
||||
className="btn-create-environment text-link pr-2 py-3 mt-2 select-none"
|
||||
onClick={() => setOpenCreateModal(true)}
|
||||
>
|
||||
<span>Create Environment</span>
|
||||
</button>
|
||||
|
||||
<span>Or</span>
|
||||
|
||||
<button
|
||||
className="btn-import-environment text-link pl-2 pr-2 py-3 select-none"
|
||||
onClick={() => setOpenImportModal(true)}
|
||||
>
|
||||
<span>Import Environment</span>
|
||||
</button>
|
||||
</div>
|
||||
<Modal size="md" title="Environments" handleCancel={onClose} hideCancel={true} hideFooter={true}>
|
||||
{tab === 'create' ? (
|
||||
<CreateEnvironment collection={collection} onClose={() => setTab('default')} />
|
||||
) : tab === 'import' ? (
|
||||
<ImportEnvironment collection={collection} onClose={() => setTab('default')} />
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<DefaultTab setTab={setTab} />
|
||||
</Modal>
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
@ -42,7 +42,7 @@ const FilePickerEditor = ({ value, onChange, collection }) => {
|
||||
};
|
||||
|
||||
const clear = () => {
|
||||
onChange('');
|
||||
onChange([]);
|
||||
};
|
||||
|
||||
const renderButtonText = (filenames) => {
|
||||
|
@ -116,6 +116,7 @@ const Headers = ({ collection, folder }) => {
|
||||
)
|
||||
}
|
||||
collection={collection}
|
||||
item={folder}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -44,8 +44,8 @@ const Script = ({ collection, folder }) => {
|
||||
<div className="text-xs mb-4 text-muted">
|
||||
Pre and post-request scripts that will run before and after any request inside this folder is sent.
|
||||
</div>
|
||||
<div className="flex-1 mt-2">
|
||||
<div className="mb-1 title text-xs">Pre Request</div>
|
||||
<div className="flex flex-col flex-1 mt-2 gap-y-2">
|
||||
<div className="title text-xs">Pre Request</div>
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
value={requestScript || ''}
|
||||
@ -54,10 +54,11 @@ const Script = ({ collection, folder }) => {
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 mt-6">
|
||||
<div className="mt-1 mb-1 title text-xs">Post Response</div>
|
||||
<div className="flex flex-col flex-1 mt-2 gap-y-2">
|
||||
<div className="title text-xs">Post Response</div>
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
value={responseScript || ''}
|
||||
@ -66,6 +67,7 @@ const Script = ({ collection, folder }) => {
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -37,6 +37,7 @@ const Tests = ({ collection, folder }) => {
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
/>
|
||||
|
||||
<div className="mt-6">
|
||||
|
@ -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 Tooltip from 'components/Tooltip';
|
||||
import InfoTip from 'components/InfoTip';
|
||||
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>
|
||||
<Tooltip text="You can write any valid JS Template Literal here" tooltipId="request-var" />
|
||||
<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>
|
||||
<Tooltip text="You can write any valid JS expression here" tooltipId="response-var" />
|
||||
<InfoTip text="You can write any valid JS expression here" infotipId="response-var" />
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
@ -130,6 +130,7 @@ const VarsTable = ({ folder, collection, vars, varType }) => {
|
||||
)
|
||||
}
|
||||
collection={collection}
|
||||
item={folder}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -12,15 +12,15 @@ const FolderSettings = ({ collection, folder }) => {
|
||||
const dispatch = useDispatch();
|
||||
let tab = 'headers';
|
||||
const { folderLevelSettingsSelectedTab } = collection;
|
||||
if (folderLevelSettingsSelectedTab?.[folder.uid]) {
|
||||
tab = folderLevelSettingsSelectedTab[folder.uid];
|
||||
if (folderLevelSettingsSelectedTab?.[folder?.uid]) {
|
||||
tab = folderLevelSettingsSelectedTab[folder?.uid];
|
||||
}
|
||||
|
||||
const setTab = (tab) => {
|
||||
dispatch(
|
||||
updatedFolderSettingsSelectedTab({
|
||||
collectionUid: collection.uid,
|
||||
folderUid: folder.uid,
|
||||
collectionUid: collection?.uid,
|
||||
folderUid: folder?.uid,
|
||||
tab
|
||||
})
|
||||
);
|
||||
@ -50,7 +50,7 @@ const FolderSettings = ({ collection, folder }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<StyledWrapper className="flex flex-col h-full">
|
||||
<div className="flex flex-col h-full relative px-4 py-4">
|
||||
<div className="flex flex-wrap items-center tabs" role="tablist">
|
||||
<div className={getTabClassname('headers')} role="tab" onClick={() => setTab('headers')}>
|
||||
|
16
packages/bruno-app/src/components/Icons/Dot/index.js
Normal file
16
packages/bruno-app/src/components/Icons/Dot/index.js
Normal file
@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
const DotIcon = ({ width }) => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={width}
|
||||
viewBox="0 0 24 24" strokeWidth="1.5"
|
||||
stroke="currentColor" fill="none" strokeLinecap="round" strokeLinejoin="round"
|
||||
className='inline-block'
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 7a5 5 0 1 1 -4.995 5.217l-.005 -.217l.005 -.217a5 5 0 0 1 4.995 -4.783z" stroke-width="0" fill="currentColor" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default DotIcon;
|
@ -1,26 +1,25 @@
|
||||
import React from 'react';
|
||||
import { Tooltip as ReactTooltip } from 'react-tooltip';
|
||||
import { Tooltip as ReactInfoTip } from 'react-tooltip';
|
||||
|
||||
const Tooltip = ({ text, tooltipId }) => {
|
||||
const InfoTip = ({ text, infotipId }) => {
|
||||
return (
|
||||
<>
|
||||
<svg
|
||||
tabIndex="-1"
|
||||
id={tooltipId}
|
||||
id={infotipId}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="14"
|
||||
height="14"
|
||||
fill="currentColor"
|
||||
className="inline-block ml-2 cursor-pointer"
|
||||
viewBox="0 0 16 16"
|
||||
style={{ marginTop: 1 }}
|
||||
>
|
||||
<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>
|
||||
<ReactTooltip anchorId={tooltipId} html={text} />
|
||||
<ReactInfoTip anchorId={infotipId} html={text} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tooltip;
|
||||
export default InfoTip;
|
@ -2,7 +2,6 @@ import styled from 'styled-components';
|
||||
|
||||
const StyledMarkdownBodyWrapper = styled.div`
|
||||
background: transparent;
|
||||
height: inherit;
|
||||
.markdown-body {
|
||||
background: transparent;
|
||||
overflow-y: auto;
|
||||
@ -70,6 +69,7 @@ const StyledMarkdownBodyWrapper = styled.div`
|
||||
|
||||
pre {
|
||||
background: ${(props) => props.theme.sidebar.bg};
|
||||
color: ${(props) => props.theme.text};
|
||||
}
|
||||
|
||||
table {
|
||||
|
@ -1,15 +1,35 @@
|
||||
import MarkdownIt from 'markdown-it';
|
||||
import * as MarkdownItReplaceLink from 'markdown-it-replace-link';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import * as React from 'react';
|
||||
import React from 'react';
|
||||
|
||||
const md = new MarkdownIt();
|
||||
const Markdown = ({ collectionPath, onDoubleClick, content }) => {
|
||||
const markdownItOptions = {
|
||||
replaceLink: function (link, env) {
|
||||
return link.replace(/^\./, collectionPath);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOnClick = (event) => {
|
||||
const target = event.target;
|
||||
if (target.tagName === 'A') {
|
||||
event.preventDefault();
|
||||
const href = target.getAttribute('href');
|
||||
if (href) {
|
||||
window.open(href, '_blank');
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Markdown = ({ onDoubleClick, content }) => {
|
||||
const handleOnDoubleClick = (event) => {
|
||||
if (event?.detail === 2) {
|
||||
if (event.detail === 2) {
|
||||
onDoubleClick();
|
||||
}
|
||||
};
|
||||
|
||||
const md = new MarkdownIt(markdownItOptions).use(MarkdownItReplaceLink);
|
||||
|
||||
const htmlFromMarkdown = md.render(content || '');
|
||||
|
||||
return (
|
||||
@ -17,7 +37,8 @@ const Markdown = ({ onDoubleClick, content }) => {
|
||||
<div
|
||||
className="markdown-body"
|
||||
dangerouslySetInnerHTML={{ __html: htmlFromMarkdown }}
|
||||
onClick={handleOnDoubleClick}
|
||||
onClick={handleOnClick}
|
||||
onDoubleClick={handleOnDoubleClick}
|
||||
/>
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
@ -1,10 +1,10 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const ModalHeader = ({ title, handleCancel, customHeader }) => (
|
||||
const ModalHeader = ({ title, handleCancel, customHeader, hideClose }) => (
|
||||
<div className="bruno-modal-header">
|
||||
{customHeader ? customHeader : <>{title ? <div className="bruno-modal-header-title">{title}</div> : null}</>}
|
||||
{handleCancel ? (
|
||||
{handleCancel && !hideClose ? (
|
||||
<div className="close cursor-pointer" onClick={handleCancel ? () => handleCancel() : null}>
|
||||
×
|
||||
</div>
|
||||
@ -63,6 +63,7 @@ const Modal = ({
|
||||
confirmDisabled,
|
||||
hideCancel,
|
||||
hideFooter,
|
||||
hideClose,
|
||||
disableCloseOnOutsideClick,
|
||||
disableEscapeKey,
|
||||
onClick,
|
||||
@ -100,7 +101,12 @@ const Modal = ({
|
||||
return (
|
||||
<StyledWrapper className={classes} onClick={onClick ? (e) => onClick(e) : null}>
|
||||
<div className={`bruno-modal-card modal-${size}`}>
|
||||
<ModalHeader title={title} handleCancel={() => closeModal({ type: 'icon' })} customHeader={customHeader} />
|
||||
<ModalHeader
|
||||
title={title}
|
||||
hideClose={hideClose}
|
||||
handleCancel={() => closeModal({ type: 'icon' })}
|
||||
customHeader={customHeader}
|
||||
/>
|
||||
<ModalContent>{children}</ModalContent>
|
||||
<ModalFooter
|
||||
confirmText={confirmText}
|
||||
|
@ -23,7 +23,7 @@ const StyledWrapper = styled.div`
|
||||
overflow: hidden !important;
|
||||
${'' /* padding-bottom: 50px !important; */}
|
||||
position: relative;
|
||||
display: contents;
|
||||
display: block;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ class MultiLineEditor extends Component {
|
||||
componentDidMount() {
|
||||
// Initialize CodeMirror as a single line editor
|
||||
/** @type {import("codemirror").Editor} */
|
||||
const variables = getAllVariables(this.props.collection, this.props.item);
|
||||
|
||||
this.editor = CodeMirror(this.editorRef.current, {
|
||||
lineWrapping: false,
|
||||
lineNumbers: false,
|
||||
@ -31,7 +33,7 @@ class MultiLineEditor extends Component {
|
||||
placeholder: this.props.placeholder,
|
||||
mode: 'brunovariables',
|
||||
brunoVarInfo: {
|
||||
variables: getAllVariables(this.props.collection)
|
||||
variables
|
||||
},
|
||||
scrollbarStyle: null,
|
||||
tabindex: 0,
|
||||
@ -86,7 +88,7 @@ class MultiLineEditor extends Component {
|
||||
}
|
||||
this.editor.setValue(String(this.props.value) || '');
|
||||
this.editor.on('change', this._onEdit);
|
||||
this.addOverlay();
|
||||
this.addOverlay(variables);
|
||||
}
|
||||
|
||||
_onEdit = () => {
|
||||
@ -104,10 +106,10 @@ class MultiLineEditor extends Component {
|
||||
// event loop.
|
||||
this.ignoreChangeEvent = true;
|
||||
|
||||
let variables = getAllVariables(this.props.collection);
|
||||
let variables = getAllVariables(this.props.collection, this.props.item);
|
||||
if (!isEqual(variables, this.variables)) {
|
||||
this.editor.options.brunoVarInfo.variables = variables;
|
||||
this.addOverlay();
|
||||
this.addOverlay(variables);
|
||||
}
|
||||
if (this.props.theme !== prevProps.theme && this.editor) {
|
||||
this.editor.setOption('theme', this.props.theme === 'dark' ? 'monokai' : 'default');
|
||||
@ -126,10 +128,8 @@ class MultiLineEditor extends Component {
|
||||
this.editor.getWrapperElement().remove();
|
||||
}
|
||||
|
||||
addOverlay = () => {
|
||||
let variables = getAllVariables(this.props.collection);
|
||||
addOverlay = (variables) => {
|
||||
this.variables = variables;
|
||||
|
||||
defineCodeMirrorBrunoVariablesMode(variables, 'text/plain');
|
||||
this.editor.setOption('mode', 'brunovariables');
|
||||
};
|
||||
|
@ -10,6 +10,8 @@ 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;
|
||||
|
||||
@ -20,6 +22,7 @@ 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;
|
||||
@ -85,21 +88,22 @@ 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 hover:text-gray-700 ${unreadNotifications?.length > 0 ? 'bell' : ''}`}
|
||||
className={`mr-2 ${unreadNotifications?.length > 0 ? 'bell' : ''}`}
|
||||
/>
|
||||
{unreadNotifications.length > 0 && (
|
||||
<span className="notification-count text-xs">{unreadNotifications.length}</span>
|
||||
)}
|
||||
</ToolHint>
|
||||
</a>
|
||||
|
||||
{showNotificationsModal && (
|
||||
@ -129,8 +133,7 @@ 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)}
|
||||
>
|
||||
@ -141,8 +144,7 @@ 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}
|
||||
>
|
||||
@ -159,8 +161,7 @@ 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}
|
||||
>
|
||||
|
@ -9,17 +9,25 @@ 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 handleInputChange = (event) => {
|
||||
const handleCodeFontChange = (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
|
||||
codeFont,
|
||||
codeFontSize
|
||||
}
|
||||
})
|
||||
).then(() => {
|
||||
@ -29,6 +37,8 @@ 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"
|
||||
@ -37,9 +47,23 @@ const Font = ({ close }) => {
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={handleInputChange}
|
||||
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>
|
||||
|
||||
<div className="mt-10">
|
||||
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||
|
@ -2,7 +2,7 @@ import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
.settings-label {
|
||||
width: 80px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.textbox {
|
||||
@ -20,6 +20,12 @@ const StyledWrapper = styled.div`
|
||||
outline: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.system-proxy-settings {
|
||||
label {
|
||||
color: ${(props) => props.theme.colors.text.yellow};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
@ -11,14 +11,17 @@ 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({
|
||||
enabled: Yup.boolean(),
|
||||
mode: Yup.string().oneOf(['off', 'on', 'system']),
|
||||
protocol: Yup.string().required().oneOf(['http', 'https', 'socks4', 'socks5']),
|
||||
hostname: Yup.string()
|
||||
.when('enabled', {
|
||||
is: true,
|
||||
is: 'on',
|
||||
then: (hostname) => hostname.required('Specify the hostname for your proxy.'),
|
||||
otherwise: (hostname) => hostname.nullable()
|
||||
})
|
||||
@ -31,7 +34,7 @@ const ProxySettings = ({ close }) => {
|
||||
.transform((_, val) => (val ? Number(val) : null)),
|
||||
auth: Yup.object()
|
||||
.when('enabled', {
|
||||
is: true,
|
||||
is: 'on',
|
||||
then: Yup.object({
|
||||
enabled: Yup.boolean(),
|
||||
username: Yup.string()
|
||||
@ -54,7 +57,7 @@ const ProxySettings = ({ close }) => {
|
||||
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
enabled: preferences.proxy.enabled || false,
|
||||
mode: preferences.proxy.mode,
|
||||
protocol: preferences.proxy.protocol || 'http',
|
||||
hostname: preferences.proxy.hostname || '',
|
||||
port: preferences.proxy.port || 0,
|
||||
@ -94,7 +97,7 @@ const ProxySettings = ({ close }) => {
|
||||
|
||||
useEffect(() => {
|
||||
formik.setValues({
|
||||
enabled: preferences.proxy.enabled || false,
|
||||
mode: preferences.proxy.mode,
|
||||
protocol: preferences.proxy.protocol || 'http',
|
||||
hostname: preferences.proxy.hostname || '',
|
||||
port: preferences.proxy.port || '',
|
||||
@ -109,14 +112,81 @@ 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 className="settings-label" htmlFor="protocol">
|
||||
Mode
|
||||
</label>
|
||||
<div className="flex items-center">
|
||||
<label className="flex items-center cursor-pointer">
|
||||
<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'}
|
||||
onChange={formik.handleChange}
|
||||
className="mr-1 cursor-pointer"
|
||||
/>
|
||||
System Proxy
|
||||
</label>
|
||||
<input type="checkbox" name="enabled" checked={formik.values.enabled} onChange={formik.handleChange} />
|
||||
</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>
|
||||
) : null}
|
||||
{formik?.values?.mode === 'on' ? (
|
||||
<>
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="protocol">
|
||||
Protocol
|
||||
@ -168,7 +238,6 @@ const ProxySettings = ({ close }) => {
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-3 flex items-center">
|
||||
<label className="settings-label" htmlFor="hostname">
|
||||
Hostname
|
||||
@ -291,6 +360,8 @@ const ProxySettings = ({ close }) => {
|
||||
<div className="ml-3 text-red-500">{formik.errors.bypassProxy}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
<div className="mt-6">
|
||||
<button type="submit" className="submit btn btn-md btn-secondary">
|
||||
Save
|
||||
|
@ -1,39 +1,42 @@
|
||||
import React from 'react';
|
||||
import { IconSpeakerphone, IconBrandTwitter, IconBrandGithub, IconBrandDiscord, IconBook } from '@tabler/icons';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const Support = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="rows">
|
||||
<div className="mt-2">
|
||||
<a href="https://docs.usebruno.com" target="_blank" className="flex items-end">
|
||||
<IconBook size={18} strokeWidth={2} />
|
||||
<span className="label ml-2">Documentation</span>
|
||||
<span className="label ml-2">{t('COMMON.DOCUMENTATION')}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<a href="https://github.com/usebruno/bruno/issues" target="_blank" className="flex items-end">
|
||||
<IconSpeakerphone size={18} strokeWidth={2} />
|
||||
<span className="label ml-2">Report Issues</span>
|
||||
<span className="label ml-2">{t('COMMON.REPORT_ISSUES')}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<a href="https://discord.com/invite/KgcZUncpjq" target="_blank" className="flex items-end">
|
||||
<IconBrandDiscord size={18} strokeWidth={2} />
|
||||
<span className="label ml-2">Discord</span>
|
||||
<span className="label ml-2">{t('COMMON.DISCORD')}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<a href="https://github.com/usebruno/bruno" target="_blank" className="flex items-end">
|
||||
<IconBrandGithub size={18} strokeWidth={2} />
|
||||
<span className="label ml-2">GitHub</span>
|
||||
<span className="label ml-2">{t('COMMON.GITHUB')}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<a href="https://twitter.com/use_bruno" target="_blank" className="flex items-end">
|
||||
<IconBrandTwitter size={18} strokeWidth={2} />
|
||||
<span className="label ml-2">Twitter</span>
|
||||
<span className="label ml-2">{t('COMMON.TWITTER')}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
112
packages/bruno-app/src/components/ReorderTable/index.js
Normal file
112
packages/bruno-app/src/components/ReorderTable/index.js
Normal file
@ -0,0 +1,112 @@
|
||||
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;
|
@ -1,7 +1,4 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from 'providers/Theme/index';
|
||||
import darkTheme from 'themes/dark';
|
||||
import lightTheme from 'themes/light';
|
||||
|
||||
/**
|
||||
* Assertion operators
|
||||
@ -81,16 +78,10 @@ const AssertionOperator = ({ operator, onChange }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<select value={operator} onChange={handleChange} className="mousetrap">
|
||||
{operators.map((operator) => (
|
||||
<option
|
||||
style={{ backgroundColor: storedTheme === 'dark' ? darkTheme.bg : lightTheme.bg }}
|
||||
key={operator}
|
||||
value={operator}
|
||||
>
|
||||
<option key={operator} value={operator}>
|
||||
{getLabel(operator)}
|
||||
</option>
|
||||
))}
|
||||
|
@ -191,6 +191,7 @@ const AssertionRow = ({
|
||||
}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
) : (
|
||||
<input type="text" className="cursor-default" disabled />
|
||||
|
@ -55,6 +55,9 @@ const Wrapper = styled.div`
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
option {
|
||||
background-color: ${(props) => props.theme.bg};
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
|
@ -136,6 +136,7 @@ const AwsV4Auth = ({ onTokenChange, item, collection }) => {
|
||||
onChange={(val) => handleAccessKeyIdChange(val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -148,6 +149,8 @@ const AwsV4Auth = ({ onTokenChange, item, collection }) => {
|
||||
onChange={(val) => handleSecretAccessKeyChange(val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
isSecret={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -160,6 +163,7 @@ const AwsV4Auth = ({ onTokenChange, item, collection }) => {
|
||||
onChange={(val) => handleSessionTokenChange(val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -172,6 +176,7 @@ const AwsV4Auth = ({ onTokenChange, item, collection }) => {
|
||||
onChange={(val) => handleServiceChange(val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -184,6 +189,7 @@ const AwsV4Auth = ({ onTokenChange, item, collection }) => {
|
||||
onChange={(val) => handleRegionChange(val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -196,6 +202,7 @@ const AwsV4Auth = ({ onTokenChange, item, collection }) => {
|
||||
onChange={(val) => handleProfileNameChange(val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
|
@ -55,6 +55,7 @@ const BasicAuth = ({ item, collection }) => {
|
||||
onChange={(val) => handleUsernameChange(val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -67,6 +68,8 @@ const BasicAuth = ({ item, collection }) => {
|
||||
onChange={(val) => handlePasswordChange(val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
isSecret={true}
|
||||
/>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
|
@ -42,6 +42,8 @@ const BearerAuth = ({ item, collection }) => {
|
||||
onChange={(val) => handleTokenChange(val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
isSecret={true}
|
||||
/>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
|
@ -55,6 +55,7 @@ const DigestAuth = ({ item, collection }) => {
|
||||
onChange={(val) => handleUsernameChange(val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -67,6 +68,8 @@ const DigestAuth = ({ item, collection }) => {
|
||||
onChange={(val) => handlePasswordChange(val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
isSecret={true}
|
||||
/>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
|
@ -80,7 +80,7 @@ const OAuth2AuthorizationCode = ({ item, collection }) => {
|
||||
return (
|
||||
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||
{inputsConfig.map((input) => {
|
||||
const { key, label } = input;
|
||||
const { key, label, isSecret } = input;
|
||||
return (
|
||||
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
||||
<label className="block font-medium">{label}</label>
|
||||
@ -92,6 +92,8 @@ const OAuth2AuthorizationCode = ({ item, collection }) => {
|
||||
onChange={(val) => handleChange(key, val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
isSecret={isSecret}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -17,7 +17,8 @@ const inputsConfig = [
|
||||
},
|
||||
{
|
||||
key: 'clientSecret',
|
||||
label: 'Client Secret'
|
||||
label: 'Client Secret',
|
||||
isSecret: true
|
||||
},
|
||||
{
|
||||
key: 'scope',
|
||||
|
@ -43,7 +43,7 @@ const OAuth2ClientCredentials = ({ item, collection }) => {
|
||||
return (
|
||||
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||
{inputsConfig.map((input) => {
|
||||
const { key, label } = input;
|
||||
const { key, label, isSecret } = input;
|
||||
return (
|
||||
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
||||
<label className="block font-medium">{label}</label>
|
||||
@ -55,6 +55,8 @@ const OAuth2ClientCredentials = ({ item, collection }) => {
|
||||
onChange={(val) => handleChange(key, val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
isSecret={isSecret}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -9,7 +9,8 @@ const inputsConfig = [
|
||||
},
|
||||
{
|
||||
key: 'clientSecret',
|
||||
label: 'Client Secret'
|
||||
label: 'Client Secret',
|
||||
isSecret: true
|
||||
},
|
||||
{
|
||||
key: 'scope',
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user