Compare commits

...

427 Commits

Author SHA1 Message Date
Anoop M D
2dadad3af0 Merge branch 'main' into feature/env-secrets 2023-09-24 23:11:45 +05:30
Anoop M D
eaa31342dc Merge pull request #207 from mirkogolze/feature/env-secrets
#199 improve code to check given env vars correctly
2023-09-24 23:07:08 +05:30
Anoop M D
c4fd9d38a5 Merge pull request #208 from mirkogolze/feature/cli-env-vars
#199 bring feature cli overridable env vars to main
2023-09-24 23:05:19 +05:30
Anoop M D
9c4c219b99 feat(#199): Env Secrets - UI and Electron Layer updates 2023-09-24 23:02:39 +05:30
Mirko Golze
8e22aa2fca #199 improve code to check given envvars correctly 2023-09-24 15:40:04 +02:00
Mirko Golze
6b9e085696 #199 small code refactoring 2023-09-24 15:39:56 +02:00
Mirko Golze
74282706aa #199 add CLI feature to use command line parameter '--env-var secret=xzy123' 2023-09-24 15:39:51 +02:00
Mirko Golze
aa88aa73a2 #199 improve code to check given envvars correctly 2023-09-24 15:28:33 +02:00
Anoop M D
f78c1640e9 feat(#199): electron safeStorage util for storing secrets with aes256 fallback 2023-09-24 17:49:28 +05:30
Anoop M D
a5a17cf8eb fix(#131): fixed macos ctrl+a select all issue 2023-09-24 02:47:05 +05:30
Anoop M D
c5a86cb343 feat: documentation link in app titlebar 2023-09-24 02:33:58 +05:30
Anoop M D
9b94cddc9b Merge pull request #204 from jeffprinty/enhancement/add-about-window
Issue #203 Add about-window to help menu
2023-09-24 02:10:23 +05:30
Anoop M D
0a172ddce8 feat(#206): Collection and Env variables viewer 2023-09-24 02:07:31 +05:30
Jeff Printy
aea1cbba9e Issue #203 Add about-window to help menu 2023-09-22 23:17:53 -05:00
Anoop M D
7a1b44858d Merge pull request #202 from mirkogolze/feature/env-secrets
#199 add CLI feature to use command line parameter '--env-var' secret=…
2023-09-23 03:18:24 +05:30
Anoop M D
1c89ab3450 fix: fixed issue where vm2 instantiated objects were not being sent to renderer process 2023-09-23 03:14:27 +05:30
Anoop M D
e3ce420216 feat(#122): supporting process.env vars in UI and electron layer 2023-09-23 02:55:54 +05:30
Anoop M D
c91fef2264 chore: refactor electron storage related modules 2023-09-22 20:38:45 +05:30
Mirko Golze
c83fce16dc #199 small code refactoring 2023-09-22 09:22:46 +02:00
Mirko Golze
5415e20d7e #199 add CLI feature to use command line parameter '--env-var secret=xzy123' 2023-09-21 22:17:46 +02:00
Anoop M D
2f45b95930 feat(#199): bru lang updates to store environment secrets 2023-09-22 01:08:35 +05:30
Anoop M D
4531cfc994 chore(#197): ran prettier on tests folder 2023-09-22 00:45:42 +05:30
Anoop M D
bd0738198c chore(#197): ran prettier on packages/bruno-testbench 2023-09-22 00:44:47 +05:30
Anoop M D
9a81793151 chore(#197): ran prettier on packages/bruno-tauri 2023-09-22 00:44:28 +05:30
Anoop M D
88c16fa388 chore(#197): ran prettier on packages/bruno-schema 2023-09-22 00:43:56 +05:30
Anoop M D
f68eacfe0d chore(#197): ran prettier on packages/bruno-query 2023-09-22 00:43:31 +05:30
Anoop M D
116e050987 chore(#197): ran prettier on packages/bruno-lang 2023-09-22 00:42:48 +05:30
Anoop M D
5af2f68252 chore(#197): ran prettier on packages/bruno-js 2023-09-22 00:42:14 +05:30
Anoop M D
a53dd76854 chore(#197): ran prettier on packages/bruno-graphql-docs 2023-09-22 00:37:51 +05:30
Anoop M D
67fe264494 chore(#197): ran prettier on packages/bruno-electron 2023-09-22 00:36:42 +05:30
Anoop M D
ae692dde06 chore(#197): ran prettier on packages/bruno-cli 2023-09-22 00:34:11 +05:30
Anoop M D
1c4c5cc0c0 feat(#122): Support parsing of dotenv files 2023-09-22 00:27:27 +05:30
Anoop M D
19ca1af71e chore: release 0.14.1 2023-09-20 13:11:16 +05:30
Anoop M D
4016a83626 Merge pull request #201 from mirkogolze/bugfix/import-postman-header-check
#192 implement fallback to search body language by header 'content-type'
2023-09-20 13:04:25 +05:30
Mirko Golze
71b18c8b21 implement fallback to search body language by content-type header 2023-09-20 08:31:23 +02:00
Anoop M D
b53fcbb3d1 Merge pull request #198 from usebruno/feature/prettier-formatting
feat(#197): prettier formatting on all files in packages/bruno-app
2023-09-18 13:47:05 +05:30
Anoop M D
19a7f397bb feat(#197): prettier formatting on all files in packages/bruno-app 2023-09-18 13:37:00 +05:30
Anoop M D
a103f41d85 Merge pull request #196 from mirkogolze/main
check response type for ResponsePane CodeEditor
2023-09-18 13:20:16 +05:30
Mirko Golze
7d4d1573af set content-type header only if not set by user 2023-09-18 07:12:18 +02:00
Mirko Golze
6f2bb55ecf format change 2023-09-16 22:32:35 +02:00
Mirko Golze
f189cb1a2e check response type to show XML not surrounded by quotation marks and with highlighting 2023-09-16 22:24:35 +02:00
Anoop M D
c020cd94a8 chore: updated @bruno/js version in electron 2023-09-15 17:15:15 +05:30
Anoop M D
0d4f7e6a06 chore: bumped release version to v0.14.0 2023-09-14 00:25:04 +05:30
Anoop M D
f9ed68843d feat(#190): Console Logs in scripts should be written into developer console 2023-09-14 00:20:04 +05:30
Anoop M D
d3a56fdc82 fix(#151, #188): fixed issue where collections vars in tests were not being updated 2023-09-13 23:58:16 +05:30
Anoop M D
e6c3a5af4c feat: refactored run single request events 2023-09-13 23:37:21 +05:30
Anoop M D
cee8073bb7 Merge branch 'main' of github.com:usebruno/bruno 2023-09-13 18:10:22 +05:30
Anoop M D
69be52cf9e chore: electron notarization for mac 2023-09-13 18:09:59 +05:30
Anoop M D
9e400085e3 Merge pull request #187 from BrentShikoski/feature/support-custom-cacert
support custom cacert file
2023-09-09 13:59:05 +05:30
Brent Shikoski
b07bb67943 consistent formatting 2023-09-08 21:33:09 -05:00
Brent Shikoski
593210456a refactor code around https.Agent for flexibility 2023-09-08 21:23:27 -05:00
Brent Shikoski
e328a4615e cacert support through process environment vars 2023-09-08 20:56:12 -05:00
Brent Shikoski
18afb73238 support custom cacerts in electron app 2023-09-08 20:53:18 -05:00
Anoop M D
73b71e0829 release(#183): bruno cli v0.7.1 2023-09-07 12:31:21 +05:30
Anoop M D
4c25aa99aa Merge pull request #183 from BrentShikoski/feature/support-custom-cacert
support alternative cacert file in cli
2023-09-07 12:06:13 +05:30
Anoop M D
23843b5d0a chore: temporarily disabling mac notarization 2023-09-06 22:30:45 +05:30
Anoop M D
7fbd338fa6 feat: bru.getEnvName() 2023-09-06 20:36:55 +05:30
Anoop M D
4aeb5cf56d Merge pull request #182 from jeffprinty/feature/sidebar-collection-scrolling
Enable scrolling in collections sidebar #118
2023-09-05 12:42:36 +05:30
Brent Shikoski
1ed39a5ea6 support alternative cacert 2023-09-04 15:49:36 -05:00
Jeff Printy
74f248782b Enable scrolling in collections sidebar #118 2023-09-04 14:21:59 -05:00
Anoop M D
99239e19b4 release bruno cli v0.7.0 2023-09-04 12:24:18 +05:30
Anoop M D
f46160e161 Merge pull request #181 from BrentShikoski/feature/cli-certificate-validation
add ability to turn certificate validation off in the cli
2023-09-04 12:09:28 +05:30
Brent Shikoski
d0147778db add ability to turn certificate validation off 2023-09-03 20:43:32 -05:00
Brent Shikoski
87119cee2e add ability to turn certificate validation off 2023-09-03 20:19:56 -05:00
Anoop M D
b25d896dd6 fix(#178): fixed await issue while running scripts 2023-09-01 17:49:34 +05:30
Anoop M D
08495e7fb5 fix(#178): fixed issue in bruno cli where collection run was getting aborted for 4xx/5xx responses 2023-09-01 14:18:41 +05:30
Anoop M D
ee084696f5 chore: release v0.13.2 2023-09-01 14:17:29 +05:30
Anoop M D
aeb9fc8875 chore: release bruno packages to npm 2023-09-01 13:43:26 +05:30
Anoop M D
26a05f92cb chore: bumped version v0.13.1 2023-09-01 00:15:14 +05:30
Anoop M D
fff7819c46 fix(#177): fixed issue where collection run was getting aborted after 4xx/5xx response 2023-09-01 00:13:31 +05:30
Anoop M D
1d678ee0d9 chore: bump version to 0.13.0 2023-08-31 23:03:34 +05:30
Anoop M D
fd4c188c95 feat(#177 #165): running tests and assertions for error responses 2023-08-30 21:35:59 +05:30
Anoop M D
97678b05fc Merge branch 'main' of github.com:usebruno/bruno 2023-08-30 20:31:57 +05:30
Anoop M D
a1c9625aee feat(#168): disable ssl option 2023-08-30 20:31:15 +05:30
Anoop M D
a9d74467ff Merge pull request #176 from LesageYann/chore/doc-improve-suggest
chore: improve the cli doc to avoid misunderstandings
2023-08-29 20:05:55 +05:30
Lesage Yann
bd670eceb6 chore: improve the cli doc to avoid misunderstandings on the command: 2023-08-29 11:36:35 +02:00
Anoop M D
1ccb66e92a Merge pull request #173 from dcoomber/feature/48-husky-prettier
Added prettier precommit hook with husky Re #48
2023-08-29 10:47:09 +05:30
Anoop M D
d62881fe0d feat: updated electron builder config to use js based config 2023-08-28 23:14:40 +05:30
David Coomber
9ea95b4571 Copy prettier config from packages/bruno-app Re #48 2023-08-19 10:40:54 +02:00
David Coomber
df9322b767 Added prettier precommit hook with husky Re #48 2023-08-19 09:20:46 +02:00
Anoop M D
c27c750c3e feat: preferences local storage and electron sync 2023-08-19 00:36:37 +05:30
Anoop M D
94baee8e25 feat: Preferences (General Tab) 2023-08-18 01:09:00 +05:30
Anoop M D
417b50b0ad feat: Preferences (Theme and Support) 2023-08-18 00:18:30 +05:30
Anoop M D
bf5ee7e409 Merge pull request #171 from dcoomber/feature/process-env-secrets
RFC: Support process.env in Bru script Re #170
2023-08-17 23:00:41 +05:30
David Coomber
3ca0107f1b Proposal to support process.env in Bru script Re #170 2023-08-11 19:48:09 +02:00
Anoop M D
ec22fdb637 chore: initial commit for bruno docs package 2023-05-19 19:57:58 +05:30
Anoop M D
66024d04e9 chore: version bump 2023-05-02 15:26:27 +05:30
Anoop M D
330a7ad18a fix: fixed bug where content type was getting overwritten everytime as application/x-www-form-urlencoded 2023-05-02 15:25:07 +05:30
Anoop M D
2976842588 chore: version bump for bruno cli v0.5.0 2023-04-30 12:10:08 +05:30
Anoop M D
51e0ea2c2d feat(#155): exit process with non zero code when tests or assertions fail 2023-04-30 12:08:23 +05:30
Anoop M D
3b85e7ebcc chore: version bump v0.12.1 2023-04-27 19:26:41 +05:30
Anoop M D
49b0f3a322 fix(#148) : fixed issue where form url encoded params were not being interpolated 2023-04-27 19:25:52 +05:30
Anoop M D
45ca5ded96 chore: bumped version to v0.12.0 2023-04-20 11:54:36 +05:30
Anoop M D
b6528062f0 fix: fixed issue where postman collection import was failing when the filename had / or ? chars (#147) 2023-04-20 11:46:55 +05:30
Anoop M D
86094cc054 fix: fixed issue where cancelling requests was throwing an error (#146) 2023-04-20 11:19:12 +05:30
Anoop M D
8e0bc68ada feat: support node-fetch as an inbuilt library (#138) 2023-04-20 10:51:29 +05:30
Anoop M D
c36c7b44a6 Merge pull request #145 from DivyMohan14/feature/async-pre-request-scripts
Adding support for using async pre-request scripts
2023-04-20 09:55:06 +05:30
Divy Mohan Rai
0ac27dee56 feature(async-script): adding support for using async pre-request scripts 2023-04-15 12:33:45 +05:30
Anoop M D
ede122ab09 fix: fix image rendering issues in bru cli npm readme 2023-04-01 13:57:59 +05:30
Anoop M D
96e368cb18 fix: fixed bru cli typos 2023-04-01 13:55:32 +05:30
Anoop M D
942b75861c fix: fixed broken link in bruno cli readme 2023-04-01 13:54:33 +05:30
Anoop M D
c1711ea01b fix: fixing npm image rendering issues in readme 2023-04-01 13:53:32 +05:30
Anoop M D
dedfefbc9a chore: release cli docs to npm 2023-04-01 13:48:35 +05:30
Anoop M D
65dd5df87e chore: added screenshot of bru cli output 2023-04-01 13:46:13 +05:30
Anoop M D
78d2393686 chore: prep for bruno-cli release 2023-04-01 13:40:23 +05:30
Anoop M D
0b65c4580e chore: updated image 2023-03-30 11:04:20 +05:30
Anoop M D
9cc1bf1e2f chore: updated readme 2023-03-30 10:56:37 +05:30
Anoop M D
b96e3d0f23 release: v0.11.0 2023-03-29 13:00:03 +05:30
Anoop M D
11c99d55dc fix: fixed yml indentation issue 2023-03-29 12:58:03 +05:30
Anoop M D
d054dc4c78 feat(#105): github workflow for release updates to homebrew 2023-03-29 12:55:49 +05:30
Anoop M D
9014dc5769 fix: fixed dark mode styling issue in assertions op selector 2023-03-29 12:49:17 +05:30
Anoop M D
d346970241 fix: fixed issue where unsaved changes where not being picked up while running tests (#125) 2023-03-29 12:43:47 +05:30
Anoop M D
bdb3051c2b fix: fixed issue where env vars was not getting embedded inside xml req body (#141) 2023-03-29 12:22:42 +05:30
Anoop M D
f8325b22b3 fix: fixed issue where env vars was not getting embedded inside xml req body (#141) 2023-03-29 12:20:40 +05:30
Anoop M D
78251c530c feat: added custom assertion for chaijs for match() method 2023-03-23 21:36:35 +05:30
Anoop M D
dea95664b9 fix: fixed issue in bru cli where assertions was not being run 2023-03-23 21:35:41 +05:30
Anoop M D
fbc6e7bff5 Merge pull request #135 from dcoomber/bugfix/132-isjson-assertion
Resolve issue with to.be.json assertions Re #132
2023-03-23 14:11:55 +05:30
David Coomber
4884106aaa Removed chai-http Re #132 2023-03-22 22:12:17 +02:00
David Coomber
5c15438949 Updated plugin to be addProperty Re #132 2023-03-22 20:56:35 +02:00
Anoop M D
b53a9eaee9 Merge pull request #134 from dcoomber/bugfix/128-close-tab-hotkey
Proposed addition of CMD+W hotkey Re #128
2023-03-21 22:26:28 +05:30
David Coomber
5899ca446d Applied code review feedback Re #128 2023-03-21 17:45:26 +02:00
David Coomber
d21e7f6fb5 Added Chai.js plugin to cater for isJson assertion Re #132 2023-03-21 17:30:45 +02:00
Anoop M D
ee8a3eae8c Merge pull request #130 from dcoomber/bugfix/request-dialog-terminology
Proposed adjustment to terminology on requests
2023-03-21 01:34:19 +05:30
Anoop M D
fac5109242 Merge pull request #136 from dcoomber/bugfix/dev-docs
Corrected reference to bruno-query node script
2023-03-21 01:33:24 +05:30
David Coomber
47dfbd2a64 Corrected reference to bruno-query node script 2023-03-19 21:14:52 +02:00
David Coomber
074d72d885 Add chai-http to enable to.be.json assertions Re #132 2023-03-19 21:08:19 +02:00
David Coomber
8c29d131e2 Proposed addition of CMD+W hotkey Re #128 2023-03-19 18:38:08 +02:00
David Coomber
437044bdcd Applied code review feedback 2023-03-19 17:17:38 +02:00
Anoop M D
2120a562da chore: improved dev documentation 2023-03-19 15:41:18 +05:30
Anoop M D
04c3c2dbf1 Merge pull request #133 from bharathbdev/bugfix/assertion-result-issue
Bugfix/assertion result issue
2023-03-19 14:57:19 +05:30
David Coomber
1d03e1d5ea Adjusted terminology on requests (REST, GraphQL, Form URL encoded) 2023-03-18 10:53:49 +02:00
Bharath B
2b174e1c60 added the indentation 2023-03-18 13:43:16 +05:30
Bharath B
7a2b32069e bugfix/assertion-result-issue fixed the issue related to assertions still displayed in Tests tab after deletion#121 2023-03-18 12:06:20 +05:30
Anoop M D
a9e6c3a35c feat: support for importing insomnia collections (#74) 2023-03-05 00:19:03 +05:30
Anoop M D
e6a754b933 Merge pull request #108 from ajaishankar/feature/object-predicate
filter shortcut for scalar properties
2023-02-27 21:32:59 +05:30
Ajai Shankar
ee4509f037 feat(query): simple object predicate for scalar properties 2023-02-26 12:56:11 -06:00
Anoop M D
c04f0e7a71 chore: added docs link 2023-02-26 17:26:06 +05:30
Anoop M D
2f52ce4c71 feat: windows codesigning 2023-02-26 17:22:30 +05:30
Anoop M D
b1edaba1c6 fix: fixed issue in react hook order during search (#106) 2023-02-26 14:51:55 +05:30
Anoop M D
3f6fcdd582 Merge branch 'main' of github.com:usebruno/bruno 2023-02-23 12:37:47 +05:30
Anoop M D
c745786b1c chore: release v0.10.1 2023-02-23 12:37:34 +05:30
Anoop M D
9e30c7b440 feat: vars and asserts in gql request UI 2023-02-23 11:42:25 +05:30
Anoop M D
b87cc7ccae Merge pull request #104 from dcoomber/feature/update-development-doc
Added snippet to development.md
2023-02-22 23:48:07 +05:30
David Coomber
1595d736f2 Added snippet to assist in deleting node_modules / package-lock.json in dir structure 2023-02-22 20:08:48 +02:00
Anoop M D
b38c25ca70 feat: mac signinging and notarization 2023-02-22 19:15:37 +05:30
Anoop M D
f22858219b fix: fixed issue while deleting empty query params (#93) 2023-02-22 02:42:59 +05:30
Anoop M D
8044286b80 feat: integrated assert runtime for ui 2023-02-22 02:25:02 +05:30
Anoop M D
34a2e23dc6 feat: assertion operator in UI 2023-02-22 01:20:07 +05:30
Anoop M D
224b8c3cc4 feat: vars runtime in UI 2023-02-21 15:26:12 +05:30
Anoop M D
d58e92205b feat: assertions implementation in UI 2023-02-21 14:04:05 +05:30
Anoop M D
925af1f26f feat: vars implementation in UI 2023-02-21 13:05:51 +05:30
Anoop M D
d07744d5c2 chore: deleted unused chrome extension package 2023-02-21 00:22:20 +05:30
Anoop M D
5efb18ad63 chore: npm publish 2023-02-21 00:00:10 +05:30
Anoop M D
9cfb54ee9f Merge pull request #91 from ajaishankar/feature/get-supercharged
res.get : deep object navigation and filtering
2023-02-20 19:35:10 +05:30
Anoop M D
4c9d22d1e0 Merge pull request #99 from dcoomber/bugfix/createcollection-tab-order
Correct the tab order on the CreateCollection modal
2023-02-20 14:53:42 +05:30
Ajai Shankar
c5d43cc9e6 chore: add bruno-query test/build to github workflows 2023-02-19 23:51:47 -06:00
Ajai Shankar
8300830a95 Merge branch 'main' into feature/get-supercharged 2023-02-19 23:38:27 -06:00
Ajai Shankar
2dfc972930 feat: res default to bruno query 2023-02-19 23:35:49 -06:00
Ajai Shankar
4fdfdaf2cb feat(query): bruno-query package 2023-02-19 22:48:34 -06:00
David Coomber
a1385ba1e2 Location 'inputRef' was overriding the same on Name 2023-02-18 13:02:59 +02:00
Anoop M D
15804ac293 chore: updated bruno schema version 2023-02-17 14:20:36 +05:30
Anoop M D
0244b2e1d6 Merge pull request #98 from dcoomber/bugfix/contributing
Removed redundant instructions in docs
2023-02-17 14:06:05 +05:30
Anoop M D
7e70d05dc8 chore: bumped version to v0.9.4 2023-02-17 13:58:02 +05:30
Anoop M D
e60b06e4a4 chore: npm publish 2023-02-17 13:57:06 +05:30
Anoop M D
17ded5de4c fix: fixed issues with creating patch requests 2023-02-17 13:55:23 +05:30
Anoop M D
e1b97643bd fix: fixed issue with separators in electron menu #92 2023-02-17 13:47:13 +05:30
Anoop M D
8103554545 fix: disable app reload #94 2023-02-17 13:39:05 +05:30
Anoop M D
a425b42615 feat: ux improvements in environment settings 2023-02-17 13:36:22 +05:30
Anoop M D
b14f867811 chore: publish npm packages 2023-02-17 12:59:30 +05:30
Anoop M D
013abeaa80 fix: fixed parser issue related to env variables #97 2023-02-17 12:56:48 +05:30
David Coomber
9d3762702f Removed redundant local dev environment instructions 2023-02-16 21:22:35 +02:00
Anoop M D
cac9f9aef4 chore: updated dev docs 2023-02-16 01:59:07 +05:30
Anoop M D
2b63368f2c chore: updated dev docs 2023-02-16 01:58:11 +05:30
Anoop M D
acd980ffc6 chore: updated dev docs 2023-02-16 01:56:38 +05:30
Anoop M D
1a175e4449 chore: added bruno-js unit tests to github workflows 2023-02-13 13:11:56 +05:30
Ajai Shankar
209f30998e test: minor 2023-02-12 19:47:14 -06:00
Ajai Shankar
e777eed00d feat(get): supercharged res getter 2023-02-12 17:27:54 -06:00
Anoop M D
15fc24679c chore: release v0.9.3 2023-02-12 22:05:38 +05:30
Anoop M D
48d26c05d9 fix: fix windows filepath issues #89 2023-02-12 21:59:20 +05:30
Anoop M D
9d395ded33 Merge branch 'main' of github.com:usebruno/bruno 2023-02-12 21:46:58 +05:30
Anoop M D
943e74c327 fix: fix windows filepath issues #89 2023-02-12 21:46:42 +05:30
Anoop M D
b852d1cc52 Merge pull request #90 from ajaishankar/feature/expression-eval
Compiled and cached expressions
2023-02-11 22:57:17 +05:30
Ajai Shankar
3d22f77226 feat(eval): handle globals 2023-02-11 08:57:27 -06:00
Ajai Shankar
429ca4093c test: expression cache 2023-02-10 23:34:46 -06:00
Ajai Shankar
a4f757ee87 minor: clear expression cache before and after test 2023-02-10 22:24:28 -06:00
Ajai Shankar
df4f322024 feat(eval): compiled and cached expressions 2023-02-10 21:55:05 -06:00
Anoop M D
ddd39e630d chore: release v0.9.2 2023-02-09 17:56:25 +05:30
Anoop M D
ef8e8bf637 feat: improved error messaging while attempting to create duplicate requests and folders 2023-02-09 17:55:42 +05:30
Anoop M D
7405fa9709 chore: publish npm packages 2023-02-09 17:33:21 +05:30
Anoop M D
242fcac2d3 feat: bru lang now allows empty urls 2023-02-09 17:31:37 +05:30
Anoop M D
efd15838aa fix: fixed bug where gql imports were not working 2023-02-09 15:11:05 +05:30
Anoop M D
c55f9d42da feat: better error handling in bru cli 2023-02-08 18:27:33 +05:30
Anoop M D
2f32f7024e chore: npm publish 2023-02-08 18:19:26 +05:30
Anoop M D
aff6499478 feat: assert tab allows any valid js code as keys 2023-02-08 18:17:30 +05:30
Anoop M D
45ed47ff90 chore: added website link in readme 2023-02-08 16:47:52 +05:30
Anoop M D
27c6c1349a chore: release v0.9.1 2023-02-08 16:27:33 +05:30
Anoop M D
c78ffa3a80 chore: npm publish 2023-02-08 16:26:11 +05:30
Anoop M D
6b2d335ade fix: fixed string length comparision bug in assert runtime 2023-02-08 05:10:14 +05:30
Anoop M D
837e39d870 fix: fixed bugs in bru cli related to gql requests 2023-02-08 04:13:22 +05:30
Anoop M D
67643c4c48 chore: publishing to npm 2023-02-08 03:45:27 +05:30
Anoop M D
2b384656b6 feat: bruno can run a collection by specifying "bru run" 2023-02-08 03:27:27 +05:30
Anoop M D
411c06f4cb feat: bruno cli can not run a folder recursively 2023-02-08 02:53:55 +05:30
Anoop M D
03fa46d8b3 feat: bruno cli can sort the requests being run 2023-02-08 01:43:16 +05:30
Anoop M D
d0f2eb27bc feat: bru cli can now run all requests inside a directory 2023-02-08 01:25:15 +05:30
Anoop M D
1b9ec05a58 feat: assert runtime 2023-02-08 01:13:21 +05:30
Anoop M D
3f74178c81 feat: bru cli - specify env + completed vars runtime 2023-02-07 21:01:35 +05:30
Anoop M D
78ca6c5e96 feat: error messages for reserved file and folder names in bruno 2023-02-07 19:36:34 +05:30
Anoop M D
5f59a16090 feat: run again, run collection and close functionality in collection runner 2023-02-07 19:00:17 +05:30
Anoop M D
3805cef0c4 feat: auto focus newly created environment 2023-02-07 18:11:34 +05:30
Anoop M D
3c1a6ca71e chore: release v0.9.0 2023-02-07 08:15:13 +05:30
Anoop M D
d2227b2b05 feat: renamed vars:req,res as vars:pre-request,post-response 2023-02-07 05:13:14 +05:30
Anoop M D
dc03b6a761 feat: renamed script:req,res as script:pre-request,post-response 2023-02-07 04:39:23 +05:30
Anoop M D
e22f164cbc feat: simple vars runtime is working! 2023-02-07 04:33:25 +05:30
Anoop M D
580d681e0a fix: fixing issues in bru cli 2023-02-07 02:58:44 +05:30
Anoop M D
89b721d726 fix: fixed issues around body mode conversion 2023-02-07 02:50:15 +05:30
Anoop M D
1110a4edda fix: fixed gql related issues 2023-02-07 02:12:23 +05:30
Anoop M D
f69332d9c3 feat: automagically migrate users of bru v1 to bru v2 2023-02-07 01:19:32 +05:30
Anoop M D
6947860204 feat: made bru lang parser more robust to optional newlines and whitespaces 2023-02-07 01:18:18 +05:30
Anoop M D
963b0c257f feat: integrate new env model of bru lang 2023-02-06 23:22:48 +05:30
Anoop M D
33f8900705 chore: cleanup unused files 2023-02-06 23:02:47 +05:30
Anoop M D
22a14aa67a feat: making request and response scripts work 2023-02-06 23:00:50 +05:30
Anoop M D
60c96f7d27 feat: script and vars are segmented at req and res levels separately 2023-02-06 21:18:36 +05:30
Anoop M D
c8de57aa51 chore: restructure bru js package 2023-02-06 15:24:34 +05:30
Anoop M D
827c480689 feat: bru cli prints test results 2023-02-06 14:52:22 +05:30
Anoop M D
1c869013c6 feat: cli runner can now run a single request 2023-02-06 03:40:13 +05:30
Anoop M D
404a516fef chore: bruno cli accept request filename 2023-02-06 02:57:59 +05:30
Anoop M D
e26075060e chore: bru cli - added package deps 2023-02-06 02:34:27 +05:30
Anoop M D
c524f40ab2 feat: bru cli init 2023-02-06 02:27:22 +05:30
Anoop M D
3e563ea126 feat: bru lang - support body default as json 2023-02-06 01:27:08 +05:30
Anoop M D
a0cb53445f feat: bru lang - supporting ~ @ identifiers 2023-02-05 23:13:18 +05:30
Anoop M D
84bd603e11 feat: bru lang - parse env files 2023-02-05 19:06:48 +05:30
Anoop M D
c3236d4eb1 feat: making changes in app to use the new bru lang format 2023-02-05 01:25:36 +05:30
Anoop M D
4a4208f272 feat: bru lang - jsonToBru functionality 2023-02-05 00:27:18 +05:30
Anoop M D
d24f1a1054 refactor: organized v1 and v2 versions inside bru-lang 2023-02-04 20:11:33 +05:30
Anoop M D
86200a8f11 Merge pull request #85 from usebruno/feature/bru-lang-parser
Bru Lang Parser
2023-02-04 20:02:34 +05:30
Anoop M D
cf0ede1a83 chore: using fixtures to cleanup test file 2023-02-04 16:11:29 +05:30
Anoop M D
342a39bcb4 chore: renamed test files 2023-02-04 16:06:32 +05:30
Anoop M D
e7d332c7d7 feat: bru lang - support for vars, asserts and docs 2023-02-04 16:02:27 +05:30
Anoop M D
689d886e74 feat: bru lang - support for body type parsing 2023-02-04 06:06:02 +05:30
Anoop M D
7a8e5198ff feat: bru lang - support disabled headers parsing 2023-02-03 23:27:06 +05:30
Anoop M D
118ceacf46 feat: bru lang - allow parsing empty header values 2023-02-03 23:02:16 +05:30
Anoop M D
a21615a5fb feat: bru lang - keys can support any char except whitespace, values can have any char except newline 2023-02-03 21:44:07 +05:30
Anoop M D
2ee2e270b0 feat: brun lang - ast updates, tests for headers and script tags 2023-02-03 21:08:40 +05:30
Anoop M D
9d6ba4691c feat: bru lang tests, scripts and headers using ohm 2023-02-03 08:01:44 +05:30
Anoop M D
104bd272f9 feat: bru lang - simple parser 2023-02-03 04:39:45 +05:30
Anoop M D
62a184c386 chore: fixed github star button alignment 2023-02-01 22:20:06 +05:30
Anoop M D
4663a1246c release: v0.8.1 2023-02-01 21:54:11 +05:30
Anoop M D
0efd782bcb feat: github star button blends with dark mode 2023-02-01 21:52:46 +05:30
Anoop M D
a0903a5842 fix: fixed many bugs (too many to count :) ) 2023-02-01 21:21:21 +05:30
Anoop M D
8202182074 release time: v0.8.0 2023-02-01 18:09:27 +05:30
Anoop M D
ee4d4e3361 chore: fixed runner layout issues 2023-02-01 18:03:43 +05:30
Anoop M D
b76ddcd007 feat: scripting and testing support in graphql has arrived 2023-02-01 18:02:10 +05:30
Anoop M D
6f6dedbb9c feat: collection variables 2023-02-01 17:56:13 +05:30
Anoop M D
37b1c043eb feat: start collection runner at root 2023-02-01 17:29:53 +05:30
Anoop M D
58bc247c53 feat: collection runner 2023-02-01 17:06:04 +05:30
Anoop M D
c5b509115a chore: fixed collection chevron width issue 2023-02-01 10:07:11 +05:30
Anoop M D
524a59aed4 chore: hardened dnd boundary 2023-02-01 10:05:09 +05:30
Anoop M D
be49ef5f12 feat: rename collection 2023-02-01 09:59:23 +05:30
Anoop M D
d4f05fa843 feat: support for graphql variables 2023-02-01 09:23:11 +05:30
Anoop M D
adedd08e8a fix: relax schema validation max values in bruno-schema 2023-02-01 08:47:30 +05:30
Anoop M D
7dd0d10a5d fix: fixed bugs related to sequencing 2023-02-01 08:37:48 +05:30
Anoop M D
d9ef1692fe feat: generic key val line parser 2023-02-01 08:00:10 +05:30
Anoop M D
b88848f0dc chore: deleted unused workspace schema 2023-02-01 06:35:45 +05:30
Anoop M D
abc26e5c5a feat: load current environment during configuring envs 2023-02-01 06:31:32 +05:30
Anoop M D
d7733552bf chore: github workflow now runs bruno-app tests 2023-02-01 06:08:50 +05:30
Anoop M D
6852cc6631 feat: refactored logic around query param parsing 2023-02-01 06:07:43 +05:30
Anoop M D
8bfb2591c2 feat: graphql schema introspection 2023-02-01 05:09:42 +05:30
Anoop M D
05a290839b fix: fixed sidebar toggle width issues 2023-02-01 03:25:46 +05:30
Anoop M D
80f9e33be5 fix: fix overflow issues in keyval editors in the app 2023-02-01 03:06:32 +05:30
Anoop M D
5a78dfa210 fix: fixed dark mode theme issue in single line editor 2023-01-31 21:38:11 +05:30
Anoop M D
61caca59ee fix: fixed scrollbar issue in single line editor 2023-01-31 21:25:25 +05:30
Anoop M D
383c5ba782 chore: fixed typo in readme 2023-01-30 00:43:30 +05:30
Anoop M D
28fbaa3470 chore: adjusted logo image width in readme 2023-01-30 00:41:56 +05:30
Anoop M D
27dcf78e73 chore: updated readme documentation 2023-01-30 00:39:39 +05:30
Anoop M D
25883b84fa feat: added badges in readme 2023-01-30 00:28:42 +05:30
Anoop M D
667811cbd4 bumped version 2023-01-29 20:45:30 +05:30
Anoop M D
7839e93a57 fix: fixed dark mode issues 2023-01-29 20:44:34 +05:30
Anoop M D
11c60273b4 fix: fixed missing module error 2023-01-29 19:52:13 +05:30
Anoop M D
1dcff56c78 release: v0.7.0 2023-01-29 19:27:44 +05:30
Anoop M D
2e32423869 feat: better error messaging 2023-01-29 18:04:17 +05:30
Anoop M D
c328281f21 feat: testing support has arrived ! 2023-01-29 17:35:28 +05:30
Anoop M D
cc261326fc fix: fixed env var issues 2023-01-29 14:17:01 +05:30
Anoop M D
050ee2680f feat: improved request queuing status functionality 2023-01-29 13:20:19 +05:30
Anoop M D
b2c28465e9 fix: patch bug temporarily 2023-01-29 13:09:33 +05:30
Anoop M D
cd36335c60 feat: support crypto-js as an inbuilt library 2023-01-29 12:34:37 +05:30
Anoop M D
d89f12c071 feat: support loading external libraries 2023-01-29 12:33:12 +05:30
Anoop M D
905f459ed0 feat: support for inbuilt libraries during scripting 2023-01-29 11:12:34 +05:30
Anoop M D
b800055df4 chore: hiding menubar icon temporarily 2023-01-29 11:12:11 +05:30
Anoop M D
b1d2b798ba feat: scripting support almost done 2023-01-29 04:49:31 +05:30
Anoop M D
4a403a253e feat: show current env vars 2023-01-27 03:24:21 +05:30
Anoop M D
a45628dd85 feat: moved env var interpolation logic to electron 2023-01-25 10:39:07 +05:30
Anoop M D
977637e556 chore: updated manifesto in readme 2023-01-24 21:44:33 +05:30
Anoop M D
3d63db806d feat: moved prepare request logic to electron 2023-01-24 19:34:06 +05:30
Anoop M D
1ec24d1138 feat: scripting support (#16) 2023-01-24 18:27:47 +05:30
Anoop M D
fa40685a6a feat: skipping telemetry in dev env 2023-01-24 17:28:31 +05:30
Anoop M D
037013005f chore: bumped version 2023-01-23 00:18:54 +05:30
Anoop M D
0c42298ce6 feat: deprecated form-url-encoded in favour of form-urlencoded in body type in bru lang 2023-01-22 23:49:25 +05:30
Anoop M D
84ce75263b chore: added bruno-schema tests to github workflow 2023-01-22 23:39:59 +05:30
Anoop M D
5c8d0a9e8a feat: script and tests functionality 2023-01-22 23:39:16 +05:30
Anoop M D
b70bbf78b1 chore: renamed file 2023-01-22 18:35:57 +05:30
Anoop M D
43b9412ddb chore: rename package 2023-01-22 18:32:28 +05:30
Anoop M D
b56972fd93 chore: updated readme 2023-01-22 06:02:00 +05:30
Anoop M D
c102ac527a chore: updated readme 2023-01-22 06:00:00 +05:30
Anoop M D
45229b1af7 chore: version bump 2023-01-22 02:50:24 +05:30
Anoop M D
f9a3fb2f1b chore: cleanup old files 2023-01-22 02:43:14 +05:30
Anoop M D
65d8a707d8 chore: updated readme 2023-01-22 02:41:31 +05:30
Anoop M D
cc6bf45d5f chore: updated dev docs 2023-01-22 02:38:59 +05:30
Anoop M D
8fbb777665 feat: ask foldername when creating collection 2023-01-22 02:34:23 +05:30
Anoop M D
0e041d460c feat: listeners for cmd+s ctr+s in SingleLineEditor 2023-01-22 00:49:11 +05:30
Anoop M D
2e3b296021 feat: making regex in bruno lang support windows line endings 2023-01-22 00:38:10 +05:30
Anoop M D
405b50edcd fix: support parsing of empty urls in bru files 2023-01-22 00:35:58 +05:30
Anoop M D
fff540010e feat: hardening seq number functionality 2023-01-21 23:22:45 +05:30
Anoop M D
e513694912 fix: fixed seq type bug in bruno lang parser 2023-01-21 19:56:39 +05:30
Anoop M D
d01cada16c fix: fix json stringify bug in response timeline 2023-01-21 19:54:06 +05:30
Anoop M D
dd4fecfd1c fix: fix env info popup issues in graphql query editor 2023-01-21 19:41:37 +05:30
Anoop M D
095d7c6bcb feat: dark mode styles for env var info popup 2023-01-21 19:07:58 +05:30
Anoop M D
d165a04377 feat: environment variable syntax highlighting 2023-01-21 18:12:34 +05:30
Anoop M D
d3d1e47950 fix: fixed unit tests 2023-01-21 01:56:30 +05:30
Anoop M D
9c14941c15 feat: using single line editors instead of input boxes 2023-01-21 01:42:20 +05:30
Anoop M D
1627f65bd7 feat: using single line editors instead of input boxes 2023-01-21 01:38:48 +05:30
Anoop M D
19f4f3c1a5 chore: deprecating descriptions temporarily in key value pairs 2023-01-21 01:26:40 +05:30
Anoop M D
ae70680ceb feat: ener keybindings for single line editor 2023-01-21 01:23:33 +05:30
Anoop M D
60fc13c765 feat: refactor codemirror bruno variables mode 2023-01-21 01:17:27 +05:30
Anoop M D
60c3d41c8e feat: codemirror single line editor 2023-01-20 09:39:32 +05:30
Anoop M D
fb8ff37d83 feat: codemirror syntax highlight for env vars 2023-01-20 08:14:03 +05:30
Anoop M D
0d9b30e730 chore: updated .bru examples 2023-01-20 07:23:31 +05:30
Anoop M D
695f42df80 feat: run request upon cmd+enter from response pane 2023-01-20 07:16:57 +05:30
Anoop M D
6b43159be2 chore: cleanup 2023-01-20 03:27:01 +05:30
Anoop M D
21c9c8b4fb feat: drag and drop for files and folders 2023-01-20 00:45:07 +05:30
Anoop M D
c4abe54c3f fix: disable watcher updates on env directories 2023-01-19 02:00:02 +05:30
Anoop M D
dd71c9e71b feat: response timeline 2023-01-18 20:55:10 +05:30
Anoop M D
2be3e4bf69 feat: yay node v14 to v18 2023-01-18 20:53:27 +05:30
Anoop M D
f34e9f7b26 chore: fixed typo 2023-01-18 14:47:43 +05:30
Anoop M D
76b0729af3 feat: ditched web, all in on desktop app 2023-01-18 04:11:42 +05:30
Anoop M D
4877bc3849 chore: updated product tagline 2023-01-17 19:42:34 +05:30
Anoop M D
0742e3415c feat: github workflow for running unit tests 2023-01-17 19:35:59 +05:30
Anoop M D
ae7e3a722c chore: disable dragndrop temporarily due to electron issues 2023-01-17 19:27:11 +05:30
Anoop M D
7f2e19250f feat: generate collection hash on the fly 2023-01-17 19:26:41 +05:30
Anoop M D
4e16e954ef Merge branch 'main' of github.com:usebruno/bruno 2023-01-17 02:01:36 +05:30
Anoop M D
b6c3205474 feat: integrating app with the bru lang 2023-01-17 02:00:58 +05:30
Anoop M D
23076b41c6 feat: bruno land outdent strings during parsing 2023-01-17 00:55:47 +05:30
Anoop M D
b5116b54af feat: fix bugs in bruno-lang in data format 2023-01-17 00:20:22 +05:30
A-childs-encyclopedia
83aaa21b5b Update readme.md (#77) 2023-01-16 09:23:28 +05:30
Anoop M D
e1e7b37ce5 feat: bruno lang support for stringify json into bru file 2023-01-16 00:49:06 +05:30
Anoop M D
8dab9268f2 feat: bruno lang now supports parsing text only multipart form data 2023-01-15 23:02:59 +05:30
Anoop M D
4eed999db1 feat: bruno lang now supports parsing for url encoded params 2023-01-15 22:42:56 +05:30
Anoop M D
c29ab50a3d chore: bruno lang improve parsing logic of headers and params 2023-01-15 05:04:35 +05:30
Anoop M D
5e1d6cba4a feat: bruno lang support for parsing xml body 2023-01-15 05:03:58 +05:30
Anoop M D
a645d1459c feat: bruno lang - support parsing text body 2023-01-15 04:03:52 +05:30
Anoop M D
24e11a864c feat: bruno lang - parse graphql body 2023-01-15 03:51:48 +05:30
Anoop M D
87a4778a91 feat: bruno-lang now supprts parsing body json 2023-01-15 00:45:01 +05:30
Anoop M D
0750af4c68 chore: added examples inside bruno-lang 2023-01-15 00:44:21 +05:30
Anoop M D
60e613fac8 feat: bruno lang support parsing headers in .bru file 2023-01-14 20:21:54 +05:30
Anoop M D
b75baf57ba feat: bruno lang parse .bru file 2023-01-14 20:16:09 +05:30
Anoop M D
137df3c5c0 feat: bruno lang inline tag parser 2023-01-14 16:53:52 +05:30
Anoop M D
6ef2daebbd chore: updated bru files formatting 2023-01-14 04:37:39 +05:30
Anoop M D
55f85e3728 chore: updated bru files 2023-01-14 04:21:57 +05:30
Anoop M D
f0269069d2 feat: brun-lang package init 2023-01-12 23:46:01 +05:30
Anoop M D
61dbca3243 feat: highlight js inside script block in .bru files 2023-01-10 10:58:47 +05:30
Anoop M D
f21cb240c4 feat: vscode extension for bruno 2023-01-10 10:07:30 +05:30
Anoop M D
ca46e14732 feat: bru-file package init 2023-01-10 09:45:24 +05:30
Anoop M D
87f6000b85 Merge branch 'feature/sort-requests' 2023-01-10 09:39:13 +05:30
Anoop M D
36d0550472 feat: drag item to root of collection 2023-01-10 09:37:09 +05:30
Anoop M D
ee4734c957 feat: move requests across folders 2022-12-28 04:48:49 +05:30
Anoop M D
02f9fc0a7b fix: fixed mac electron build issues 2022-12-22 00:34:10 +05:30
Anoop M D
6ce657d891 feat: drag and drop events (#57) 2022-11-11 03:53:51 +05:30
Vinod Godti
cffef31f97 RequestPane body form input text color visibility fix in dark mode (#70) 2022-11-10 18:29:17 +05:30
Nash
b93be5a846 Set default theme to the user's browser theme (#69) 2022-11-09 22:55:30 +05:30
Anoop M D
544765af3e fix: fixed graphql docs height overflow (#65) 2022-11-08 03:45:30 +05:30
Anoop M D
2393092248 feat: graphql docs explorer (#65) 2022-11-08 03:35:58 +05:30
Anoop M D
dcdeb78995 release: bruno-graphql-docs@0.1.0 2022-11-08 03:34:25 +05:30
Ankur Singh Chauhan
e16650d4a7 Bugfix/split line (#66)
* Issue:62,fix Split line should have max squeeze limit
* added limit to right pane also
2022-11-07 18:22:09 +05:30
Anoop M D
62ed489847 chore: bumped bruno-schema in bruno-app 2022-11-07 03:14:48 +05:30
Anoop M D
2930eb29ec chore: release bruno-schema 0.2.0 2022-11-07 03:13:22 +05:30
Anoop M D
eecb60f5cf Merge branch 'main' of github.com:usebruno/bruno into main 2022-11-07 03:06:18 +05:30
Ankur Singh Chauhan
82fb2819c2 Issue:62,fix Split line should have max squeeze limit (#64)
Co-authored-by: Ankur Singh Chauhan <anx450z@gmail.com>
2022-11-07 03:03:35 +05:30
Anoop M D
2aef7c61a4 feat: graphql support (#65) 2022-11-07 02:56:58 +05:30
Anoop M D
530af1f929 chore: fix peer deps issue for rollup 2022-11-06 14:41:10 +05:30
Anoop M D
3753fd1e20 feat: standalone graphiql docs explorer 2022-11-06 01:04:30 +05:30
Anoop M D
a59ae75809 fix: fixed postman import url issue 2022-11-05 01:00:21 +05:30
Anoop M D
4c18c27406 chore: allow legacy peer deps while while running playwright tests 2022-11-05 00:21:15 +05:30
Anoop M D
5d25fdcf7a chore: bumped rollup version 2022-11-05 00:16:54 +05:30
Anoop M D
8cfdb3ebcb Merge branch 'main' of github.com:usebruno/bruno 2022-11-04 23:42:43 +05:30
Anoop M D
9e64ea5439 feat: package init for graphql-docs 2022-11-04 23:42:34 +05:30
shash68i
23c8044973 chore: prettier script added in root bruno/package.json (#63) 2022-11-02 15:22:48 +05:30
shash68i
46ac15dd81 Collection items whole div made as action button and cursor pointer added to the items (#61) 2022-11-01 00:50:51 +05:30
depa panjie purnama
5ad9be4f6b feat: Create New Request e2e test (#52)
* add selector ID
* add createNewRequest flow
* selector update
2022-10-31 16:50:57 +05:30
shash68i
f46625c689 Added missing <br /> between Website and Discord link in readme.md (#60) 2022-10-31 16:49:27 +05:30
Anoop M D
c0e1bf6bc2 chore: added discord invite link in readme 2022-10-31 03:14:29 +05:30
Anoop M D
874ca07f39 chore: added discord link 2022-10-31 00:58:36 +05:30
Anoop M D
a291e7f345 fix: fixed mac electron build issues 2022-10-31 00:58:20 +05:30
Anoop M D
c9cabfde35 release: bumped version 2022-10-30 20:07:11 +05:30
Anoop M D
fde15d7c31 Merge branch 'main' of github.com:usebruno/bruno into main 2022-10-30 19:58:06 +05:30
Anoop M D
11defe18ca chore: disable telemetry during playwright test execution 2022-10-30 19:56:54 +05:30
anusreesubash
54b14a005d fix(#53): fix for response editor search issue (#55) 2022-10-30 17:50:38 +05:30
Anoop M D
f283df2a1b feat: posthog telemetry 2022-10-30 17:48:36 +05:30
Anoop M D
820c99711b fix: fixed missing sidebar bg color 2022-10-30 04:05:52 +05:30
Anoop M D
df1cd4aff9 Merge branch 'feature/import-postman-collection' 2022-10-30 02:01:22 +05:30
Anoop M D
481486cd1c feat: import postman collection (#45) 2022-10-30 02:00:54 +05:30
Anoop M D
bf4c26de33 feat: refactored import collection 2022-10-30 00:09:24 +05:30
depa panjie purnama
c3fa473dae use fakerjs as random test data (#51) 2022-10-27 23:23:04 +05:30
depa panjie purnama
90a29918d0 feat: Create Collection e2e test (#50)
* add selector IDs
* add Create Collection e2e test
2022-10-26 21:59:37 +05:30
Anoop M D
c0698adcb3 chore: cleanup 2022-10-25 14:58:57 +05:30
Anoop M D
0d0f99e810 chore: cleanup 2022-10-25 14:57:53 +05:30
Anoop M D
7f5a6d5566 chore: using npm i instead of ci in playwright github actions 2022-10-25 00:52:42 +05:30
depa panjie purnama
dc68d511bd add e2e test using playwright (#44) 2022-10-25 00:42:53 +05:30
Anoop M D
0fceaf6918 chore: updated readme 2022-10-23 22:40:26 +05:30
Anoop M D
831223711a chore: updated app description 2022-10-23 20:57:35 +05:30
Anoop M D
e4cf3750bd chore: fixed missing screenshote in readme 2022-10-23 20:51:12 +05:30
Anoop M D
01e15b7fc1 Merge branch 'main' of github.com:usebruno/bruno 2022-10-23 19:48:33 +05:30
Anoop M D
3bf3d30ce8 fix: environment var interpolation issues 2022-10-23 19:48:02 +05:30
Vijay Hudge
bf6e6b29f5 Updated Readme with all contributors shown as image (#43) 2022-10-23 17:17:45 +05:30
Sean
075aaaebec Fixed spelling error in contributing.md and updated pull request section (#41) 2022-10-23 13:50:42 +05:30
Anoop M D
1136f1b105 release: v0.2.0 2022-10-23 13:43:12 +05:30
Anoop M D
5c8e66b684 feat: using a lighter shade of blue for dark theme 2022-10-23 12:53:24 +05:30
Anoop M D
09faf46635 chore: updated bruno tagline 2022-10-23 12:39:19 +05:30
Anoop M D
ef28637d0c Merge branch 'feature/dark-mode' 2022-10-23 12:37:20 +05:30
Anoop M D
51784d08cd fix: fixed bugs 2022-10-23 11:57:35 +05:30
Anoop M D
96f50b0c6d feat: dark-mode completed :) 2022-10-23 11:26:16 +05:30
Anoop M D
2ba6e4823d feat: dark-mode (response status and support modal) 2022-10-23 05:32:47 +05:30
Anoop M D
04a0a37ca4 feat: dark-mode (code editor) 2022-10-23 04:48:43 +05:30
Anoop M D
23400a77f8 feat: dark mode (tabs and request tabs) 2022-10-23 03:21:23 +05:30
Anoop M D
4718c77e3d feat: dark model (modals) 2022-10-23 02:14:59 +05:30
anusreesubash
0cde789697 fix: fixed issue renaming workspaces and creating collections (#40) 2022-10-22 16:32:42 +05:30
Anoop M D
6be2818bfb feat: dark mode (environment selector, query url) 2022-10-22 16:00:04 +05:30
Anoop M D
8e70e191e1 feat: darkmode (sidebar, menubar and welcome page #23) 2022-10-21 04:20:23 +05:30
Sean
cbdfabb4db Features/dark mode (#37)
* Added use local storage hook
* Added theme
* Added theme support
* Added theme provider
* Added dark theme for sidebar
* Added dark theme for main content area
* Added theme
* Added theme support
* Added theme provider
* Added dark theme for sidebar
* Added dark theme for main content area
2022-10-21 00:44:09 +05:30
415 changed files with 19238 additions and 5400 deletions

View File

@@ -0,0 +1,12 @@
name: Bump Homebrew Cask
on:
release:
types: [published]
jobs:
bump:
runs-on: macos-10.15
steps:
- name: Bump Homebrew Cask
run: brew bump-cask-pr bruno --version "${GITHUB_REF_NAME#v}"

27
.github/workflows/playwright.yml vendored Normal file
View File

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

29
.github/workflows/unit-tests.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Unit Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install dependencies
run: npm i --legacy-peer-deps
- name: Test Package bruno-query
run: npm run test --workspace=packages/bruno-query
- name: Build Package bruno-query
run: npm run build --workspace=packages/bruno-query
- name: Test Package bruno-lang
run: npm run test --workspace=packages/bruno-lang
- name: Test Package bruno-schema
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

4
.gitignore vendored
View File

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

4
.husky/pre-commit Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx pretty-quick --staged

2
.nvmrc
View File

@@ -1 +1 @@
v14.17.0
v18.13.0

7
.prettierrc.json Normal file
View File

@@ -0,0 +1,7 @@
{
"trailingComma": "none",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"printWidth": 120
}

BIN
assets/images/cli-demo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
assets/images/landing-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

View File

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

View File

@@ -1,27 +1,53 @@
## development
## Development
Bruno is deing developed as a desktop app. You need to load the app by running the nextjs app in one terminal and then run the electron app in another terminal.
### Dependencies
* NodeJS v18
### Local Development
```bash
# use nodejs 18 version
nvm use
# install deps
npm i
npm i --legacy-peer-deps
# run next app
npm run dev --workspace=packages/bruno-app
# build graphql docs
# note: you can for now ignore the error thrown while building the graphql docs
npm run build:graphql-docs
# run electron app
npm run dev --workspace=packages/bruno-electron
# build bruno query
npm run build:bruno-query
# build next app
npm run build --workspace=packages/bruno-app
# run next app (terminal 1)
npm run dev:web
# run electron app (terminal 2)
npm run dev:electron
```
## fix
### Troubleshooting
You might encounter a `Unsupported platform` error when you run `npm install`. To fix this, you will need to delete `node_modules` and `package-lock.json` and run `npm install`. This should install all the necessary packages needed to run the app.
# testing
```shell
# Delete node_modules in sub-directories
find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do
rm -rf "$dir"
done
# Delete package-lock in sub-directories
find . -type f -name "package-lock.json" -delete
```
### Testing
```bash
# bruno-schema
npm test --workspace=packages/bruno-schema
# bruno-lang
npm test --workspace=packages/bruno-lang
```

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,19 +4,40 @@
"workspaces": [
"packages/bruno-app",
"packages/bruno-electron",
"packages/bruno-cli",
"packages/bruno-tauri",
"packages/bruno-schema",
"packages/bruno-testbench"
"packages/bruno-query",
"packages/bruno-js",
"packages/bruno-lang",
"packages/bruno-testbench",
"packages/bruno-graphql-docs"
],
"homepage": "https://usebruno.com",
"devDependencies": {
"@faker-js/faker": "^7.6.0",
"@jest/globals": "^29.2.0",
"@playwright/test": "^1.27.1",
"about-window": "^1.15.2",
"husky": "^8.0.3",
"jest": "^29.2.0",
"randomstring": "^1.2.2"
"pretty-quick": "^3.1.3",
"randomstring": "^1.2.2",
"ts-jest": "^29.0.5"
},
"scripts": {
"dev:web": "npm run dev --workspace=packages/bruno-app",
"build:web": "npm run build --workspace=packages/bruno-app",
"prettier:web": "npm run prettier --workspace=packages/bruno-app",
"dev:electron": "npm run dev --workspace=packages/bruno-electron",
"build:chrome-extension": "./scripts/build-chrome-extension.sh",
"build:electron": "./scripts/build-electron.sh"
"build:bruno-query": "npm run build --workspace=packages/bruno-query",
"build:graphql-docs": "npm run build --workspace=packages/bruno-graphql-docs",
"build:electron": "./scripts/build-electron.sh",
"test:e2e": "npx playwright test",
"test:report": "npx playwright show-report",
"prepare": "husky install"
},
"overrides": {
"rollup": "3.2.5"
}
}

View File

@@ -3,5 +3,5 @@
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"printWidth": 180
"printWidth": 120
}

View File

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

View File

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

View File

@@ -1,11 +1,13 @@
{
"name": "@usebruno/app",
"version": "0.3.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "cross-env ENV=dev next dev",
"build": "next build && next export",
"start": "next start",
"lint": "next lint",
"test": "jest",
"prettier": "prettier --write \"./src/**/*.{js,jsx,json,ts,tsx}\""
},
"dependencies": {
@@ -15,7 +17,8 @@
"@reduxjs/toolkit": "^1.8.0",
"@tabler/icons": "^1.46.0",
"@tippyjs/react": "^4.2.6",
"@usebruno/schema": "0.1.0",
"@usebruno/graphql-docs": "0.1.0",
"@usebruno/schema": "0.3.1",
"axios": "^0.26.0",
"classnames": "^2.3.1",
"codemirror": "^5.65.2",
@@ -25,23 +28,29 @@
"file-saver": "^2.0.5",
"formik": "^2.2.9",
"graphiql": "^1.5.9",
"graphql": "^16.2.0",
"graphql": "^16.6.0",
"graphql-request": "^3.7.0",
"idb": "^7.0.0",
"immer": "^9.0.15",
"lodash": "^4.17.21",
"markdown-it": "^13.0.1",
"mousetrap": "^1.6.5",
"nanoid": "3.3.4",
"next": "12.3.1",
"next": "12.3.3",
"path": "^0.12.7",
"platform": "^1.3.6",
"posthog-node": "^2.1.0",
"qs": "^6.11.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "18.2.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "18.2.0",
"react-github-btn": "^1.4.0",
"react-hot-toast": "^2.4.0",
"react-inspector": "^6.0.2",
"react-redux": "^7.2.6",
"react-tabs": "^3.2.3",
"react-tooltip": "^5.5.2",
"sass": "^1.46.0",
"split-on-first": "^3.0.0",
"styled-components": "^5.3.3",
"tailwindcss": "^2.2.19",
"yup": "^0.32.11"
@@ -53,6 +62,7 @@
"@babel/preset-react": "^7.16.0",
"@babel/runtime": "^7.16.3",
"babel-loader": "^8.2.3",
"cross-env": "^7.0.3",
"css-loader": "^6.5.1",
"file-loader": "^6.2.0",
"html-loader": "^3.0.1",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 436 KiB

View File

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

View File

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

View File

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

View File

@@ -1,23 +0,0 @@
import { get, post, put } from './base';
// not used. kept as a placeholder for reference while implementing license key stuff
const AuthApi = {
whoami: () => get('auth/v1/user/whoami'),
signup: (params) => post('auth/v1/user/signup', params),
login: (params) => {
return new Promise((resolve, reject) => {
const { ipcRenderer } = window.require('electron');
ipcRenderer
.invoke('bruno-account-request', {
data: params,
method: 'POST',
url: `${process.env.NEXT_PUBLIC_BRUNO_SERVER_API}/auth/v1/user/login`
})
.then(resolve)
.catch(reject);
});
}
};
export default AuthApi;

View File

@@ -1,30 +0,0 @@
import axios from 'axios';
const apiClient = axios.create({
baseURL: process.env.NEXT_PUBLIC_GRAFNODE_SERVER_API
});
apiClient.interceptors.request.use(
(config) => {
const headers = {
'Content-Type': 'application/json'
};
return {
...config,
headers: headers
};
},
(error) => Promise.reject(error)
);
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
return Promise.reject(error.response ? error.response.data : error);
}
);
const { get, post, put, delete: destroy } = apiClient;
export { get, post, put, destroy };

View File

@@ -14,7 +14,11 @@ const Bruno = ({ width }) => {
stroke="none"
points="36,47.2521 32.9167,49.6688 30.4167,49.6688 30.3333,53.5021 31.0833,57.0021 32.1667,58.9188 35,60.4188 39.5833,59.8355 41.1667,58.0855 42.1667,53.8355 41.9167,49.8355 39.9167,50.0855"
/>
<polygon fill="#3F3F3F" stroke="none" points="32.5,36.9188 30.9167,40.6688 33.0833,41.9188 34.3333,42.4188 38.6667,42.5855 41.5833,40.3355 39.8333,37.0855" />
<polygon
fill="#3F3F3F"
stroke="none"
points="32.5,36.9188 30.9167,40.6688 33.0833,41.9188 34.3333,42.4188 38.6667,42.5855 41.5833,40.3355 39.8333,37.0855"
/>
</g>
<g id="hair" />
<g id="skin" />
@@ -84,8 +88,27 @@ const Bruno = ({ width }) => {
strokeWidth="2"
d="M52.6309,46.4628c0,0-3.0781,6.7216-7.8049,8.2712"
/>
<path fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" d="M19.437,46.969c0,0,3.0781,6.0823,7.8049,7.632" />
<line x1="36.2078" x2="36.2078" y1="47.3393" y2="44.3093" fill="none" stroke="#000000" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="10" strokeWidth="2" />
<path
fill="none"
stroke="#000000"
strokeLinecap="round"
strokeLinejoin="round"
strokeMiterlimit="10"
strokeWidth="2"
d="M19.437,46.969c0,0,3.0781,6.0823,7.8049,7.632"
/>
<line
x1="36.2078"
x2="36.2078"
y1="47.3393"
y2="44.3093"
fill="none"
stroke="#000000"
strokeLinecap="round"
strokeLinejoin="round"
strokeMiterlimit="10"
strokeWidth="2"
/>
</g>
</svg>
);

View File

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

View File

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

View File

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

View File

@@ -6,6 +6,9 @@
*/
import React from 'react';
import isEqual from 'lodash/isEqual';
import { getEnvironmentVariables } from 'utils/collections';
import { defineCodeMirrorBrunoVariablesMode } from 'utils/common/codemirror';
import StyledWrapper from './StyledWrapper';
let CodeMirror;
@@ -15,7 +18,7 @@ if (!SERVER_RENDERED) {
CodeMirror = require('codemirror');
}
export default class QueryEditor extends React.Component {
export default class CodeEditor extends React.Component {
constructor(props) {
super(props);
@@ -23,6 +26,7 @@ export default class QueryEditor extends React.Component {
// editor is updated, which can later be used to protect the editor from
// unnecessary updates during the update lifecycle.
this.cachedValue = props.value || '';
this.variables = {};
}
componentDidMount() {
@@ -38,7 +42,9 @@ export default class QueryEditor extends React.Component {
showCursorWhenSelecting: true,
foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
readOnly: this.props.readOnly ? 'nocursor' : false,
readOnly: this.props.readOnly,
scrollbarStyle: 'overlay',
theme: this.props.theme === 'dark' ? 'monokai' : 'default',
extraKeys: {
'Cmd-Enter': () => {
if (this.props.onRun) {
@@ -60,6 +66,8 @@ export default class QueryEditor extends React.Component {
this.props.onSave();
}
},
'Cmd-F': 'findPersistent',
'Ctrl-F': 'findPersistent',
Tab: function (cm) {
cm.replaceSelection(' ', 'end');
}
@@ -67,6 +75,7 @@ export default class QueryEditor extends React.Component {
}));
if (editor) {
editor.on('change', this._onEdit);
this.addOverlay();
}
}
@@ -85,7 +94,17 @@ export default class QueryEditor extends React.Component {
if (this.props.value !== prevProps.value && this.props.value !== this.cachedValue && this.editor) {
this.cachedValue = this.props.value;
this.editor.setValue(this.props.value);
this.editor.setOption('mode', this.props.mode);
}
if (this.editor) {
let variables = getEnvironmentVariables(this.props.collection);
if (!isEqual(variables, this.variables)) {
this.addOverlay();
}
}
if (this.props.theme !== prevProps.theme && this.editor) {
this.editor.setOption('theme', this.props.theme === 'dark' ? 'monokai' : 'default');
}
this.ignoreChangeEvent = false;
}
@@ -109,6 +128,15 @@ export default class QueryEditor extends React.Component {
);
}
addOverlay = () => {
const mode = this.props.mode || 'application/ld+json';
let variables = getEnvironmentVariables(this.props.collection);
this.variables = variables;
defineCodeMirrorBrunoVariablesMode(variables, mode);
this.editor.setOption('mode', 'brunovariables');
};
_onEdit = () => {
if (!this.ignoreChangeEvent && this.editor) {
this.cachedValue = this.editor.getValue();

View File

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

View File

@@ -5,7 +5,16 @@ import StyledWrapper from './StyledWrapper';
const Dropdown = ({ icon, children, onCreate, placement }) => {
return (
<StyledWrapper className="dropdown">
<Tippy content={children} placement={placement || 'bottom-end'} animation={false} arrow={false} onCreate={onCreate} interactive={true} trigger="click" appendTo="parent">
<Tippy
content={children}
placement={placement || 'bottom-end'}
animation={false}
arrow={false}
onCreate={onCreate}
interactive={true}
trigger="click"
appendTo="parent"
>
{icon}
</Tippy>
</StyledWrapper>

View File

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

View File

@@ -2,7 +2,7 @@ import React, { useRef, forwardRef, useState } from 'react';
import find from 'lodash/find';
import Dropdown from 'components/Dropdown';
import { selectEnvironment } from 'providers/ReduxStore/slices/collections/actions';
import { IconSettings, IconCaretDown, IconDatabase } from '@tabler/icons';
import { IconSettings, IconCaretDown, IconDatabase, IconDatabaseOff } from '@tabler/icons';
import EnvironmentSettings from '../EnvironmentSettings';
import toast from 'react-hot-toast';
import { useDispatch } from 'react-redux';
@@ -35,7 +35,7 @@ const EnvironmentSelector = ({ collection }) => {
toast.success(`No Environments are active now`);
}
})
.catch((err) => console.log(err) && toast.error('An error occured while selecting the environment'));
.catch((err) => console.log(err) && toast.error('An error occurred while selecting the environment'));
};
return (
@@ -63,13 +63,14 @@ const EnvironmentSelector = ({ collection }) => {
onSelect(null);
}}
>
<span>No Environment</span>
<IconDatabaseOff size={18} strokeWidth={1.5} />
<span className="ml-2">No Environment</span>
</div>
<div className="dropdown-item" style={{ borderTop: 'solid 1px #e7e7e7' }} onClick={() => setOpenSettingsModal(true)}>
<div className="dropdown-item border-top" onClick={() => setOpenSettingsModal(true)}>
<div className="pr-2 text-gray-600">
<IconSettings size={18} strokeWidth={1.5} />
</div>
<span>Settings</span>
<span>Configure</span>
</div>
</Dropdown>
</div>

View File

@@ -16,7 +16,10 @@ const CreateEnvironment = ({ collection, onClose }) => {
name: ''
},
validationSchema: Yup.object({
name: Yup.string().min(1, 'must be atleast 1 characters').max(50, 'must be 50 characters or less').required('name is required')
name: Yup.string()
.min(1, 'must be atleast 1 characters')
.max(50, 'must be 50 characters or less')
.required('name is required')
}),
onSubmit: (values) => {
dispatch(addEnvironment(values.name, collection.uid))
@@ -40,7 +43,13 @@ const CreateEnvironment = ({ collection, onClose }) => {
return (
<Portal>
<Modal size="sm" title={'Create Environment'} confirmText="Create" handleConfirm={onSubmit} handleCancel={onClose}>
<Modal
size="sm"
title={'Create Environment'}
confirmText="Create"
handleConfirm={onSubmit}
handleCancel={onClose}
>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
<div>
<label htmlFor="name" className="block font-semibold">
@@ -59,7 +68,9 @@ const CreateEnvironment = ({ collection, onClose }) => {
onChange={formik.handleChange}
value={formik.values.name || ''}
/>
{formik.touched.name && formik.errors.name ? <div className="text-red-500">{formik.errors.name}</div> : null}
{formik.touched.name && formik.errors.name ? (
<div className="text-red-500">{formik.errors.name}</div>
) : null}
</div>
</form>
</Modal>

View File

@@ -20,7 +20,13 @@ const DeleteEnvironment = ({ onClose, environment, collection }) => {
return (
<Portal>
<StyledWrapper>
<Modal size="sm" title={'Delete Environment'} confirmText="Delete" handleConfirm={onConfirm} handleCancel={onClose}>
<Modal
size="sm"
title={'Delete Environment'}
confirmText="Delete"
handleConfirm={onConfirm}
handleCancel={onClose}
>
Are you sure you want to delete <span className="font-semibold">{environment.name}</span> ?
</Modal>
</StyledWrapper>

View File

@@ -5,18 +5,28 @@ const Wrapper = styled.div`
width: 100%;
border-collapse: collapse;
font-weight: 600;
table-layout: fixed;
thead,
td {
border: 1px solid #efefef;
border: 1px solid ${(props) => props.theme.collection.environment.settings.gridBorder};
padding: 4px 10px;
&:nth-child(1) {
width: 30%;
}
&:nth-child(3) {
width: 70px;
}
}
thead {
color: #616161;
color: ${(props) => props.theme.table.thead.color};
font-size: 0.8125rem;
user-select: none;
}
td {
thead td {
padding: 6px 10px;
}
}
@@ -29,6 +39,7 @@ const Wrapper = styled.div`
width: 100%;
border: solid 1px transparent;
outline: none !important;
background-color: transparent;
&:focus {
outline: none !important;

View File

@@ -2,13 +2,16 @@ import React, { useReducer } from 'react';
import toast from 'react-hot-toast';
import cloneDeep from 'lodash/cloneDeep';
import { IconTrash } from '@tabler/icons';
import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux';
import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions';
import reducer from './reducer';
import SingleLineEditor from 'components/SingleLineEditor';
import StyledWrapper from './StyledWrapper';
const EnvironmentVariables = ({ environment, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const [state, reducerDispatch] = useReducer(reducer, { hasChanges: false, variables: environment.variables || [] });
const { variables, hasChanges } = state;
@@ -44,6 +47,10 @@ const EnvironmentVariables = ({ environment, collection }) => {
variable.enabled = e.target.checked;
break;
}
case 'secret': {
variable.secret = e.target.checked;
break;
}
}
reducerDispatch({
type: 'UPDATE_VAR',
@@ -63,8 +70,10 @@ const EnvironmentVariables = ({ environment, collection }) => {
<table>
<thead>
<tr>
<td>Enabled</td>
<td>Name</td>
<td>Value</td>
<td>Secret</td>
<td></td>
</tr>
</thead>
@@ -73,6 +82,14 @@ const EnvironmentVariables = ({ environment, collection }) => {
? variables.map((variable, index) => {
return (
<tr key={variable.uid}>
<td className="text-center">
<input
type="checkbox"
checked={variable.enabled}
className="mr-3 mousetrap"
onChange={(e) => handleVarChange(e, variable, 'enabled')}
/>
</td>
<td>
<input
type="text"
@@ -86,24 +103,25 @@ const EnvironmentVariables = ({ environment, collection }) => {
/>
</td>
<td>
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
<SingleLineEditor
value={variable.value}
className="mousetrap"
onChange={(e) => handleVarChange(e, variable, 'value')}
theme={storedTheme}
onChange={(newValue) => handleVarChange({ target: { value: newValue } }, variable, 'value')}
collection={collection}
/>
</td>
<td>
<div className="flex items-center">
<input type="checkbox" checked={variable.enabled} className="mr-3 mousetrap" onChange={(e) => handleVarChange(e, variable, 'enabled')} />
<button onClick={() => handleRemoveVars(variable)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>
<input
type="checkbox"
checked={variable.secret}
className="mr-3 mousetrap"
onChange={(e) => handleVarChange(e, variable, 'secret')}
/>
</td>
<td>
<button onClick={() => handleRemoveVars(variable)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</td>
</tr>
);
@@ -119,7 +137,12 @@ const EnvironmentVariables = ({ environment, collection }) => {
</div>
<div>
<button type="submit" className="submit btn btn-md btn-secondary mt-2" disabled={!hasChanges} onClick={saveChanges}>
<button
type="submit"
className="submit btn btn-md btn-secondary mt-2"
disabled={!hasChanges}
onClick={saveChanges}
>
Save
</button>
</div>

View File

@@ -12,6 +12,7 @@ const reducer = (state, action) => {
name: '',
value: '',
type: 'text',
secret: false,
enabled: true
});
draft.hasChanges = true;
@@ -24,6 +25,7 @@ const reducer = (state, action) => {
variable.name = action.variable.name;
variable.value = action.variable.value;
variable.enabled = action.variable.enabled;
variable.secret = action.variable.secret;
draft.hasChanges = true;
});
}

View File

@@ -7,12 +7,19 @@ import DeleteEnvironment from '../../DeleteEnvironment';
const EnvironmentDetails = ({ environment, collection }) => {
const [openEditModal, setOpenEditModal] = useState(false);
const [openDeleteModal, setOpenDeleteModal] = useState(false);
console.log(environment);
return (
<div className="px-6 flex-grow flex flex-col pt-6" style={{ maxWidth: '700px' }}>
{openEditModal && <RenameEnvironment onClose={() => setOpenEditModal(false)} environment={environment} collection={collection} />}
{openDeleteModal && <DeleteEnvironment onClose={() => setOpenDeleteModal(false)} environment={environment} collection={collection} />}
{openEditModal && (
<RenameEnvironment onClose={() => setOpenEditModal(false)} environment={environment} collection={collection} />
)}
{openDeleteModal && (
<DeleteEnvironment
onClose={() => setOpenDeleteModal(false)}
environment={environment}
collection={collection}
/>
)}
<div className="flex">
<div className="flex flex-grow items-center">
<IconDatabase className="cursor-pointer" size={20} strokeWidth={1.5} />

View File

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

View File

@@ -1,4 +1,6 @@
import React, { useEffect, useState, forwardRef, useRef } from 'react';
import { findEnvironmentInCollection } from 'utils/collections';
import usePrevious from 'hooks/usePrevious';
import EnvironmentDetails from './EnvironmentDetails';
import CreateEnvironment from '../CreateEnvironment/index';
import StyledWrapper from './StyledWrapper';
@@ -8,9 +10,36 @@ const EnvironmentList = ({ collection }) => {
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
const [openCreateModal, setOpenCreateModal] = useState(false);
const envUids = environments ? environments.map((env) => env.uid) : [];
const prevEnvUids = usePrevious(envUids);
useEffect(() => {
setSelectedEnvironment(environments[0]);
}, []);
if (selectedEnvironment) {
return;
}
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
if (environment) {
setSelectedEnvironment(environment);
} else {
setSelectedEnvironment(environments && environments.length ? environments[0] : null);
}
}, [collection, environments, selectedEnvironment]);
useEffect(() => {
// check env add
if (prevEnvUids && prevEnvUids.length && envUids.length > prevEnvUids.length) {
const newEnv = environments.find((env) => !prevEnvUids.includes(env.uid));
if (newEnv) {
setSelectedEnvironment(newEnv);
}
}
// check env delete
if (prevEnvUids && prevEnvUids.length && envUids.length < prevEnvUids.length) {
setSelectedEnvironment(environments && environments.length ? environments[0] : null);
}
}, [envUids, environments, prevEnvUids]);
if (!selectedEnvironment) {
return null;
@@ -25,7 +54,11 @@ const EnvironmentList = ({ collection }) => {
{environments &&
environments.length &&
environments.map((env) => (
<div key={env.uid} className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'} onClick={() => setSelectedEnvironment(env)}>
<div
key={env.uid}
className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'}
onClick={() => setSelectedEnvironment(env)}
>
<span>{env.name}</span>
</div>
))}

View File

@@ -16,7 +16,10 @@ const RenameEnvironment = ({ onClose, environment, collection }) => {
name: environment.name
},
validationSchema: Yup.object({
name: Yup.string().min(1, 'must be atleast 1 characters').max(50, 'must be 50 characters or less').required('name is required')
name: Yup.string()
.min(1, 'must be atleast 1 characters')
.max(50, 'must be 50 characters or less')
.required('name is required')
}),
onSubmit: (values) => {
dispatch(renameEnvironment(values.name, environment.uid, collection.uid))
@@ -40,7 +43,13 @@ const RenameEnvironment = ({ onClose, environment, collection }) => {
return (
<Portal>
<Modal size="sm" title={'Rename Environment'} confirmText="Rename" handleConfirm={onSubmit} handleCancel={onClose}>
<Modal
size="sm"
title={'Rename Environment'}
confirmText="Rename"
handleConfirm={onSubmit}
handleCancel={onClose}
>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
<div>
<label htmlFor="name" className="block font-semibold">
@@ -59,7 +68,9 @@ const RenameEnvironment = ({ onClose, environment, collection }) => {
onChange={formik.handleChange}
value={formik.values.name || ''}
/>
{formik.touched.name && formik.errors.name ? <div className="text-red-500">{formik.errors.name}</div> : null}
{formik.touched.name && formik.errors.name ? (
<div className="text-red-500">{formik.errors.name}</div>
) : null}
</div>
</form>
</Modal>

View File

@@ -11,11 +11,21 @@ const EnvironmentSettings = ({ collection, onClose }) => {
if (!environments || !environments.length) {
return (
<StyledWrapper>
<Modal size="md" title="Environments" confirmText={'Close'} handleConfirm={onClose} handleCancel={onClose} hideCancel={true}>
<Modal
size="md"
title="Environments"
confirmText={'Close'}
handleConfirm={onClose}
handleCancel={onClose}
hideCancel={true}
>
{openCreateModal && <CreateEnvironment collection={collection} onClose={() => setOpenCreateModal(false)} />}
<div className="text-center">
<p>No environments found!</p>
<button className="btn-create-environment text-link pr-2 py-3 mt-2 select-none" onClick={() => setOpenCreateModal(true)}>
<button
className="btn-create-environment text-link pr-2 py-3 mt-2 select-none"
onClick={() => setOpenCreateModal(true)}
>
+ <span>Create Environment</span>
</button>
</div>

View File

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

View File

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

View File

@@ -14,7 +14,15 @@ const ModalHeader = ({ title, handleCancel }) => (
const ModalContent = ({ children }) => <div className="bruno-modal-content px-4 py-6">{children}</div>;
const ModalFooter = ({ confirmText, cancelText, handleSubmit, handleCancel, confirmDisabled, hideCancel, hideFooter }) => {
const ModalFooter = ({
confirmText,
cancelText,
handleSubmit,
handleCancel,
confirmDisabled,
hideCancel,
hideFooter
}) => {
confirmText = confirmText || 'Save';
cancelText = cancelText || 'Cancel';
@@ -30,7 +38,12 @@ const ModalFooter = ({ confirmText, cancelText, handleSubmit, handleCancel, conf
</button>
</span>
<span>
<button type="submit" className="submit btn btn-md btn-secondary" disabled={confirmDisabled} onClick={handleSubmit}>
<button
type="submit"
className="submit btn btn-md btn-secondary"
disabled={confirmDisabled}
onClick={handleSubmit}
>
{confirmText}
</button>
</span>
@@ -38,7 +51,18 @@ const ModalFooter = ({ confirmText, cancelText, handleSubmit, handleCancel, conf
);
};
const Modal = ({ size, title, confirmText, cancelText, handleCancel, handleConfirm, children, confirmDisabled, hideCancel, hideFooter }) => {
const Modal = ({
size,
title,
confirmText,
cancelText,
handleCancel,
handleConfirm,
children,
confirmDisabled,
hideCancel,
hideFooter
}) => {
const [isClosing, setIsClosing] = useState(false);
const escFunction = (event) => {
const escKeyCode = 27;
@@ -64,6 +88,9 @@ const Modal = ({ size, title, confirmText, cancelText, handleCancel, handleConfi
if (isClosing) {
classes += ' modal--animate-out';
}
if (hideFooter) {
classes += ' modal-footer-none';
}
return (
<StyledWrapper className={classes}>
<div className={`bruno-modal-card modal-${size}`}>

View File

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

View File

@@ -0,0 +1,38 @@
import React, { useState } from 'react';
import { usePreferences } from 'providers/Preferences';
import StyledWrapper from './StyledWrapper';
const General = () => {
const { preferences, setPreferences } = usePreferences();
const [sslVerification, setSslVerification] = useState(preferences.request.sslVerification);
const handleCheckboxChange = () => {
const updatedPreferences = {
...preferences,
request: {
...preferences.request,
sslVerification: !sslVerification
}
};
setPreferences(updatedPreferences)
.then(() => {
setSslVerification(!sslVerification);
})
.catch((err) => {
console.error(err);
});
};
return (
<StyledWrapper>
<div className="flex items-center mt-2">
<input type="checkbox" checked={sslVerification} onChange={handleCheckboxChange} className="mr-3 mousetrap" />
SSL Certificate Verification
</div>
</StyledWrapper>
);
};
export default General;

View File

@@ -0,0 +1,36 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
div.tabs {
margin-top: -0.5rem;
div.tab {
padding: 6px 0px;
border: none;
border-bottom: solid 2px transparent;
margin-right: 1.25rem;
color: var(--color-tab-inactive);
cursor: pointer;
&:focus,
&:active,
&:focus-within,
&:focus-visible,
&:target {
outline: none !important;
box-shadow: none !important;
}
&.active {
color: ${(props) => props.theme.tabs.active.color} !important;
border-bottom: solid 2px ${(props) => props.theme.tabs.active.border} !important;
}
}
}
section.tab-panel {
min-height: 300px;
}
`;
export default StyledWrapper;

View File

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

View File

@@ -0,0 +1,44 @@
import React from 'react';
import { IconSpeakerphone, IconBrandTwitter, IconBrandGithub, IconBrandDiscord, IconBook } from '@tabler/icons';
import StyledWrapper from './StyledWrapper';
const Support = () => {
return (
<StyledWrapper>
<div className="rows">
<div className="mt-2">
<a href="https://docs.usebruno.com" target="_blank" className="flex items-end">
<IconBook size={18} strokeWidth={2} />
<span className="label ml-2">Documentation</span>
</a>
</div>
<div className="mt-2">
<a href="https://github.com/usebruno/bruno/issues" target="_blank" className="flex items-end">
<IconSpeakerphone size={18} strokeWidth={2} />
<span className="label ml-2">Report Issues</span>
</a>
</div>
<div className="mt-2">
<a href="https://discord.com/invite/KgcZUncpjq" target="_blank" className="flex items-end">
<IconBrandDiscord size={18} strokeWidth={2} />
<span className="label ml-2">Discord</span>
</a>
</div>
<div className="mt-2">
<a href="https://github.com/usebruno/bruno" target="_blank" className="flex items-end">
<IconBrandGithub size={18} strokeWidth={2} />
<span className="label ml-2">Github</span>
</a>
</div>
<div className="mt-2">
<a href="https://twitter.com/use_bruno" target="_blank" className="flex items-end">
<IconBrandTwitter size={18} strokeWidth={2} />
<span className="label ml-2">Twitter</span>
</a>
</div>
</div>
</StyledWrapper>
);
};
export default Support;

View File

@@ -0,0 +1,7 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
color: var(--color-text);
`;
export default StyledWrapper;

View File

@@ -0,0 +1,64 @@
import React from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import StyledWrapper from './StyledWrapper';
import { useTheme } from 'providers/Theme';
const Theme = () => {
const { storedTheme, setStoredTheme } = useTheme();
const formik = useFormik({
enableReinitialize: true,
initialValues: {
theme: storedTheme
},
validationSchema: Yup.object({
theme: Yup.string().oneOf(['light', 'dark']).required('theme is required')
}),
onSubmit: (values) => {
setStoredTheme(values.theme);
}
});
return (
<StyledWrapper>
<div className="bruno-form">
<div className="flex items-center mt-2">
<input
id="light-theme"
className="cursor-pointer"
type="radio"
name="theme"
onChange={(e) => {
formik.handleChange(e);
formik.handleSubmit();
}}
value="light"
checked={formik.values.theme === 'light'}
/>
<label htmlFor="light-theme" className="ml-1 cursor-pointer select-none">
Light
</label>
<input
id="dark-theme"
className="ml-4 cursor-pointer"
type="radio"
name="theme"
onChange={(e) => {
formik.handleChange(e);
formik.handleSubmit();
}}
value="dark"
checked={formik.values.theme === 'dark'}
/>
<label htmlFor="dark-theme" className="ml-1 cursor-pointer select-none">
Dark
</label>
</div>
</div>
</StyledWrapper>
);
};
export default Theme;

View File

@@ -0,0 +1,54 @@
import Modal from 'components/Modal/index';
import classnames from 'classnames';
import React, { useState } from 'react';
import Support from './Support';
import General from './General';
import Theme from './Theme';
import StyledWrapper from './StyledWrapper';
const Preferences = ({ onClose }) => {
const [tab, setTab] = useState('general');
const getTabClassname = (tabName) => {
return classnames(`tab select-none ${tabName}`, {
active: tabName === tab
});
};
const getTabPanel = (tab) => {
switch (tab) {
case 'general': {
return <General />;
}
case 'theme': {
return <Theme />;
}
case 'support': {
return <Support />;
}
}
};
return (
<StyledWrapper>
<Modal size="lg" title="Preferences" handleCancel={onClose} hideFooter={true}>
<div className="flex items-center px-2 tabs" role="tablist">
<div className={getTabClassname('general')} role="tab" onClick={() => setTab('general')}>
General
</div>
<div className={getTabClassname('theme')} role="tab" onClick={() => setTab('theme')}>
Theme
</div>
<div className={getTabClassname('support')} role="tab" onClick={() => setTab('support')}>
Support
</div>
</div>
<section className="flex flex-grow px-2 mt-4 tab-panel">{getTabPanel(tab)}</section>
</Modal>
</StyledWrapper>
);
};
export default Preferences;

View File

@@ -0,0 +1,90 @@
import React from 'react';
/**
* Assertion operators
*
* eq : equal to
* neq : not equal to
* gt : greater than
* gte : greater than or equal to
* lt : less than
* lte : less than or equal to
* in : in
* notIn : not in
* contains : contains
* notContains : not contains
* length : length
* matches : matches
* notMatches : not matches
* startsWith : starts with
* endsWith : ends with
* between : between
* isEmpty : is empty
* isNull : is null
* isUndefined : is undefined
* isDefined : is defined
* isTruthy : is truthy
* isFalsy : is falsy
* isJson : is json
* isNumber : is number
* isString : is string
* isBoolean : is boolean
*/
const AssertionOperator = ({ operator, onChange }) => {
const operators = [
'eq',
'neq',
'gt',
'gte',
'lt',
'lte',
'in',
'notIn',
'contains',
'notContains',
'length',
'matches',
'notMatches',
'startsWith',
'endsWith',
'between',
'isEmpty',
'isNull',
'isUndefined',
'isDefined',
'isTruthy',
'isFalsy',
'isJson',
'isNumber',
'isString',
'isBoolean'
];
const handleChange = (e) => {
onChange(e.target.value);
};
const getLabel = (operator) => {
switch (operator) {
case 'eq':
return 'equals';
case 'neq':
return 'notEquals';
default:
return operator;
}
};
return (
<select value={operator} onChange={handleChange} className="mousetrap">
{operators.map((operator) => (
<option key={operator} value={operator}>
{getLabel(operator)}
</option>
))}
</select>
);
};
export default AssertionOperator;

View File

@@ -0,0 +1,212 @@
import React from 'react';
import { IconTrash } from '@tabler/icons';
import SingleLineEditor from 'components/SingleLineEditor';
import AssertionOperator from '../AssertionOperator';
import { useTheme } from 'providers/Theme';
/**
* Assertion operators
*
* eq : equal to
* neq : not equal to
* gt : greater than
* gte : greater than or equal to
* lt : less than
* lte : less than or equal to
* in : in
* notIn : not in
* contains : contains
* notContains : not contains
* length : length
* matches : matches
* notMatches : not matches
* startsWith : starts with
* endsWith : ends with
* between : between
* isEmpty : is empty
* isNull : is null
* isUndefined : is undefined
* isDefined : is defined
* isTruthy : is truthy
* isFalsy : is falsy
* isJson : is json
* isNumber : is number
* isString : is string
* isBoolean : is boolean
*/
const parseAssertionOperator = (str = '') => {
if (!str || typeof str !== 'string' || !str.length) {
return {
operator: 'eq',
value: str
};
}
const operators = [
'eq',
'neq',
'gt',
'gte',
'lt',
'lte',
'in',
'notIn',
'contains',
'notContains',
'length',
'matches',
'notMatches',
'startsWith',
'endsWith',
'between',
'isEmpty',
'isNull',
'isUndefined',
'isDefined',
'isTruthy',
'isFalsy',
'isJson',
'isNumber',
'isString',
'isBoolean'
];
const unaryOperators = [
'isEmpty',
'isNull',
'isUndefined',
'isDefined',
'isTruthy',
'isFalsy',
'isJson',
'isNumber',
'isString',
'isBoolean'
];
const [operator, ...rest] = str.trim().split(' ');
const value = rest.join(' ');
if (unaryOperators.includes(operator)) {
return {
operator,
value: ''
};
}
if (operators.includes(operator)) {
return {
operator,
value
};
}
return {
operator: 'eq',
value: str
};
};
const isUnaryOperator = (operator) => {
const unaryOperators = [
'isEmpty',
'isNull',
'isUndefined',
'isDefined',
'isTruthy',
'isFalsy',
'isJson',
'isNumber',
'isString',
'isBoolean'
];
return unaryOperators.includes(operator);
};
const AssertionRow = ({
item,
collection,
assertion,
handleAssertionChange,
handleRemoveAssertion,
onSave,
handleRun
}) => {
const { storedTheme } = useTheme();
const { operator, value } = parseAssertionOperator(assertion.value);
return (
<tr key={assertion.uid}>
<td>
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={assertion.name}
className="mousetrap"
onChange={(e) => handleAssertionChange(e, assertion, 'name')}
/>
</td>
<td>
<AssertionOperator
operator={operator}
onChange={(op) =>
handleAssertionChange(
{
target: {
value: `${op} ${value}`
}
},
assertion,
'value'
)
}
/>
</td>
<td>
{!isUnaryOperator(operator) ? (
<SingleLineEditor
value={value}
theme={storedTheme}
readOnly={true}
onSave={onSave}
onChange={(newValue) =>
handleAssertionChange(
{
target: {
value: newValue
}
},
assertion,
'value'
)
}
onRun={handleRun}
collection={collection}
/>
) : (
<input type="text" className="cursor-default" disabled />
)}
</td>
<td>
<div className="flex items-center">
<input
type="checkbox"
checked={assertion.enabled}
className="mr-3 mousetrap"
onChange={(e) => handleAssertionChange(e, assertion, 'enabled')}
/>
<button onClick={() => handleRemoveAssertion(assertion)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
</div>
</td>
</tr>
);
};
export default AssertionRow;

View File

@@ -0,0 +1,60 @@
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(4) {
width: 70px;
}
select {
background-color: transparent;
}
}
}
.btn-add-assertion {
font-size: 0.8125rem;
}
input[type='text'] {
width: 100%;
border: solid 1px transparent;
outline: none !important;
background-color: inherit;
&:focus {
outline: none !important;
border: solid 1px transparent;
}
}
input[type='checkbox'] {
cursor: pointer;
position: relative;
top: 1px;
}
`;
export default Wrapper;

View File

@@ -0,0 +1,96 @@
import React from 'react';
import get from 'lodash/get';
import cloneDeep from 'lodash/cloneDeep';
import { useDispatch } from 'react-redux';
import { addAssertion, updateAssertion, deleteAssertion } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import AssertionRow from './AssertionRow';
import StyledWrapper from './StyledWrapper';
const Assertions = ({ item, collection }) => {
const dispatch = useDispatch();
const assertions = item.draft ? get(item, 'draft.request.assertions') : get(item, 'request.assertions');
const handleAddAssertion = () => {
dispatch(
addAssertion({
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
const handleRun = () => dispatch(sendRequest(item, collection.uid));
const handleAssertionChange = (e, _assertion, type) => {
const assertion = cloneDeep(_assertion);
switch (type) {
case 'name': {
assertion.name = e.target.value;
break;
}
case 'value': {
assertion.value = e.target.value;
break;
}
case 'enabled': {
assertion.enabled = e.target.checked;
break;
}
}
dispatch(
updateAssertion({
assertion: assertion,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
const handleRemoveAssertion = (assertion) => {
dispatch(
deleteAssertion({
assertUid: assertion.uid,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
return (
<StyledWrapper className="w-full">
<table>
<thead>
<tr>
<td>Expr</td>
<td>Operator</td>
<td>Value</td>
<td></td>
</tr>
</thead>
<tbody>
{assertions && assertions.length
? assertions.map((assertion) => {
return (
<AssertionRow
key={assertion.uid}
assertion={assertion}
item={item}
collection={collection}
handleAssertionChange={handleAssertionChange}
handleRemoveAssertion={handleRemoveAssertion}
onSave={onSave}
handleRun={handleRun}
/>
);
})
: null}
</tbody>
</table>
<button className="btn-add-assertion text-link pr-2 py-3 mt-2 select-none" onClick={handleAddAssertion}>
+ Add Assertion
</button>
</StyledWrapper>
);
};
export default Assertions;

View File

@@ -5,19 +5,28 @@ const Wrapper = styled.div`
width: 100%;
border-collapse: collapse;
font-weight: 600;
table-layout: fixed;
thead,
td {
border: 1px solid #efefef;
border: 1px solid ${(props) => props.theme.table.border};
}
thead {
color: #616161;
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;
}
}
}
@@ -29,6 +38,8 @@ const Wrapper = styled.div`
width: 100%;
border: solid 1px transparent;
outline: none !important;
color: ${(props) => props.theme.table.input.color};
background: transparent;
&:focus {
outline: none !important;

View File

@@ -3,11 +3,19 @@ import get from 'lodash/get';
import cloneDeep from 'lodash/cloneDeep';
import { IconTrash } from '@tabler/icons';
import { useDispatch } from 'react-redux';
import { addFormUrlEncodedParam, updateFormUrlEncodedParam, deleteFormUrlEncodedParam } from 'providers/ReduxStore/slices/collections';
import { useTheme } from 'providers/Theme';
import {
addFormUrlEncodedParam,
updateFormUrlEncodedParam,
deleteFormUrlEncodedParam
} from 'providers/ReduxStore/slices/collections';
import SingleLineEditor from 'components/SingleLineEditor';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
const FormUrlEncodedParams = ({ item, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const params = item.draft ? get(item, 'draft.request.body.formUrlEncoded') : get(item, 'request.body.formUrlEncoded');
const addParam = () => {
@@ -19,6 +27,8 @@ const FormUrlEncodedParams = ({ item, collection }) => {
);
};
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
const handleRun = () => dispatch(sendRequest(item, collection.uid));
const handleParamChange = (e, _param, type) => {
const param = cloneDeep(_param);
switch (type) {
@@ -30,10 +40,6 @@ const FormUrlEncodedParams = ({ item, collection }) => {
param.value = e.target.value;
break;
}
case 'description': {
param.description = e.target.value;
break;
}
case 'enabled': {
param.enabled = e.target.checked;
break;
@@ -65,7 +71,6 @@ const FormUrlEncodedParams = ({ item, collection }) => {
<tr>
<td>Key</td>
<td>Value</td>
<td>Description</td>
<td></td>
</tr>
</thead>
@@ -87,32 +92,33 @@ const FormUrlEncodedParams = ({ item, collection }) => {
/>
</td>
<td>
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
<SingleLineEditor
value={param.value}
className="mousetrap"
onChange={(e) => handleParamChange(e, param, 'value')}
/>
</td>
<td>
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={param.description}
className="mousetrap"
onChange={(e) => handleParamChange(e, param, 'description')}
theme={storedTheme}
onSave={onSave}
onChange={(newValue) =>
handleParamChange(
{
target: {
value: newValue
}
},
param,
'value'
)
}
onRun={handleRun}
collection={collection}
/>
</td>
<td>
<div className="flex items-center">
<input type="checkbox" checked={param.enabled} className="mr-3 mousetrap" onChange={(e) => handleParamChange(e, param, 'enabled')} />
<input
type="checkbox"
checked={param.enabled}
className="mr-3 mousetrap"
onChange={(e) => handleParamChange(e, param, 'enabled')}
/>
<button onClick={() => handleRemoveParams(param)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>

View File

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

View File

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

View File

@@ -0,0 +1,55 @@
import { useState } from 'react';
import toast from 'react-hot-toast';
import { buildClientSchema } from 'graphql';
import { fetchGqlSchema } from 'utils/network';
import { simpleHash } from 'utils/common';
const schemaHashPrefix = 'bruno.graphqlSchema';
const useGraphqlSchema = (endpoint, environment) => {
const localStorageKey = `${schemaHashPrefix}.${simpleHash(endpoint)}`;
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [schema, setSchema] = useState(() => {
try {
const saved = localStorage.getItem(localStorageKey);
if (!saved) {
return null;
}
return buildClientSchema(JSON.parse(saved));
} catch {
localStorage.setItem(localStorageKey, null);
return null;
}
});
const loadSchema = () => {
setIsLoading(true);
fetchGqlSchema(endpoint, environment)
.then((res) => res.data)
.then((s) => {
if (s && s.data) {
setSchema(buildClientSchema(s.data));
setIsLoading(false);
localStorage.setItem(localStorageKey, JSON.stringify(s.data));
toast.success('GraphQL Schema loaded successfully');
} else {
return Promise.reject(new Error('An error occurred while introspecting schema'));
}
})
.catch((err) => {
setIsLoading(false);
setError(err);
toast.error('Error occured while loading GraphQL Schema');
});
};
return {
isLoading,
schema,
loadSchema,
error
};
};
export default useGraphqlSchema;

View File

@@ -0,0 +1,10 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
div.CodeMirror {
/* todo: find a better way */
height: calc(100vh - 220px);
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,42 @@
import React from 'react';
import { useDispatch } from 'react-redux';
import CodeEditor from 'components/CodeEditor';
import { updateRequestGraphqlVariables } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import { useTheme } from 'providers/Theme';
import StyledWrapper from './StyledWrapper';
const GraphQLVariables = ({ variables, item, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const onEdit = (value) => {
dispatch(
updateRequestGraphqlVariables({
variables: value,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
const onRun = () => dispatch(sendRequest(item, collection.uid));
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
return (
<StyledWrapper className="w-full">
<CodeEditor
collection={collection}
value={variables || ''}
theme={storedTheme}
onEdit={onEdit}
mode="javascript"
onRun={onRun}
onSave={onSave}
/>
</StyledWrapper>
);
};
export default GraphQLVariables;

View File

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

View File

@@ -7,6 +7,10 @@ import QueryParams from 'components/RequestPane/QueryParams';
import RequestHeaders from 'components/RequestPane/RequestHeaders';
import RequestBody from 'components/RequestPane/RequestBody';
import RequestBodyMode from 'components/RequestPane/RequestBody/RequestBodyMode';
import Vars from 'components/RequestPane/Vars';
import Assertions from 'components/RequestPane/Assertions';
import Script from 'components/RequestPane/Script';
import Tests from 'components/RequestPane/Tests';
import StyledWrapper from './StyledWrapper';
const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
@@ -34,6 +38,18 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
case 'headers': {
return <RequestHeaders item={item} collection={collection} />;
}
case 'vars': {
return <Vars item={item} collection={collection} />;
}
case 'assert': {
return <Assertions item={item} collection={collection} />;
}
case 'script': {
return <Script item={item} collection={collection} />;
}
case 'tests': {
return <Tests item={item} collection={collection} />;
}
default: {
return <div className="mt-4">404 | Not found</div>;
}
@@ -59,7 +75,7 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
<StyledWrapper className="flex flex-col h-full relative">
<div className="flex items-center tabs" role="tablist">
<div className={getTabClassname('params')} role="tab" onClick={() => selectTab('params')}>
Params
Query
</div>
<div className={getTabClassname('body')} role="tab" onClick={() => selectTab('body')}>
Body
@@ -67,6 +83,18 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
Headers
</div>
<div className={getTabClassname('vars')} role="tab" onClick={() => selectTab('vars')}>
Vars
</div>
<div className={getTabClassname('script')} role="tab" onClick={() => selectTab('script')}>
Script
</div>
<div className={getTabClassname('assert')} role="tab" onClick={() => selectTab('assert')}>
Assert
</div>
<div className={getTabClassname('tests')} role="tab" onClick={() => selectTab('tests')}>
Tests
</div>
{/* Moved to post mvp */}
{/* <div className={getTabClassname('auth')} role="tab" onClick={() => selectTab('auth')}>Auth</div> */}
{focusedTab.requestPaneTab === 'body' ? (
@@ -75,7 +103,9 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
</div>
) : null}
</div>
<section className="flex w-full mt-5">{getTabPanel(focusedTab.requestPaneTab)}</section>
<section className={`flex w-full ${['script', 'vars'].includes(focusedTab.requestPaneTab) ? '' : 'mt-5'}`}>
{getTabPanel(focusedTab.requestPaneTab)}
</section>
</StyledWrapper>
);
};

View File

@@ -5,19 +5,28 @@ const Wrapper = styled.div`
width: 100%;
border-collapse: collapse;
font-weight: 600;
table-layout: fixed;
thead,
td {
border: 1px solid #efefef;
border: 1px solid ${(props) => props.theme.table.border};
}
thead {
color: #616161;
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;
}
}
}
@@ -29,6 +38,8 @@ const Wrapper = styled.div`
width: 100%;
border: solid 1px transparent;
outline: none !important;
color: ${(props) => props.theme.table.input.color};
background: transparent;
&:focus {
outline: none !important;

View File

@@ -3,11 +3,19 @@ import get from 'lodash/get';
import cloneDeep from 'lodash/cloneDeep';
import { IconTrash } from '@tabler/icons';
import { useDispatch } from 'react-redux';
import { addMultipartFormParam, updateMultipartFormParam, deleteMultipartFormParam } from 'providers/ReduxStore/slices/collections';
import { useTheme } from 'providers/Theme';
import {
addMultipartFormParam,
updateMultipartFormParam,
deleteMultipartFormParam
} from 'providers/ReduxStore/slices/collections';
import SingleLineEditor from 'components/SingleLineEditor';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
const MultipartFormParams = ({ item, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const params = item.draft ? get(item, 'draft.request.body.multipartForm') : get(item, 'request.body.multipartForm');
const addParam = () => {
@@ -19,6 +27,8 @@ const MultipartFormParams = ({ item, collection }) => {
);
};
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
const handleRun = () => dispatch(sendRequest(item, collection.uid));
const handleParamChange = (e, _param, type) => {
const param = cloneDeep(_param);
switch (type) {
@@ -30,10 +40,6 @@ const MultipartFormParams = ({ item, collection }) => {
param.value = e.target.value;
break;
}
case 'description': {
param.description = e.target.value;
break;
}
case 'enabled': {
param.enabled = e.target.checked;
break;
@@ -65,7 +71,6 @@ const MultipartFormParams = ({ item, collection }) => {
<tr>
<td>Key</td>
<td>Value</td>
<td>Description</td>
<td></td>
</tr>
</thead>
@@ -87,32 +92,33 @@ const MultipartFormParams = ({ item, collection }) => {
/>
</td>
<td>
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
<SingleLineEditor
onSave={onSave}
theme={storedTheme}
value={param.value}
className="mousetrap"
onChange={(e) => handleParamChange(e, param, 'value')}
/>
</td>
<td>
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={param.description}
className="mousetrap"
onChange={(e) => handleParamChange(e, param, 'description')}
onChange={(newValue) =>
handleParamChange(
{
target: {
value: newValue
}
},
param,
'value'
)
}
onRun={handleRun}
collection={collection}
/>
</td>
<td>
<div className="flex items-center">
<input type="checkbox" checked={param.enabled} className="mr-3 mousetrap" onChange={(e) => handleParamChange(e, param, 'enabled')} />
<input
type="checkbox"
checked={param.enabled}
className="mr-3 mousetrap"
onChange={(e) => handleParamChange(e, param, 'enabled')}
/>
<button onClick={() => handleRemoveParams(param)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>

View File

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

View File

@@ -6,7 +6,10 @@
*/
import React from 'react';
import isEqual from 'lodash/isEqual';
import MD from 'markdown-it';
import { getAllVariables } from 'utils/collections';
import { defineCodeMirrorBrunoVariablesMode } from 'utils/common/codemirror';
import StyledWrapper from './StyledWrapper';
import onHasCompletion from './onHasCompletion';
@@ -29,6 +32,7 @@ export default class QueryEditor extends React.Component {
// editor is updated, which can later be used to protect the editor from
// unnecessary updates during the update lifecycle.
this.cachedValue = props.value || '';
this.variables = {};
}
componentDidMount() {
@@ -37,11 +41,17 @@ export default class QueryEditor extends React.Component {
lineNumbers: true,
tabSize: 2,
mode: 'graphql',
// mode: 'brunovariables',
brunoVarInfo: {
variables: getAllVariables(this.props.collection)
},
theme: this.props.editorTheme || 'graphiql',
theme: this.props.theme === 'dark' ? 'monokai' : 'default',
keyMap: 'sublime',
autoCloseBrackets: true,
matchBrackets: true,
showCursorWhenSelecting: true,
scrollbarStyle: 'overlay',
readOnly: this.props.readOnly ? 'nocursor' : false,
foldGutter: {
minFoldSize: 4
@@ -75,54 +85,51 @@ export default class QueryEditor extends React.Component {
'Alt-Space': () => editor.showHint({ completeSingle: true, container: this._node }),
'Shift-Space': () => editor.showHint({ completeSingle: true, container: this._node }),
'Shift-Alt-Space': () => editor.showHint({ completeSingle: true, container: this._node }),
'Cmd-Enter': () => {
if (this.props.onRunQuery) {
this.props.onRunQuery();
if (this.props.onRun) {
this.props.onRun();
}
},
'Ctrl-Enter': () => {
if (this.props.onRunQuery) {
this.props.onRunQuery();
if (this.props.onRun) {
this.props.onRun();
}
},
'Shift-Ctrl-C': () => {
if (this.props.onCopyQuery) {
this.props.onCopyQuery();
}
},
'Shift-Ctrl-P': () => {
if (this.props.onPrettifyQuery) {
this.props.onPrettifyQuery();
}
},
/* Shift-Ctrl-P is hard coded in Firefox for private browsing so adding an alternative to Pretiffy */
'Shift-Ctrl-F': () => {
if (this.props.onPrettifyQuery) {
this.props.onPrettifyQuery();
}
},
'Shift-Ctrl-M': () => {
if (this.props.onMergeQuery) {
this.props.onMergeQuery();
}
},
'Cmd-S': () => {
if (this.props.onRunQuery) {
// empty
if (this.props.onSave) {
this.props.onSave();
return false;
}
},
'Ctrl-S': () => {
if (this.props.onRunQuery) {
// empty
if (this.props.onSave) {
this.props.onSave();
return false;
}
}
},
'Cmd-F': 'findPersistent',
'Ctrl-F': 'findPersistent'
}
}));
if (editor) {
@@ -131,6 +138,7 @@ export default class QueryEditor extends React.Component {
editor.on('hasCompletion', this._onHasCompletion);
editor.on('beforeChange', this._onBeforeChange);
}
this.addOverlay();
}
componentDidUpdate(prevProps) {
@@ -149,6 +157,15 @@ export default class QueryEditor extends React.Component {
this.cachedValue = this.props.value;
this.editor.setValue(this.props.value);
}
if (this.props.theme !== prevProps.theme && this.editor) {
this.editor.setOption('theme', this.props.theme === 'dark' ? 'monokai' : 'default');
}
let variables = getAllVariables(this.props.collection);
if (!isEqual(variables, this.variables)) {
this.editor.options.brunoVarInfo.variables = variables;
this.addOverlay();
}
this.ignoreChangeEvent = false;
}
@@ -161,10 +178,19 @@ export default class QueryEditor extends React.Component {
}
}
// Todo: Overlay is messing up with schema hint
// Fix this
addOverlay = () => {
// let variables = getAllVariables(this.props.collection);
// this.variables = variables;
// defineCodeMirrorBrunoVariablesMode(variables, 'graphql');
// this.editor.setOption('mode', 'brunovariables');
};
render() {
return (
<StyledWrapper
className="h-full"
className="h-full w-full"
aria-label="Query Editor"
ref={(node) => {
this._node = node;
@@ -173,8 +199,11 @@ export default class QueryEditor extends React.Component {
);
}
_onKeyUp = (_cm, event) => {
if (AUTO_COMPLETE_AFTER_KEY.test(event.key) && this.editor) {
_onKeyUp = (_cm, e) => {
if (e.metaKey || e.ctrlKey || e.altKey) {
return;
}
if (AUTO_COMPLETE_AFTER_KEY.test(e.key) && this.editor) {
this.editor.execCommand('autocomplete');
}
};

View File

@@ -59,7 +59,10 @@ export default function onHasCompletion(_cm, data, onHintInformationRender) {
const description = ctx.description ? md.render(ctx.description) : 'Self descriptive.';
const type = ctx.type ? '<span className="infoType">' + renderType(ctx.type) + '</span>' : '';
information.innerHTML = '<div className="content">' + (description.slice(0, 3) === '<p>' ? '<p>' + type + description.slice(3) : type + description) + '</div>';
information.innerHTML =
'<div className="content">' +
(description.slice(0, 3) === '<p>' ? '<p>' + type + description.slice(3) : type + description) +
'</div>';
if (ctx && deprecation && ctx.deprecationReason) {
const reason = ctx.deprecationReason ? md.render(ctx.deprecationReason) : '';

View File

@@ -5,19 +5,28 @@ const Wrapper = styled.div`
width: 100%;
border-collapse: collapse;
font-weight: 600;
table-layout: fixed;
thead,
td {
border: 1px solid #efefef;
border: 1px solid ${(props) => props.theme.table.border};
}
thead {
color: #616161;
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;
}
}
}
@@ -32,6 +41,7 @@ const Wrapper = styled.div`
width: 100%;
border: solid 1px transparent;
outline: none !important;
background-color: inherit;
&:focus {
outline: none !important;

View File

@@ -3,12 +3,16 @@ import get from 'lodash/get';
import cloneDeep from 'lodash/cloneDeep';
import { IconTrash } from '@tabler/icons';
import { useDispatch } from 'react-redux';
import { useTheme } from 'providers/Theme';
import { addQueryParam, updateQueryParam, deleteQueryParam } from 'providers/ReduxStore/slices/collections';
import SingleLineEditor from 'components/SingleLineEditor';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
const QueryParams = ({ item, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const params = item.draft ? get(item, 'draft.request.params') : get(item, 'request.params');
const handleAddParam = () => {
@@ -20,6 +24,8 @@ const QueryParams = ({ item, collection }) => {
);
};
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
const handleRun = () => dispatch(sendRequest(item, collection.uid));
const handleParamChange = (e, _param, type) => {
const param = cloneDeep(_param);
@@ -32,10 +38,6 @@ const QueryParams = ({ item, collection }) => {
param.value = e.target.value;
break;
}
case 'description': {
param.description = e.target.value;
break;
}
case 'enabled': {
param.enabled = e.target.checked;
break;
@@ -66,9 +68,8 @@ const QueryParams = ({ item, collection }) => {
<table>
<thead>
<tr>
<td>Key</td>
<td>Name</td>
<td>Value</td>
<td>Description</td>
<td></td>
</tr>
</thead>
@@ -90,32 +91,33 @@ const QueryParams = ({ item, collection }) => {
/>
</td>
<td>
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
<SingleLineEditor
value={param.value}
className="mousetrap"
onChange={(e) => handleParamChange(e, param, 'value')}
/>
</td>
<td>
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={param.description}
className="mousetrap"
onChange={(e) => handleParamChange(e, param, 'description')}
theme={storedTheme}
onSave={onSave}
onChange={(newValue) =>
handleParamChange(
{
target: {
value: newValue
}
},
param,
'value'
)
}
onRun={handleRun}
collection={collection}
/>
</td>
<td>
<div className="flex items-center">
<input type="checkbox" checked={param.enabled} className="mr-3 mousetrap" onChange={(e) => handleParamChange(e, param, 'enabled')} />
<input
type="checkbox"
checked={param.enabled}
className="mr-3 mousetrap"
onChange={(e) => handleParamChange(e, param, 'enabled')}
/>
<button onClick={() => handleRemoveParam(param)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>

View File

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

View File

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

View File

@@ -1,16 +1,28 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import get from 'lodash/get';
import { useDispatch } from 'react-redux';
import { requestUrlChanged, updateRequestMethod } from 'providers/ReduxStore/slices/collections';
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import HttpMethodSelector from './HttpMethodSelector';
import { useTheme } from 'providers/Theme';
import SendIcon from 'components/Icons/Send';
import SingleLineEditor from 'components/SingleLineEditor';
import StyledWrapper from './StyledWrapper';
import SendSvg from 'assets/send.svg';
const QueryUrl = ({ item, collection, handleRun }) => {
const { theme, storedTheme } = useTheme();
const dispatch = useDispatch();
const method = item.draft ? get(item, 'draft.request.method') : get(item, 'request.method');
let url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
const url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
const [methodSelectorWidth, setMethodSelectorWidth] = useState(90);
useEffect(() => {
const el = document.querySelector('.method-selector-container');
setMethodSelectorWidth(el.offsetWidth);
}, [method]);
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
const onUrlChange = (value) => {
dispatch(
requestUrlChanged({
@@ -36,19 +48,24 @@ const QueryUrl = ({ item, collection, handleRun }) => {
<div className="flex items-center h-full method-selector-container">
<HttpMethodSelector method={method} onMethodSelect={onMethodSelect} />
</div>
<div className="flex items-center flex-grow input-container h-full">
<input
className="px-3 w-full mousetrap"
type="text"
<div
className="flex items-center flex-grow input-container h-full"
style={{
color: 'yellow',
width: `calc(100% - ${methodSelectorWidth}px)`,
maxWidth: `calc(100% - ${methodSelectorWidth}px)`
}}
>
<SingleLineEditor
value={url}
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={(event) => onUrlChange(event.target.value)}
onSave={onSave}
theme={storedTheme}
onChange={(newValue) => onUrlChange(newValue)}
onRun={handleRun}
collection={collection}
/>
<div className="flex items-center h-full mr-2 cursor-pointer" onClick={handleRun}>
<img src={SendSvg.src} style={{ width: '22px' }} />
<div className="flex items-center h-full mr-2 cursor-pointer" id="send-request" onClick={handleRun}>
<SendIcon color={theme.requestTabPanel.url.icon} width={22} />
</div>
</div>
</StyledWrapper>

View File

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

View File

@@ -52,7 +52,7 @@ const RequestBodyMode = ({ item, collection }) => {
onModeChange('formUrlEncoded');
}}
>
Form Url Encoded
Form URL Encoded
</div>
<div className="label-item font-medium">Raw</div>
<div

View File

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

View File

@@ -5,32 +5,40 @@ const Wrapper = styled.div`
width: 100%;
border-collapse: collapse;
font-weight: 600;
table-layout: fixed;
thead,
td {
border: 1px solid #efefef;
border: 1px solid ${(props) => props.theme.table.border};
}
thead {
color: #616161;
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-header {
font-size: 0.8125rem;
margin-block: 10px;
padding: 5px;
}
input[type='text'] {
width: 100%;
border: solid 1px transparent;
outline: none !important;
background-color: inherit;
&:focus {
outline: none !important;

View File

@@ -3,11 +3,15 @@ import get from 'lodash/get';
import cloneDeep from 'lodash/cloneDeep';
import { IconTrash } from '@tabler/icons';
import { useDispatch } from 'react-redux';
import { useTheme } from 'providers/Theme';
import { addRequestHeader, updateRequestHeader, deleteRequestHeader } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import SingleLineEditor from 'components/SingleLineEditor';
import StyledWrapper from './StyledWrapper';
const RequestHeaders = ({ item, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const headers = item.draft ? get(item, 'draft.request.headers') : get(item, 'request.headers');
const addHeader = () => {
@@ -19,6 +23,8 @@ const RequestHeaders = ({ item, collection }) => {
);
};
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
const handleRun = () => dispatch(sendRequest(item, collection.uid));
const handleHeaderValueChange = (e, _header, type) => {
const header = cloneDeep(_header);
switch (type) {
@@ -30,10 +36,6 @@ const RequestHeaders = ({ item, collection }) => {
header.value = e.target.value;
break;
}
case 'description': {
header.description = e.target.value;
break;
}
case 'enabled': {
header.enabled = e.target.checked;
break;
@@ -63,9 +65,8 @@ const RequestHeaders = ({ item, collection }) => {
<table>
<thead>
<tr>
<td>Key</td>
<td>Name</td>
<td>Value</td>
<td>Description</td>
<td></td>
</tr>
</thead>
@@ -87,32 +88,33 @@ const RequestHeaders = ({ item, collection }) => {
/>
</td>
<td>
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
<SingleLineEditor
value={header.value}
className="mousetrap"
onChange={(e) => handleHeaderValueChange(e, header, 'value')}
/>
</td>
<td>
<input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={header.description}
className="mousetrap"
onChange={(e) => handleHeaderValueChange(e, header, 'description')}
theme={storedTheme}
onSave={onSave}
onChange={(newValue) =>
handleHeaderValueChange(
{
target: {
value: newValue
}
},
header,
'value'
)
}
onRun={handleRun}
collection={collection}
/>
</td>
<td>
<div className="flex items-center">
<input type="checkbox" checked={header.enabled} className="mr-3 mousetrap" onChange={(e) => handleHeaderValueChange(e, header, 'enabled')} />
<input
type="checkbox"
checked={header.enabled}
className="mr-3 mousetrap"
onChange={(e) => handleHeaderValueChange(e, header, 'enabled')}
/>
<button onClick={() => handleRemoveHeader(header)}>
<IconTrash strokeWidth={1.5} size={20} />
</button>
@@ -124,7 +126,7 @@ const RequestHeaders = ({ item, collection }) => {
: null}
</tbody>
</table>
<button className="btn-add-header select-none" onClick={addHeader}>
<button className="btn-add-header text-link pr-2 py-3 mt-2 select-none" onClick={addHeader}>
+ Add Header
</button>
</StyledWrapper>

View File

@@ -28,7 +28,14 @@ const SaveRequest = ({ items, onClose }) => {
return (
<StyledWrapper>
<Modal size="md" title="Save Request" confirmText="Save" cancelText="Cancel" handleCancel={onClose} handleConfirm={onClose}>
<Modal
size="md"
title="Save Request"
confirmText="Save"
cancelText="Cancel"
handleCancel={onClose}
handleConfirm={onClose}
>
<p className="mb-2">Select a folder to save request:</p>
<div className="folder-list">
{showFolders && showFolders.length

View File

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

View File

@@ -0,0 +1,70 @@
import React from 'react';
import get from 'lodash/get';
import { useDispatch } from 'react-redux';
import CodeEditor from 'components/CodeEditor';
import { updateRequestScript, updateResponseScript } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import { useTheme } from 'providers/Theme';
import StyledWrapper from './StyledWrapper';
const Script = ({ item, collection }) => {
const dispatch = useDispatch();
const requestScript = item.draft ? get(item, 'draft.request.script.req') : get(item, 'request.script.req');
const responseScript = item.draft ? get(item, 'draft.request.script.res') : get(item, 'request.script.res');
const { storedTheme } = useTheme();
const onRequestScriptEdit = (value) => {
dispatch(
updateRequestScript({
script: value,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
const onResponseScriptEdit = (value) => {
dispatch(
updateResponseScript({
script: value,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
const onRun = () => dispatch(sendRequest(item, collection.uid));
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
return (
<StyledWrapper className="w-full flex flex-col">
<div className="flex-1 mt-2">
<div className="mb-1 title text-xs">Pre Request</div>
<CodeEditor
collection={collection}
value={requestScript || ''}
theme={storedTheme}
onEdit={onRequestScriptEdit}
mode="javascript"
onRun={onRun}
onSave={onSave}
/>
</div>
<div className="flex-1 mt-6">
<div className="mt-1 mb-1 title text-xs">Post Response</div>
<CodeEditor
collection={collection}
value={responseScript || ''}
theme={storedTheme}
onEdit={onResponseScriptEdit}
mode="javascript"
onRun={onRun}
onSave={onSave}
/>
</div>
</StyledWrapper>
);
};
export default Script;

View File

@@ -0,0 +1,10 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
div.CodeMirror {
/* todo: find a better way */
height: calc(100vh - 220px);
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,44 @@
import React from 'react';
import get from 'lodash/get';
import { useDispatch } from 'react-redux';
import CodeEditor from 'components/CodeEditor';
import { updateRequestTests } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import { useTheme } from 'providers/Theme';
import StyledWrapper from './StyledWrapper';
const Tests = ({ item, collection }) => {
const dispatch = useDispatch();
const tests = item.draft ? get(item, 'draft.request.tests') : get(item, 'request.tests');
const { storedTheme } = useTheme();
const onEdit = (value) => {
dispatch(
updateRequestTests({
tests: value,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
const onRun = () => dispatch(sendRequest(item, collection.uid));
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
return (
<StyledWrapper className="w-full">
<CodeEditor
collection={collection}
value={tests || ''}
theme={storedTheme}
onEdit={onEdit}
mode="javascript"
onRun={onRun}
onSave={onSave}
/>
</StyledWrapper>
);
};
export default Tests;

View File

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

View File

@@ -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;

View File

@@ -0,0 +1,151 @@
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 { addVar, updateVar, deleteVar } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import SingleLineEditor from 'components/SingleLineEditor';
import Tooltip from 'components/Tooltip';
import StyledWrapper from './StyledWrapper';
const VarsTable = ({ item, collection, vars, varType }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const handleAddVar = () => {
dispatch(
addVar({
type: varType,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
const handleRun = () => dispatch(sendRequest(item, collection.uid));
const handleVarChange = (e, v, type) => {
const _var = cloneDeep(v);
switch (type) {
case 'name': {
_var.name = e.target.value;
break;
}
case 'value': {
_var.value = e.target.value;
break;
}
case 'enabled': {
_var.enabled = e.target.checked;
break;
}
}
dispatch(
updateVar({
type: varType,
var: _var,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
const handleRemoveVar = (_var) => {
dispatch(
deleteVar({
type: varType,
varUid: _var.uid,
itemUid: item.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>
<Tooltip text="You can write any valid JS Template Literal here" tooltipId="request-var" />
</div>
</td>
) : (
<td>
<div className="flex items-center">
<span>Expr</span>
<Tooltip text="You can write any valid JS expression here" tooltipId="response-var" />
</div>
</td>
)}
<td></td>
</tr>
</thead>
<tbody>
{vars && vars.length
? vars.map((_var, index) => {
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'
)
}
onRun={handleRun}
collection={collection}
/>
</td>
<td>
<div className="flex items-center">
<input
type="checkbox"
checked={_var.enabled}
className="mr-3 mousetrap"
onChange={(e) => handleVarChange(e, _var, 'enabled')}
/>
<button 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={handleAddVar}>
+ Add
</button>
</StyledWrapper>
);
};
export default VarsTable;

View File

@@ -0,0 +1,54 @@
import React from 'react';
import get from 'lodash/get';
import { useDispatch } from 'react-redux';
import { updateRequestScript, updateResponseScript } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import { useTheme } from 'providers/Theme';
import VarsTable from './VarsTable';
import StyledWrapper from './StyledWrapper';
const Vars = ({ item, collection }) => {
const dispatch = useDispatch();
const requestVars = item.draft ? get(item, 'draft.request.vars.req') : get(item, 'request.vars.req');
const responseVars = item.draft ? get(item, 'draft.request.vars.res') : get(item, 'request.vars.res');
const { storedTheme } = useTheme();
const onRequestScriptEdit = (value) => {
dispatch(
updateRequestScript({
script: value,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
const onResponseScriptEdit = (value) => {
dispatch(
updateResponseScript({
script: value,
itemUid: item.uid,
collectionUid: collection.uid
})
);
};
const onRun = () => dispatch(sendRequest(item, collection.uid));
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
return (
<StyledWrapper className="w-full flex flex-col">
<div className="flex-1 mt-2">
<div className="mb-1 title text-xs">Pre Request</div>
<VarsTable item={item} collection={collection} vars={requestVars} varType="request" />
</div>
<div className="flex-1">
<div className="mt-1 mb-1 title text-xs">Post Response</div>
<VarsTable item={item} collection={collection} vars={responseVars} varType="response" />
</div>
</StyledWrapper>
);
};
export default Vars;

View File

@@ -1,9 +1,10 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import { closeTabs } from 'providers/ReduxStore/slices/tabs';
import { useDispatch } from 'react-redux';
const RequestNotFound = ({ itemUid }) => {
const dispatch = useDispatch();
const [showErrorMessage, setShowErrorMessage] = useState(false);
const closeTab = () => {
dispatch(
@@ -13,11 +14,27 @@ const RequestNotFound = ({ itemUid }) => {
);
};
useEffect(() => {
setTimeout(() => {
setShowErrorMessage(true);
}, 300);
}, []);
// add a delay component in react that shows a loading spinner
// and then shows the error message after a delay
// this will prevent the error message from flashing on the screen
if (!showErrorMessage) {
return null;
}
return (
<div className="mt-6 px-6">
<div className="p-4 bg-orange-100 border-l-4 border-yellow-500 text-yellow-700 bg-yellow-100 p-4">
<div>Request no longer exists.</div>
<div className="mt-2">This can happen when the yml file associated with this request was deleted on your filesystem.</div>
<div className="mt-2">
This can happen when the .bru file associated with this request was deleted on your filesystem.
</div>
</div>
<button className="btn btn-md btn-secondary mt-6" onClick={closeTab}>
Close Tab

View File

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

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import find from 'lodash/find';
import toast from 'react-hot-toast';
import { useSelector, useDispatch } from 'react-redux';
@@ -12,10 +12,16 @@ import { sendRequest } from 'providers/ReduxStore/slices/collections/actions';
import RequestNotFound from './RequestNotFound';
import QueryUrl from 'components/RequestPane/QueryUrl';
import NetworkError from 'components/ResponsePane/NetworkError';
import useGraphqlSchema from '../../hooks/useGraphqlSchema';
import RunnerResults from 'components/RunnerResults';
import VariablesEditor from 'components/VariablesEditor';
import { DocExplorer } from '@usebruno/graphql-docs';
import StyledWrapper from './StyledWrapper';
const MIN_LEFT_PANE_WIDTH = 300;
const MIN_RIGHT_PANE_WIDTH = 350;
const DEFAULT_PADDING = 5;
const RequestTabPanel = () => {
if (typeof window == 'undefined') {
return <div></div>;
@@ -28,24 +34,49 @@ const RequestTabPanel = () => {
let asideWidth = useSelector((state) => state.app.leftSidebarWidth);
const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
const [leftPaneWidth, setLeftPaneWidth] = useState(focusedTab && focusedTab.requestPaneWidth ? focusedTab.requestPaneWidth : (screenWidth - asideWidth) / 2.2); // 2.2 so that request pane is relatively smaller
const [rightPaneWidth, setRightPaneWidth] = useState(screenWidth - asideWidth - leftPaneWidth - 5);
const [leftPaneWidth, setLeftPaneWidth] = useState(
focusedTab && focusedTab.requestPaneWidth ? focusedTab.requestPaneWidth : (screenWidth - asideWidth) / 2.2
); // 2.2 so that request pane is relatively smaller
const [rightPaneWidth, setRightPaneWidth] = useState(screenWidth - asideWidth - leftPaneWidth - DEFAULT_PADDING);
const [dragging, setDragging] = useState(false);
// Not a recommended pattern here to have the child component
// make a callback to set state, but treating this as an exception
const docExplorerRef = useRef(null);
const [schema, setSchema] = useState(null);
const [showGqlDocs, setShowGqlDocs] = useState(false);
const onSchemaLoad = (schema) => setSchema(schema);
const toggleDocs = () => setShowGqlDocs((showGqlDocs) => !showGqlDocs);
const handleGqlClickReference = (reference) => {
if (docExplorerRef.current) {
docExplorerRef.current.showDocForReference(reference);
}
if (!showGqlDocs) {
setShowGqlDocs(true);
}
};
useEffect(() => {
const leftPaneWidth = (screenWidth - asideWidth) / 2.2;
setLeftPaneWidth(leftPaneWidth);
}, [screenWidth]);
useEffect(() => {
setRightPaneWidth(screenWidth - asideWidth - leftPaneWidth - 5);
setRightPaneWidth(screenWidth - asideWidth - leftPaneWidth - DEFAULT_PADDING);
}, [screenWidth, asideWidth, leftPaneWidth]);
const handleMouseMove = (e) => {
if (dragging) {
e.preventDefault();
setLeftPaneWidth(e.clientX - asideWidth - 5);
setRightPaneWidth(screenWidth - e.clientX - 5);
let leftPaneXPosition = e.clientX + 2;
if (
leftPaneXPosition < asideWidth + DEFAULT_PADDING + MIN_LEFT_PANE_WIDTH ||
leftPaneXPosition > screenWidth - MIN_RIGHT_PANE_WIDTH
) {
return;
}
setLeftPaneWidth(leftPaneXPosition - asideWidth);
setRightPaneWidth(screenWidth - e.clientX - DEFAULT_PADDING);
}
};
const handleMouseUp = (e) => {
@@ -55,7 +86,7 @@ const RequestTabPanel = () => {
dispatch(
updateRequestPaneTabWidth({
uid: activeTabUid,
requestPaneWidth: e.clientX - asideWidth - 5
requestPaneWidth: e.clientX - asideWidth - DEFAULT_PADDING
})
);
}
@@ -65,11 +96,6 @@ const RequestTabPanel = () => {
setDragging(true);
};
let schema = null;
// let {
// schema
// } = useGraphqlSchema('https://api.spacex.land/graphql');
useEffect(() => {
document.addEventListener('mouseup', handleMouseUp);
document.addEventListener('mousemove', handleMouseMove);
@@ -93,6 +119,15 @@ const RequestTabPanel = () => {
return <div className="pb-4 px-4">Collection not found!</div>;
}
const showRunner = collection.showRunner;
if (showRunner) {
return <RunnerResults collection={collection} />;
}
if (focusedTab.type === 'variables') {
return <VariablesEditor collection={collection} />;
}
const item = findItemInCollection(collection, activeTabUid);
if (!item || !item.uid) {
return <RequestNotFound itemUid={activeTabUid} />;
@@ -105,28 +140,35 @@ const RequestTabPanel = () => {
})
);
};
const onGraphqlQueryChange = (value) => {};
const runQuery = async () => {};
return (
<StyledWrapper className={`flex flex-col flex-grow ${dragging ? 'dragging' : ''}`}>
<StyledWrapper className={`flex flex-col flex-grow relative ${dragging ? 'dragging' : ''}`}>
<div className="pt-4 pb-3 px-4">
<QueryUrl item={item} collection={collection} handleRun={handleRun} />
</div>
<section className="main flex flex-grow pb-4">
<section className="main flex flex-grow pb-4 relative">
<section className="request-pane">
<div className="px-4" style={{ width: `${leftPaneWidth}px`, height: 'calc(100% - 5px)' }}>
<div
className="px-4"
style={{
width: `${Math.max(leftPaneWidth, MIN_LEFT_PANE_WIDTH)}px`,
height: `calc(100% - ${DEFAULT_PADDING}px)`
}}
>
{item.type === 'graphql-request' ? (
<GraphQLRequestPane
onRunQuery={runQuery}
schema={schema}
item={item}
collection={collection}
leftPaneWidth={leftPaneWidth}
value={item.request.body.graphql.query}
onQueryChange={onGraphqlQueryChange}
onSchemaLoad={onSchemaLoad}
toggleDocs={toggleDocs}
handleGqlClickReference={handleGqlClickReference}
/>
) : null}
{item.type === 'http-request' ? <HttpRequestPane item={item} collection={collection} leftPaneWidth={leftPaneWidth} /> : null}
{item.type === 'http-request' ? (
<HttpRequestPane item={item} collection={collection} leftPaneWidth={leftPaneWidth} />
) : null}
</div>
</section>
@@ -138,6 +180,16 @@ const RequestTabPanel = () => {
<ResponsePane item={item} collection={collection} rightPaneWidth={rightPaneWidth} response={item.response} />
</section>
</section>
{item.type === 'graphql-request' ? (
<div className={`graphql-docs-explorer-container ${showGqlDocs ? '' : 'hidden'}`}>
<DocExplorer schema={schema} ref={(r) => (docExplorerRef.current = r)}>
<button className="mr-2" onClick={toggleDocs} aria-label="Close Documentation Explorer">
{'\u2715'}
</button>
</DocExplorer>
</div>
) : null}
</StyledWrapper>
);
};

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