feat: theme + ux overhaul

This commit is contained in:
Anoop M D
2025-12-27 09:07:27 +05:30
parent d37bf7a5ad
commit c7449ec389
123 changed files with 10473 additions and 1513 deletions

192
package-lock.json generated
View File

@@ -22,6 +22,9 @@
"packages/bruno-requests",
"packages/bruno-filestore"
],
"dependencies": {
"ajv": "^8.17.1"
},
"devDependencies": {
"@eslint/compat": "^1.3.2",
"@faker-js/faker": "^7.6.0",
@@ -1604,7 +1607,7 @@
"version": "7.26.3",
"resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz",
"integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.25.9",
@@ -1622,7 +1625,7 @@
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz",
"integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-compilation-targets": "^7.22.6",
@@ -1639,7 +1642,7 @@
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -1657,7 +1660,7 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"devOptional": true,
"dev": true,
"license": "MIT"
},
"node_modules/@babel/helper-globals": {
@@ -1737,7 +1740,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz",
"integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.25.9",
@@ -1812,7 +1815,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz",
"integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/template": "^7.25.9",
@@ -1855,7 +1858,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz",
"integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9",
@@ -1872,7 +1875,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz",
"integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -1888,7 +1891,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz",
"integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -1904,7 +1907,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz",
"integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9",
@@ -1922,7 +1925,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz",
"integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9",
@@ -1957,7 +1960,7 @@
"version": "7.21.0-placeholder-for-preset-env.2",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
"integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
"devOptional": true,
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -2056,7 +2059,7 @@
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz",
"integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -2072,7 +2075,7 @@
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz",
"integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -2254,7 +2257,7 @@
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz",
"integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.18.6",
@@ -2271,7 +2274,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz",
"integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -2287,7 +2290,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz",
"integrity": "sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9",
@@ -2305,7 +2308,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz",
"integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.25.9",
@@ -2323,7 +2326,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz",
"integrity": "sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -2339,7 +2342,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz",
"integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -2371,7 +2374,7 @@
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz",
"integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.25.9",
@@ -2388,7 +2391,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz",
"integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.25.9",
@@ -2409,7 +2412,7 @@
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"devOptional": true,
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -2419,7 +2422,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz",
"integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9",
@@ -2436,7 +2439,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz",
"integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -2452,7 +2455,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz",
"integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.25.9",
@@ -2469,7 +2472,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz",
"integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -2485,7 +2488,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz",
"integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.25.9",
@@ -2502,7 +2505,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz",
"integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -2518,7 +2521,7 @@
"version": "7.26.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz",
"integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -2534,7 +2537,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz",
"integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -2566,7 +2569,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz",
"integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9",
@@ -2583,7 +2586,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz",
"integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-compilation-targets": "^7.25.9",
@@ -2601,7 +2604,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz",
"integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -2617,7 +2620,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz",
"integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -2633,7 +2636,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz",
"integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -2649,7 +2652,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz",
"integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -2665,7 +2668,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz",
"integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-module-transforms": "^7.25.9",
@@ -2698,7 +2701,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz",
"integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-module-transforms": "^7.25.9",
@@ -2717,7 +2720,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz",
"integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-module-transforms": "^7.25.9",
@@ -2734,7 +2737,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz",
"integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.25.9",
@@ -2751,7 +2754,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz",
"integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -2782,7 +2785,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz",
"integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -2798,7 +2801,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz",
"integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-compilation-targets": "^7.25.9",
@@ -2816,7 +2819,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz",
"integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9",
@@ -2833,7 +2836,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz",
"integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -2865,7 +2868,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz",
"integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -2897,7 +2900,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz",
"integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.25.9",
@@ -2915,7 +2918,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz",
"integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -3000,7 +3003,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz",
"integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9",
@@ -3017,7 +3020,7 @@
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz",
"integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.25.9",
@@ -3034,7 +3037,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz",
"integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -3050,7 +3053,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz",
"integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -3066,7 +3069,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz",
"integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9",
@@ -3083,7 +3086,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz",
"integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -3099,7 +3102,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz",
"integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -3115,7 +3118,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz",
"integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -3150,7 +3153,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz",
"integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
@@ -3166,7 +3169,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz",
"integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.25.9",
@@ -3183,7 +3186,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz",
"integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.25.9",
@@ -3200,7 +3203,7 @@
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz",
"integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.25.9",
@@ -3217,7 +3220,7 @@
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.0.tgz",
"integrity": "sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.26.0",
@@ -3318,7 +3321,7 @@
"version": "0.1.6-no-external-plugins",
"resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz",
"integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.0.0",
@@ -9547,6 +9550,7 @@
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.10.4",
@@ -9566,6 +9570,7 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
@@ -9578,6 +9583,7 @@
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1",
@@ -9592,6 +9598,7 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true,
"license": "MIT"
},
"node_modules/@testing-library/jest-dom": {
@@ -9702,6 +9709,7 @@
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/babel__core": {
@@ -9994,6 +10002,7 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/lodash": {
@@ -10016,6 +10025,7 @@
"version": "12.2.3",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz",
"integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/linkify-it": "*",
@@ -10026,6 +10036,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/ms": {
@@ -11400,6 +11411,7 @@
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"dequal": "^2.0.3"
@@ -11844,7 +11856,7 @@
"version": "0.4.12",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz",
"integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.22.6",
@@ -11859,7 +11871,7 @@
"version": "0.10.6",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz",
"integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-define-polyfill-provider": "^0.6.2",
@@ -11873,7 +11885,7 @@
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz",
"integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-define-polyfill-provider": "^0.6.3"
@@ -13763,7 +13775,7 @@
"version": "3.39.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.39.0.tgz",
"integrity": "sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"browserslist": "^4.24.2"
@@ -14912,6 +14924,7 @@
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
"dev": true,
"license": "MIT"
},
"node_modules/dom-converter": {
@@ -15984,7 +15997,7 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"devOptional": true,
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.10.0"
@@ -18860,7 +18873,7 @@
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"hasown": "^2.0.2"
@@ -20870,6 +20883,15 @@
"graceful-fs": "^4.1.6"
}
},
"node_modules/jsonschema": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.5.0.tgz",
"integrity": "sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/jsprim": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz",
@@ -21317,6 +21339,7 @@
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
"dev": true,
"license": "MIT",
"bin": {
"lz-string": "bin/bin.js"
@@ -22931,7 +22954,7 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"devOptional": true,
"dev": true,
"license": "MIT"
},
"node_modules/path-scurry": {
@@ -25458,14 +25481,14 @@
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
"integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==",
"devOptional": true,
"dev": true,
"license": "MIT"
},
"node_modules/regenerate-unicode-properties": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz",
"integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"regenerate": "^1.4.2"
@@ -25484,7 +25507,7 @@
"version": "0.15.2",
"resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz",
"integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.8.4"
@@ -25494,7 +25517,7 @@
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz",
"integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"regenerate": "^1.4.2",
@@ -25512,14 +25535,14 @@
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz",
"integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==",
"devOptional": true,
"dev": true,
"license": "MIT"
},
"node_modules/regjsparser": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz",
"integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==",
"devOptional": true,
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"jsesc": "~3.0.2"
@@ -25532,7 +25555,7 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
"integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
"devOptional": true,
"dev": true,
"license": "MIT",
"bin": {
"jsesc": "bin/jsesc"
@@ -25728,7 +25751,7 @@
"version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"is-core-module": "^2.16.0",
@@ -28046,7 +28069,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"devOptional": true,
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -29233,7 +29256,7 @@
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"devOptional": true,
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@@ -29294,7 +29317,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
"integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==",
"devOptional": true,
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -29304,7 +29327,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
"integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"unicode-canonical-property-names-ecmascript": "^2.0.0",
@@ -29318,7 +29341,7 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz",
"integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==",
"devOptional": true,
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -29328,7 +29351,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz",
"integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==",
"devOptional": true,
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -30437,6 +30460,7 @@
"json5": "^2.2.3",
"jsonc-parser": "^3.2.1",
"jsonpath-plus": "^10.3.0",
"jsonschema": "^1.5.0",
"know-your-http-well": "^0.5.0",
"linkify-it": "^5.0.0",
"lodash": "^4.17.21",

View File

@@ -20,17 +20,16 @@
],
"homepage": "https://usebruno.com",
"devDependencies": {
"@storybook/addon-webpack5-compiler-babel": "^4.0.0",
"@storybook/builder-webpack5": "^10.1.10",
"@storybook/react": "^10.1.10",
"@storybook/react-webpack5": "^10.1.10",
"storybook": "^10.1.10",
"@eslint/compat": "^1.3.2",
"@faker-js/faker": "^7.6.0",
"@jest/globals": "^29.2.0",
"@opencollection/types": "0.3.0",
"@playwright/test": "^1.51.1",
"@rollup/plugin-json": "^6.1.0",
"@storybook/addon-webpack5-compiler-babel": "^4.0.0",
"@storybook/builder-webpack5": "^10.1.10",
"@storybook/react": "^10.1.10",
"@storybook/react-webpack5": "^10.1.10",
"@stylistic/eslint-plugin": "^5.3.1",
"@types/jest": "^29.5.11",
"@types/lodash-es": "^4.17.12",
@@ -49,6 +48,7 @@
"pretty-quick": "^3.1.3",
"randomstring": "^1.2.2",
"rimraf": "^6.0.1",
"storybook": "^10.1.10",
"ts-jest": "^29.2.6"
},
"scripts": {
@@ -62,6 +62,7 @@
"prettier:web": "npm run prettier --workspace=packages/bruno-app",
"dev:electron": "npm run dev --workspace=packages/bruno-electron",
"dev:electron:debug": "npm run debug --workspace=packages/bruno-electron",
"storybook": "npm run storybook --workspace=packages/bruno-app",
"build:bruno-common": "npm run build --workspace=packages/bruno-common",
"build:bruno-requests": "npm run build --workspace=packages/bruno-requests",
"build:bruno-filestore": "npm run build --workspace=packages/bruno-filestore",
@@ -97,5 +98,8 @@
"json-schema-typed": "8.0.1"
}
}
},
"dependencies": {
"ajv": "^8.17.1"
}
}

View File

@@ -52,6 +52,7 @@
"json5": "^2.2.3",
"jsonc-parser": "^3.2.1",
"jsonpath-plus": "^10.3.0",
"jsonschema": "^1.5.0",
"know-your-http-well": "^0.5.0",
"linkify-it": "^5.0.0",
"lodash": "^4.17.21",

View File

@@ -246,12 +246,6 @@ const Wrapper = styled.div`
color: white;
}
}
.dropdown-item-active {
font-weight: 400 !important;
background-color: ${(props) => props.theme.dropdown.selectedBg} !important;
color: ${(props) => props.theme.dropdown.selectedColor} !important;
}
`;
export default Wrapper;

View File

@@ -19,7 +19,7 @@ const StyledWrapper = styled.div`
}
.selected-body-mode {
color: ${(props) => props.theme.colors.text.yellow};
color: ${(props) => props.theme.brand};
}
.dropdown-icon {

View File

@@ -5,9 +5,9 @@ const StyledWrapper = styled.div`
background-color: ${(props) => props.theme.requestTabPanel.card.bg};
.title {
border-top: 1px solid ${(props) => props.theme.requestTabPanel.cardTable.border};
border-left: 1px solid ${(props) => props.theme.requestTabPanel.cardTable.border};
border-right: 1px solid ${(props) => props.theme.requestTabPanel.cardTable.border};
border-top: 1px solid ${(props) => props.theme.border.BORDER0};
border-left: 1px solid ${(props) => props.theme.border.BORDER0};
border-right: 1px solid ${(props) => props.theme.border.BORDER0};
border-top-left-radius: 3px;
border-top-right-radius: 3px;
@@ -15,8 +15,8 @@ const StyledWrapper = styled.div`
.table {
thead {
background-color: ${(props) => props.theme.requestTabPanel.cardTable.table.thead.bg};
color: ${(props) => props.theme.requestTabPanel.cardTable.table.thead.color};
color: ${(props) => props.theme.table.thead.color} !important;
background: ${(props) => props.theme.sidebar.bg};
}
}
}

View File

@@ -8,7 +8,7 @@ const StyledWrapper = styled.div`
}
div.title {
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
}
`;

View File

@@ -7,7 +7,7 @@ const StyledWrapper = styled.div`
border: none;
border-bottom: solid 2px transparent;
margin-right: ${(props) => props.theme.tabs.marginRight};
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
cursor: pointer;
&:focus,

View File

@@ -4,7 +4,7 @@ const StyledWrapper = styled.div`
max-width: 800px;
div.title {
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
}
`;

View File

@@ -1,4 +1,5 @@
import styled from 'styled-components';
import { rgba } from 'polished';
const Wrapper = styled.div`
min-width: 160px;
@@ -136,17 +137,21 @@ const Wrapper = styled.div`
/* Active/selected state - applied to the currently selected item */
&.dropdown-item-active {
color: ${({ theme }) => theme.colors.text.yellow};
background-color: ${({ theme }) => theme.dropdown.activeBg};
font-weight: 500;
color: ${({ theme }) => theme.dropdown.selectedColor} !important;
background-color: ${({ theme }) => rgba(theme.dropdown.selectedColor, 0.07)} !important;
.dropdown-icon {
color: ${({ theme }) => theme.colors.text.yellow};
color: ${({ theme }) => theme.dropdown.selectedColor} !important;
}
&:hover {
color: ${({ theme }) => theme.dropdown.selectedColor} !important;
background-color: ${({ theme }) => rgba(theme.dropdown.selectedColor, 0.07)} !important;
}
}
/* Combined state - when active item is also focused */
&.dropdown-item-active.dropdown-item-focused {
background-color: ${({ theme }) => theme.dropdown.activeHoverBg};
background-color: ${({ theme }) => rgba(theme.dropdown.selectedColor, 0.07)} !important;
}
/* Focus visible for accessibility */

View File

@@ -9,7 +9,7 @@ const StyledWrapper = styled.div`
.table-container {
overflow-y: auto;
border-radius: ${(props) => props.theme.border.radius.base};
border: ${(props) => props.theme.workspace.environments.indentBorder};
border: solid 1px ${(props) => props.theme.border.border0};
}
table {
@@ -31,8 +31,8 @@ const StyledWrapper = styled.div`
padding: 5px 10px !important;
border-top: none !important;
border-left: none !important;
border-bottom: ${(props) => props.theme.workspace.environments.indentBorder};
border-right: ${(props) => props.theme.workspace.environments.indentBorder};
border-bottom: solid 1px ${(props) => props.theme.border.border0};
border-right: solid 1px ${(props) => props.theme.border.border0};
vertical-align: middle;
&:last-child {
@@ -58,8 +58,8 @@ const StyledWrapper = styled.div`
padding: 1px 10px !important;
border-top: none !important;
border-left: none !important;
border-bottom: ${(props) => props.theme.workspace.environments.indentBorder};
border-right: ${(props) => props.theme.workspace.environments.indentBorder};
border-bottom: solid 1px ${(props) => props.theme.border.border0};
border-right: solid 1px ${(props) => props.theme.border.border0};
vertical-align: middle;
&:last-child {

View File

@@ -33,7 +33,7 @@ const EnvironmentListContent = ({
{environments.map((env) => (
<div
key={env.uid}
className={`dropdown-item ${env.uid === activeEnvironmentUid ? 'active' : ''}`}
className={`dropdown-item ${env.uid === activeEnvironmentUid ? 'dropdown-item-active' : ''}`}
onClick={() => onEnvironmentSelect(env)}
data-tooltip-content={env.name}
data-tooltip-hidden={env.name?.length < 90}

View File

@@ -65,35 +65,6 @@ const Wrapper = styled.div`
overflow: hidden;
}
.tippy-box .tippy-content {
padding: 0;
display: flex;
flex-direction: column;
height: 100%;
.dropdown-item {
display: flex;
align-items: center;
padding: 0.35rem 0.6rem;
cursor: pointer;
font-size: ${(props) => props.theme.font.size.base};
color: ${(props) => props.theme.dropdown.primaryText};
&:hover:not(:disabled) {
background-color: ${(props) => props.theme.dropdown.hoverBg};
}
&.active {
background-color: ${(props) => props.theme.dropdown.selectedBg};
color: ${(props) => props.theme.dropdown.selectedColor};
}
&.no-environment {
color: ${(props) => props.theme.dropdown.mutedText};
}
}
}
.configure-button {
position: absolute;
bottom: 0;
@@ -109,7 +80,7 @@ const Wrapper = styled.div`
}
button {
color: ${(props) => props.theme.dropdown.primaryText};
color: ${(props) => props.theme.text};
display: flex;
align-items: center;
justify-content: center;
@@ -119,7 +90,7 @@ const Wrapper = styled.div`
}
.tab-button {
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
font-size: ${(props) => props.theme.font.size.base};
.tab-content-wrapper {
@@ -170,7 +141,7 @@ const Wrapper = styled.div`
min-height: 12.5rem;
h3 {
color: ${(props) => props.theme.dropdown.primaryText};
color: ${(props) => props.theme.text};
font-size: 1rem;
font-weight: 500;
margin-bottom: 0.5rem;
@@ -178,7 +149,7 @@ const Wrapper = styled.div`
}
p {
color: ${(props) => props.theme.dropdown.primaryText};
color: ${(props) => props.theme.text};
opacity: 0.75;
font-size: ${(props) => props.theme.font.size.xs};
line-height: 1.5;
@@ -194,9 +165,9 @@ const Wrapper = styled.div`
}
.space-y-2 > button {
border: 0.0625rem solid ${(props) => props.theme.dropdown.primaryText};
border: 0.0625rem solid ${(props) => props.theme.text};
background: transparent;
color: ${(props) => props.theme.dropdown.primaryText};
color: ${(props) => props.theme.text};
padding: 0.5rem 1rem;
border-radius: 0.375rem;
width: 100%;
@@ -224,7 +195,7 @@ const Wrapper = styled.div`
align-items: center;
justify-content: center;
padding: 2rem 1rem;
color: ${(props) => props.theme.dropdown.primaryText};
color: ${(props) => props.theme.text};
font-size: ${(props) => props.theme.font.size.base};
line-height: 1.5;
text-align: center;
@@ -232,7 +203,7 @@ const Wrapper = styled.div`
svg {
margin: 0 auto 1rem auto;
color: ${(props) => props.theme.dropdown.primaryText};
color: ${(props) => props.theme.text};
opacity: 0.5;
}
}

View File

@@ -9,7 +9,7 @@ const Wrapper = styled.div`
.table-container {
overflow-y: auto;
border-radius: 8px;
border: ${(props) => props.theme.workspace.environments.indentBorder};
border: solid 1px ${(props) => props.theme.border.border0};
}
table {
@@ -46,8 +46,8 @@ const Wrapper = styled.div`
td {
padding: 5px 10px !important;
border-bottom: ${(props) => props.theme.workspace.environments.indentBorder};
border-right: ${(props) => props.theme.workspace.environments.indentBorder};
border-bottom: solid 1px ${(props) => props.theme.border.border0};
border-right: solid 1px ${(props) => props.theme.border.border0};
&:last-child {
border-right: none;
@@ -64,8 +64,8 @@ const Wrapper = styled.div`
}
td {
border-bottom: ${(props) => props.theme.workspace.environments.indentBorder};
border-right: ${(props) => props.theme.workspace.environments.indentBorder};
border-bottom: solid 1px ${(props) => props.theme.border.border0};
border-right: solid 1px ${(props) => props.theme.border.border0};
&:last-child {
border-right: none;
@@ -75,26 +75,6 @@ const Wrapper = styled.div`
}
}
.btn-add-param {
font-size: 12px;
color: ${(props) => props.theme.textLink};
font-weight: 500;
padding: 7px 14px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 6px;
border-radius: 6px;
border: ${(props) => props.theme.sidebar.collection.item.indentBorder};
background: transparent;
transition: all 0.15s ease;
&:hover {
background: ${(props) => props.theme.listItem.hoverBg};
border-color: ${(props) => props.theme.textLink};
}
}
.tooltip-mod {
font-size: 11px !important;
max-width: 200px !important;

View File

@@ -1,4 +1,5 @@
import styled from 'styled-components';
import { rgba } from 'polished';
const StyledWrapper = styled.div`
display: flex;
@@ -174,7 +175,7 @@ const StyledWrapper = styled.div`
}
&.active {
background: ${(props) => props.theme.workspace.environments.activeBg};
background: ${(props) => props.theme.background.surface0};
color: ${(props) => props.theme.text};
}
@@ -235,35 +236,34 @@ const StyledWrapper = styled.div`
gap: 2px;
margin-left: 4px;
}
}
.inline-action-btn {
display: flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
padding: 0;
background: transparent;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.15s ease;
.inline-action-btn {
display: flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
padding: 0;
background: transparent;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.15s ease;
&.save {
color: ${(props) => props.theme.colors.text.green};
&.save {
color: ${(props) => props.theme.textLink};
&:hover {
background: ${(props) => props.theme.listItem.hoverBg};
}
&:hover {
background: ${(props) => rgba(props.theme.colors.text.green, 0.1)};
}
}
&.cancel {
color: ${(props) => props.theme.colors.text.danger};
&.cancel {
color: ${(props) => props.theme.colors.text.muted};
&:hover {
background: ${(props) => props.theme.listItem.hoverBg};
color: ${(props) => props.theme.text};
}
&:hover {
background: ${(props) => rgba(props.theme.colors.text.danger, 0.1)};
}
}
}

View File

@@ -11,7 +11,7 @@ const StyledWrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-top: 10%;
flex: 1;
color: ${(props) => props.theme.colors.text.muted};
@@ -32,22 +32,6 @@ const StyledWrapper = styled.div`
gap: 8px;
}
}
.shared-button {
padding: 5px 10px;
font-size: 12px;
border-radius: 5px;
border: 1px solid ${(props) => props.theme.sidebar.collection.item.indentBorder};
background: ${(props) => props.theme.sidebar.bg};
color: ${(props) => props.theme.text};
cursor: pointer;
transition: all 0.1s ease;
&:hover {
background: ${(props) => props.theme.listItem.hoverBg};
border-color: ${(props) => props.theme.textLink};
}
}
`;
export default StyledWrapper;

View File

@@ -5,18 +5,19 @@ import StyledWrapper from './StyledWrapper';
import { IconFileAlert } from '@tabler/icons';
import ImportEnvironmentModal from 'components/Environments/Common/ImportEnvironmentModal';
import ExportEnvironmentModal from 'components/Environments/Common/ExportEnvironmentModal';
import Button from 'ui/Button';
const DefaultTab = ({ setTab }) => (
<div className="empty-state">
<IconFileAlert size={48} strokeWidth={1.5} />
<div className="title">No Environments</div>
<div className="actions">
<button className="shared-button" onClick={() => setTab('create')}>
<Button size="sm" color="secondary" onClick={() => setTab('create')}>
Create Environment
</button>
<button className="shared-button" onClick={() => setTab('import')}>
</Button>
<Button size="sm" color="secondary" onClick={() => setTab('import')}>
Import Environment
</button>
</Button>
</div>
</div>
);

View File

@@ -6,7 +6,7 @@ const StyledWrapper = styled.div`
}
div.title {
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
}
`;

View File

@@ -9,7 +9,7 @@ const StyledWrapper = styled.div`
border: none;
border-bottom: solid 2px transparent;
margin-right: ${(props) => props.theme.tabs.marginRight};
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
cursor: pointer;
&:focus,

View File

@@ -2,7 +2,7 @@ import styled from 'styled-components';
const StyledWrapper = styled.div`
div.title {
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
}
`;

View File

@@ -100,15 +100,14 @@ const StyledWrapper = styled.div`
.default-badge {
padding: 1px 6px;
border-radius: ${(props) => props.theme.border.radius.sm};
background: ${(props) => props.theme.sidebar.badge.bg};
color: ${(props) => props.theme.colors.text.muted};
background: ${(props) => props.theme.background.surface1};
color: ${(props) => props.theme.text};
font-size: ${(props) => props.theme.font.size.xs};
font-weight: 500;
}
.workspace-path {
font-size: ${(props) => props.theme.font.size.xs};
color: ${(props) => props.theme.colors.text.muted};
color: ${(props) => props.theme.text.muted};
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;

View File

@@ -1,4 +1,5 @@
import styled from 'styled-components';
import { rgba } from 'polished';
const Wrapper = styled.div`
color: ${(props) => props.theme.text};
@@ -72,7 +73,6 @@ const Wrapper = styled.div`
background-color: ${(props) => props.theme.modal.title.bg};
font-size: ${(props) => props.theme.font.size.md};
padding: 0.5rem 1rem;
font-weight: 500;
border-top-left-radius: ${(props) => props.theme.border.radius.base};
border-top-right-radius: ${(props) => props.theme.border.radius.base};
@@ -98,7 +98,7 @@ const Wrapper = styled.div`
&:hover {
opacity: 1;
background-color: ${(props) => props.theme.modal.closeButton.hoverBg};
background-color: ${(props) => rgba(props.theme.modal.title.color, 0.1)};
}
}
}

View File

@@ -1,17 +1,9 @@
import React from 'react';
import Font from './Font/index';
import Theme from './Theme/index';
const Display = ({ close }) => {
return (
<div className="flex flex-col my-2 gap-10 w-full">
<div className="w-full flex flex-col gap-2">
<span>
Theme
</span>
<Theme close={close} />
</div>
<div className="h-[1px] bg-[#aaa5] w-full"></div>
<div className="w-fit flex flex-col gap-2">
<Font close={close} />
</div>

View File

@@ -0,0 +1,97 @@
import styled from 'styled-components';
import { rgba } from 'polished';
const StyledWrapper = styled.div`
.theme-mode-selector {
display: flex;
gap: 16px;
margin-bottom: 24px;
}
.theme-mode-option {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
}
.theme-variant-section {
margin-top: 20px;
}
.theme-variant-label {
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.muted};
margin-bottom: 12px;
font-weight: 500;
}
.theme-variants {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.theme-variant-card {
display: flex;
flex-direction: column;
align-items: center;
padding: 12px 16px;
border: 2px solid ${(props) => props.theme.input.border};
border-radius: ${(props) => props.theme.border.radius.md};
cursor: pointer;
transition: all 0.15s ease;
min-width: 120px;
&:hover {
border-color: ${(props) => props.theme.input.focusBorder};
}
&.selected {
border-color: ${(props) => props.theme.accents.primary};
background: ${(props) => rgba(props.theme.accents.primary, 0.07)};
}
}
.theme-preview {
width: 60px;
height: 40px;
border-radius: ${(props) => props.theme.border.radius.sm};
margin-bottom: 8px;
display: flex;
overflow: hidden;
}
.theme-preview-sidebar {
width: 15px;
height: 100%;
}
.theme-preview-main {
flex: 1;
height: 100%;
display: flex;
flex-direction: column;
padding: 4px;
gap: 3px;
}
.theme-preview-line {
height: 4px;
border-radius: 2px;
width: 80%;
}
.theme-variant-name {
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.text};
}
.section-divider {
height: 1px;
background: ${(props) => props.theme.input.border};
margin: 24px 0;
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,130 @@
import React from 'react';
import { useTheme } from 'providers/Theme';
import { getLightThemes, getDarkThemes } from 'themes/index';
import StyledWrapper from './StyledWrapper';
const ThemePreview = ({ themeId, isDark }) => {
const bgColor = isDark ? '#1e1e1e' : '#ffffff';
const sidebarColor = isDark ? '#252526' : '#f8f8f8';
const lineColor = isDark ? '#3d3d3d' : '#e5e5e5';
return (
<div className="theme-preview" style={{ background: bgColor, border: `1px solid ${lineColor}` }}>
<div className="theme-preview-sidebar" style={{ background: sidebarColor }} />
<div className="theme-preview-main">
<div className="theme-preview-line" style={{ background: lineColor }} />
<div className="theme-preview-line" style={{ background: lineColor, width: '60%' }} />
<div className="theme-preview-line" style={{ background: lineColor, width: '70%' }} />
</div>
</div>
);
};
const ThemeVariantCard = ({ theme, isSelected, onClick }) => {
const isDark = theme.mode === 'dark';
return (
<div className={`theme-variant-card ${isSelected ? 'selected' : ''}`} onClick={onClick}>
<ThemePreview themeId={theme.id} isDark={isDark} />
<span className="theme-variant-name">{theme.name}</span>
</div>
);
};
const Themes = () => {
const {
storedTheme,
setStoredTheme,
themeVariantLight,
setThemeVariantLight,
themeVariantDark,
setThemeVariantDark
} = useTheme();
const lightThemes = getLightThemes();
const darkThemes = getDarkThemes();
const handleModeChange = (mode) => {
setStoredTheme(mode);
};
const renderThemeVariants = (themes, selectedVariant, onSelect, label) => (
<div className="theme-variant-section">
<div className="theme-variant-label">{label}</div>
<div className="theme-variants">
{themes.map((theme) => (
<ThemeVariantCard
key={theme.id}
theme={theme}
isSelected={selectedVariant === theme.id}
onClick={() => onSelect(theme.id)}
/>
))}
</div>
</div>
);
return (
<StyledWrapper>
<div className="flex flex-col gap-4 w-full">
<div>
<div className="section-header">Appearance</div>
<div className="theme-mode-selector">
<label className="theme-mode-option">
<input
type="radio"
name="theme-mode"
value="light"
checked={storedTheme === 'light'}
onChange={() => handleModeChange('light')}
/>
<span>Light</span>
</label>
<label className="theme-mode-option">
<input
type="radio"
name="theme-mode"
value="dark"
checked={storedTheme === 'dark'}
onChange={() => handleModeChange('dark')}
/>
<span>Dark</span>
</label>
<label className="theme-mode-option">
<input
type="radio"
name="theme-mode"
value="system"
checked={storedTheme === 'system'}
onChange={() => handleModeChange('system')}
/>
<span>System</span>
</label>
</div>
</div>
{storedTheme === 'light' && (
<>
{renderThemeVariants(lightThemes, themeVariantLight, setThemeVariantLight, 'Light Theme')}
</>
)}
{storedTheme === 'dark' && (
<>
{renderThemeVariants(darkThemes, themeVariantDark, setThemeVariantDark, 'Dark Theme')}
</>
)}
{storedTheme === 'system' && (
<>
{renderThemeVariants(lightThemes, themeVariantLight, setThemeVariantLight, 'Light Theme')}
<div className="section-divider" />
{renderThemeVariants(darkThemes, themeVariantDark, setThemeVariantDark, 'Dark Theme')}
</>
)}
</div>
</StyledWrapper>
);
};
export default Themes;

View File

@@ -1,9 +1,11 @@
import Modal from 'components/Modal/index';
import classnames from 'classnames';
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import Support from './Support';
import General from './General';
import Themes from './Themes';
import Proxy from './ProxySettings';
import Display from './Display';
import Keybindings from './Keybindings';
@@ -12,7 +14,8 @@ import Beta from './Beta';
import StyledWrapper from './StyledWrapper';
const Preferences = ({ onClose }) => {
const [tab, setTab] = useState('general');
const preferencesTab = useSelector((state) => state.app.preferencesTab);
const [tab, setTab] = useState(preferencesTab || 'general');
const getTabClassname = (tabName) => {
return classnames(`tab select-none ${tabName}`, {
@@ -26,6 +29,10 @@ const Preferences = ({ onClose }) => {
return <General close={onClose} />;
}
case 'themes': {
return <Themes close={onClose} />;
}
case 'proxy': {
return <Proxy close={onClose} />;
}
@@ -56,6 +63,9 @@ const Preferences = ({ onClose }) => {
<div className={getTabClassname('general')} role="tab" onClick={() => setTab('general')}>
General
</div>
<div className={getTabClassname('themes')} role="tab" onClick={() => setTab('themes')}>
Themes
</div>
<div className={getTabClassname('display')} role="tab" onClick={() => setTab('display')}>
Display
</div>

View File

@@ -4,55 +4,124 @@ const Wrapper = styled.div`
display: flex;
flex-direction: column;
width: 100%;
flex: 1;
/* height: 100%; */
height: 100%;
position: relative;
.grpc-message-header {
.font-medium {
color: ${(props) => props.theme.text};
.messages-container {
flex: 1;
display: flex;
flex-direction: column;
&.single {
height: 100%;
}
button {
&.multi {
overflow-y: auto;
padding-bottom: 48px;
}
}
.message-container {
display: flex;
flex-direction: column;
&.single {
height: 100%;
.editor-container {
height: calc(100% - 32px);
}
}
&:not(.single) {
min-height: 240px;
margin-bottom: 8px;
&.last {
margin-bottom: 0;
}
}
}
.message-toolbar {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 4px;
padding: 4px 0px;
height: 32px;
flex-shrink: 0;
.message-label {
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext1};
margin-right: auto;
}
.toolbar-actions {
display: flex;
align-items: center;
gap: 2px;
}
.toolbar-btn {
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
width: 28px;
height: 28px;
border-radius: 4px;
color: ${(props) => props.theme.colors.text.muted};
transition: all 0.15s ease;
&:hover {
transform: scale(1.1);
background-color: ${(props) => props.theme.dropdown.hoverBg};
color: ${(props) => props.theme.text};
}
&:active {
transform: scale(0.95);
&.disabled {
opacity: 0.4;
cursor: not-allowed;
&:hover {
background-color: transparent;
color: ${(props) => props.theme.colors.text.muted};
}
}
&.delete:hover {
color: ${(props) => props.theme.colors.text.danger};
}
}
}
#grpc-messages-container {
/* height: 100%; */
position: relative;
.editor-container {
flex: 1;
min-height: 0;
}
.add-message-btn-container {
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
gap: 12px;
p {
color: ${(props) => props.theme.colors.text.muted};
font-size: 13px;
}
}
.add-message-footer {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding-top: 8px;
background: ${(props) => props.theme.bg || '#fff'};
z-index: 15;
border-top: 1px solid ${(props) => props.theme.border || 'rgba(0, 0, 0, 0.1)'};
.add-message-btn {
width: 100%;
}
}
.CodeMirror {
border-top: 0;
border-top-left-radius: 0;
border-top-right-radius: 0;
padding: 8px;
background: ${(props) => props.theme.bg};
}
`;

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from 'react';
import React, { useEffect, useRef } from 'react';
import { get } from 'lodash';
import { useDispatch, useSelector } from 'react-redux';
import { useTheme } from 'providers/Theme';
@@ -8,37 +8,84 @@ import { sendGrpcMessage, generateGrpcSampleMessage } from 'utils/network/index'
import useLocalStorage from 'hooks/useLocalStorage';
import CodeEditor from 'components/CodeEditor/index';
import Button from 'ui/Button';
import StyledWrapper from './StyledWrapper';
import { IconSend, IconRefresh, IconWand, IconPlus, IconTrash, IconChevronDown, IconChevronUp } from '@tabler/icons';
import { IconSend, IconRefresh, IconWand, IconPlus, IconTrash } from '@tabler/icons';
import ToolHint from 'components/ToolHint/index';
import { toastError } from 'utils/common/error';
import toast from 'react-hot-toast';
import { getAbsoluteFilePath } from 'utils/common/path';
import { prettifyJsonString } from 'utils/common/index';
const SingleGrpcMessage = ({ message, item, collection, index, methodType, isCollapsed, onToggleCollapse, handleRun, canClientSendMultipleMessages }) => {
const MessageToolbar = ({
index,
canClientStream,
isConnectionActive,
onSend,
onRegenerateMessage,
onPrettify,
onDeleteMessage,
showDelete
}) => {
return (
<div className="message-toolbar">
<span className="message-label">Message {index + 1}</span>
<div className="toolbar-actions">
<ToolHint text="Format JSON" toolhintId={`prettify-msg-${index}`}>
<button onClick={onPrettify} className="toolbar-btn">
<IconWand size={16} strokeWidth={1.5} />
</button>
</ToolHint>
<ToolHint text="Generate sample" toolhintId={`regenerate-msg-${index}`}>
<button onClick={onRegenerateMessage} className="toolbar-btn">
<IconRefresh size={16} strokeWidth={1.5} />
</button>
</ToolHint>
{canClientStream && (
<ToolHint text={isConnectionActive ? 'Send message' : 'Connection not active'} toolhintId={`send-msg-${index}`}>
<button
onClick={onSend}
disabled={!isConnectionActive}
className={`toolbar-btn ${!isConnectionActive ? 'disabled' : ''}`}
>
<IconSend size={16} strokeWidth={1.5} />
</button>
</ToolHint>
)}
{showDelete && (
<ToolHint text="Delete message" toolhintId={`delete-msg-${index}`}>
<button onClick={onDeleteMessage} className="toolbar-btn delete">
<IconTrash size={16} strokeWidth={1.5} />
</button>
</ToolHint>
)}
</div>
</div>
);
};
const SingleGrpcMessage = ({ message, item, collection, index, methodType, handleRun, canClientSendMultipleMessages, isLast }) => {
const dispatch = useDispatch();
const { displayedTheme, theme } = useTheme();
const { displayedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const body = item.draft ? get(item, 'draft.request.body') : get(item, 'request.body');
const isConnectionActive = useSelector((state) => state.collections.activeConnections.includes(item.uid));
// Access gRPC method metadata from local storage
const [reflectionCache] = useLocalStorage('bruno.grpc.reflectionCache', {});
const [protofileCache] = useLocalStorage('bruno.grpc.protofileCache', {});
const canClientStream = methodType === 'client-streaming' || methodType === 'bidi-streaming';
const { name, content } = message;
const onEdit = (value) => {
const currentMessages = [...(body.grpc || [])];
currentMessages[index] = {
name: name ? name : `message ${index + 1}`,
content: value
};
dispatch(updateRequestBody({
content: currentMessages,
itemUid: item.uid,
@@ -53,60 +100,51 @@ const SingleGrpcMessage = ({ message, item, collection, index, methodType, isCol
console.error('Error sending message:', error);
}
};
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
const onRegenerateMessage = async () => {
try {
const methodPath = item.draft?.request?.method || item.request?.method;
if (!methodPath) {
toastError(new Error('Method path not found in request'));
return;
}
// Get the URL and protoPath to determine which cache to use
const url = item.draft?.request?.url || item.request?.url;
const protoPath = item.draft?.request?.protoPath || item.request?.protoPath;
// Find the method metadata from the appropriate cache
let methodMetadata = null;
if (protoPath) {
// Use protofile cache if protoPath is available
const absolutePath = getAbsoluteFilePath(collection.pathname, protoPath);
const cachedMethods = protofileCache[absolutePath];
if (cachedMethods) {
methodMetadata = cachedMethods.find((method) => method.path === methodPath);
}
} else if (url) {
// Use reflection cache if no protoPath (reflection mode)
const cachedMethods = reflectionCache[url];
if (cachedMethods) {
methodMetadata = cachedMethods.find((method) => method.path === methodPath);
}
}
const result = await generateGrpcSampleMessage(methodPath,
content,
{
arraySize: 2,
methodMetadata // Pass the method metadata to the function
});
const result = await generateGrpcSampleMessage(methodPath, content, {
arraySize: 2,
methodMetadata
});
if (result.success) {
const currentMessages = [...(body.grpc || [])];
currentMessages[index] = {
name: name ? name : `message ${index + 1}`,
content: result.message
};
dispatch(updateRequestBody({
content: currentMessages,
itemUid: item.uid,
collectionUid: collection.uid
}));
toast.success('Sample message generated successfully!');
toast.success('Sample message generated');
} else {
toastError(new Error(result.error || 'Failed to generate sample message'));
}
@@ -118,9 +156,7 @@ const SingleGrpcMessage = ({ message, item, collection, index, methodType, isCol
const onDeleteMessage = () => {
const currentMessages = [...(body.grpc || [])];
currentMessages.splice(index, 1);
dispatch(updateRequestBody({
content: currentMessages,
itemUid: item.uid,
@@ -131,7 +167,6 @@ const SingleGrpcMessage = ({ message, item, collection, index, methodType, isCol
const onPrettify = () => {
try {
const prettyBodyJson = prettifyJsonString(content);
const currentMessages = [...(body.grpc || [])];
currentMessages[index] = {
name: name ? name : `message ${index + 1}`,
@@ -147,101 +182,45 @@ const SingleGrpcMessage = ({ message, item, collection, index, methodType, isCol
}
};
const getContainerHeight = (canClientSendMultipleMessages && body.grpc.length > 1) ? `${isCollapsed ? '' : 'h-80'}` : 'h-full';
const isSingleMessage = !canClientSendMultipleMessages || body.grpc.length === 1;
return (
<div className={`flex flex-col mb-3 border border-neutral-200 dark:border-neutral-800 rounded-md overflow-hidden ${getContainerHeight} relative`}>
<div
className="grpc-message-header flex items-center justify-between px-3 py-2 bg-neutral-100 dark:bg-neutral-700 cursor-pointer"
onClick={onToggleCollapse}
>
<div className="flex items-center gap-2">
{isCollapsed
? <IconChevronDown size={16} strokeWidth={1.5} className="text-zinc-700 dark:text-zinc-300" />
: <IconChevronUp size={16} strokeWidth={1.5} className="text-zinc-700 dark:text-zinc-300" />}
<span className="font-medium">{`Message ${canClientStream ? index + 1 : ''}`}</span>
</div>
<div className="flex items-center gap-2" onClick={(e) => e.stopPropagation()}>
<ToolHint text="Format JSON with proper indentation and spacing" toolhintId={`prettify-msg-${index}`}>
<button
onClick={onPrettify}
className="p-1 rounded hover:bg-zinc-200 dark:hover:bg-zinc-600 transition-colors"
>
<IconWand size={16} strokeWidth={1.5} className="text-zinc-700 dark:text-zinc-300" />
</button>
</ToolHint>
<ToolHint text="Generate a new sample message based on schema" toolhintId={`regenerate-msg-${index}`}>
<button
onClick={onRegenerateMessage}
className="p-1 rounded hover:bg-zinc-200 dark:hover:bg-zinc-600 transition-colors"
>
<IconRefresh size={16} strokeWidth={1.5} className="text-zinc-700 dark:text-zinc-300" />
</button>
</ToolHint>
{canClientStream && (
<ToolHint text={isConnectionActive ? 'Send gRPC message' : 'Connection not active'} toolhintId={`send-msg-${index}`}>
<button
onClick={onSend}
disabled={!isConnectionActive}
className={`p-1 rounded ${isConnectionActive ? 'hover:bg-zinc-200 dark:hover:bg-zinc-600' : 'opacity-50 cursor-not-allowed'} transition-colors`}
data-testid={`grpc-send-message-${index}`}
>
<IconSend
size={16}
strokeWidth={1.5}
className={`${isConnectionActive ? 'text-zinc-700 dark:text-zinc-300' : 'text-zinc-400 dark:text-zinc-500'}`}
/>
</button>
</ToolHint>
)}
{index > 0 && (
<ToolHint text="Delete this message" toolhintId={`delete-msg-${index}`}>
<button
onClick={onDeleteMessage}
className="p-1 rounded hover:bg-zinc-200 dark:hover:bg-zinc-600 transition-colors"
>
<IconTrash size={16} strokeWidth={1.5} className="text-zinc-700 dark:text-zinc-300" />
</button>
</ToolHint>
)}
</div>
<div className={`message-container ${isSingleMessage ? 'single' : ''} ${isLast ? 'last' : ''}`}>
<MessageToolbar
index={index}
canClientStream={canClientStream}
isConnectionActive={isConnectionActive}
onSend={onSend}
onRegenerateMessage={onRegenerateMessage}
onPrettify={onPrettify}
onDeleteMessage={onDeleteMessage}
showDelete={index > 0}
/>
<div className="editor-container">
<CodeEditor
collection={collection}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
value={content}
onEdit={onEdit}
onRun={handleRun}
onSave={onSave}
mode="application/ld+json"
enableVariableHighlighting={true}
/>
</div>
{!isCollapsed && (
<div className={`flex ${body.grpc.length === 1 || !canClientSendMultipleMessages ? 'h-full' : 'h-80'} relative`}>
<CodeEditor
collection={collection}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
value={content}
onEdit={onEdit}
onRun={handleRun}
onSave={onSave}
mode="application/ld+json"
enableVariableHighlighting={true}
/>
</div>
)}
</div>
);
};
const GrpcBody = ({ item, collection, handleRun }) => {
const preferences = useSelector((state) => state.app.preferences);
const isVerticalLayout = preferences?.layout?.responsePaneOrientation === 'vertical';
const dispatch = useDispatch();
const [collapsedMessages, setCollapsedMessages] = useState([]);
const messagesContainerRef = useRef(null);
const body = item.draft ? get(item, 'draft.request.body') : get(item, 'request.body');
const methodType = item.draft ? get(item, 'draft.request.methodType') : get(item, 'request.methodType');
const canClientSendMultipleMessages = methodType === 'client-streaming' || methodType === 'bidi-streaming';
// Auto-scroll to the latest message when messages are added
useEffect(() => {
if (messagesContainerRef.current && body?.grpc?.length > 0) {
const container = messagesContainerRef.current;
@@ -249,26 +228,12 @@ const GrpcBody = ({ item, collection, handleRun }) => {
}
}, [body?.grpc?.length]);
const toggleMessageCollapse = (index) => {
setCollapsedMessages((prev) => {
if (prev.includes(index)) {
return prev.filter((i) => i !== index);
} else {
return [...prev, index];
}
});
};
const addNewMessage = () => {
const currentMessages = Array.isArray(body.grpc)
? [...body.grpc]
: [];
const currentMessages = Array.isArray(body.grpc) ? [...body.grpc] : [];
currentMessages.push({
name: `message ${currentMessages.length + 1}`,
content: '{}'
});
dispatch(updateRequestBody({
content: currentMessages,
itemUid: item.uid,
@@ -278,61 +243,58 @@ const GrpcBody = ({ item, collection, handleRun }) => {
if (!body?.grpc || !Array.isArray(body.grpc)) {
return (
<StyledWrapper isVerticalLayout={isVerticalLayout}>
<div className="flex flex-col items-center justify-center py-8">
<p className="text-zinc-500 dark:text-zinc-400 mb-4">No gRPC messages available</p>
<ToolHint text="Add the first message to your gRPC request" toolhintId="add-first-msg">
<button
onClick={addNewMessage}
className="flex items-center justify-center gap-2 py-2 px-4 rounded-md border border-neutral-200 dark:border-neutral-800 bg-neutral-100 dark:bg-neutral-700 hover:bg-neutral-200 dark:hover:bg-neutral-600 transition-colors"
>
<IconPlus size={16} strokeWidth={1.5} className="text-neutral-700 dark:text-neutral-300" />
<span className="font-medium text-neutral-700 dark:text-neutral-300">Add First Message</span>
</button>
</ToolHint>
<StyledWrapper>
<div className="empty-state">
<p>No gRPC messages available</p>
<Button
onClick={addNewMessage}
variant="filled"
color="secondary"
size="sm"
icon={<IconPlus size={14} strokeWidth={1.5} />}
>
Add Message
</Button>
</div>
</StyledWrapper>
);
}
const messagesToShow = body.grpc.filter((_, index) => canClientSendMultipleMessages || index === 0);
return (
<StyledWrapper isVerticalLayout={isVerticalLayout}>
<StyledWrapper>
<div
ref={messagesContainerRef}
id="grpc-messages-container"
data-testid="grpc-messages-container"
className={`flex-1 ${body.grpc.length === 1 || !canClientSendMultipleMessages ? 'h-full' : 'overflow-y-auto'} ${canClientSendMultipleMessages && 'pb-16'}`}
className={`messages-container ${canClientSendMultipleMessages && messagesToShow.length > 1 ? 'multi' : 'single'}`}
>
{body.grpc
.filter((_, index) => canClientSendMultipleMessages || index === 0)
.map((message, index) => (
<SingleGrpcMessage
key={index}
message={message}
item={item}
collection={collection}
index={index}
methodType={methodType}
isCollapsed={collapsedMessages.includes(index)}
onToggleCollapse={() => toggleMessageCollapse(index)}
handleRun={handleRun}
canClientSendMultipleMessages={canClientSendMultipleMessages}
/>
))}
{messagesToShow.map((message, index) => (
<SingleGrpcMessage
key={index}
message={message}
item={item}
collection={collection}
index={index}
methodType={methodType}
handleRun={handleRun}
canClientSendMultipleMessages={canClientSendMultipleMessages}
isLast={index === messagesToShow.length - 1}
/>
))}
</div>
{canClientSendMultipleMessages && (
<div className="add-message-btn-container">
<ToolHint text="Add a new gRPC message to the request" toolhintId="add-msg-fixed">
<button
onClick={addNewMessage}
className="add-message-btn flex items-center justify-center gap-2 py-2 px-4 rounded-md border border-neutral-200 dark:border-neutral-800 bg-neutral-100 dark:bg-neutral-700 hover:bg-neutral-200 dark:hover:bg-neutral-600 transition-colors shadow-md"
data-testid="grpc-add-message-button"
>
<IconPlus size={16} strokeWidth={1.5} className="text-neutral-700 dark:text-neutral-300" />
<span className="font-medium text-neutral-700 dark:text-neutral-300">Add Message</span>
</button>
</ToolHint>
<div className="add-message-footer">
<Button
onClick={addNewMessage}
variant="filled"
color="secondary"
size="sm"
fullWidth
icon={<IconPlus size={14} strokeWidth={1.5} />}
>
Add Message
</Button>
</div>
)}
</StyledWrapper>

View File

@@ -49,7 +49,7 @@ const Wrapper = styled.div`
.infotip-text {
visibility: hidden;
width: auto;
background-color: ${(props) => props.theme.requestTabs.active.bg};
background-color: ${(props) => props.theme.background.surface2};
color: ${(props) => props.theme.text};
text-align: center;
border-radius: 4px;
@@ -72,7 +72,7 @@ const Wrapper = styled.div`
margin-left: -4px;
border-width: 4px;
border-style: solid;
border-color: ${(props) => props.theme.requestTabs.active.bg} transparent transparent transparent;
border-color: ${(props) => props.theme.background.surface2} transparent transparent transparent;
}
.shortcut {

View File

@@ -348,7 +348,7 @@ const GrpcQueryUrl = ({ item, collection, handleRun }) => {
<IconRefresh
color={theme.requestTabs.icon.color}
strokeWidth={1.5}
size={22}
size={20}
className={`${(isReflectionMode ? reflectionManagement.isLoadingMethods : protoFileManagement.isLoadingMethods) ? 'animate-spin' : 'cursor-pointer'}`}
data-testid="refresh-methods-icon"
/>
@@ -367,7 +367,7 @@ const GrpcQueryUrl = ({ item, collection, handleRun }) => {
<IconCode
color={theme.requestTabs.icon.color}
strokeWidth={1.5}
size={22}
size={20}
/>
<span className="infotip-text text-xs">Generate grpcurl command</span>
</div>
@@ -383,7 +383,7 @@ const GrpcQueryUrl = ({ item, collection, handleRun }) => {
<IconDeviceFloppy
color={item.draft ? theme.colors.text.yellow : theme.requestTabs.icon.color}
strokeWidth={1.5}
size={22}
size={20}
className={`${item.draft ? 'cursor-pointer' : 'cursor-default'}`}
/>
<span className="infotip-text text-xs">
@@ -394,7 +394,7 @@ const GrpcQueryUrl = ({ item, collection, handleRun }) => {
{isConnectionActive && isStreamingMethod && (
<div className="connection-controls relative flex items-center h-full gap-3">
<div className="infotip" onClick={handleCancelConnection} data-testid="grpc-cancel-connection-button">
<IconX color={theme.requestTabs.icon.color} strokeWidth={1.5} size={22} className="cursor-pointer" />
<IconX color={theme.requestTabs.icon.color} strokeWidth={1.5} size={20} className="cursor-pointer" />
<span className="infotip-text text-xs">Cancel</span>
</div>
@@ -403,7 +403,7 @@ const GrpcQueryUrl = ({ item, collection, handleRun }) => {
<IconCheck
color={theme.colors.text.green}
strokeWidth={2}
size={22}
size={20}
className="cursor-pointer"
/>
</div>
@@ -420,7 +420,7 @@ const GrpcQueryUrl = ({ item, collection, handleRun }) => {
handleRun(e);
}}
>
<IconArrowRight color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={22} />
<IconArrowRight color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={20} />
</div>
)}
</div>

View File

@@ -7,7 +7,7 @@ const StyledWrapper = styled.div`
border: none;
border-bottom: solid 2px transparent;
margin-right: ${(props) => props.theme.tabs.marginRight};
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
cursor: pointer;
&:focus,

View File

@@ -2,7 +2,7 @@ import styled from 'styled-components';
const Wrapper = styled.div`
div.title {
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
}
table {
width: 100%;

View File

@@ -49,7 +49,7 @@ const Wrapper = styled.div`
.infotiptext {
visibility: hidden;
width: auto;
background-color: ${(props) => props.theme.requestTabs.active.bg};
background-color: ${(props) => props.theme.background.surface2};
color: ${(props) => props.theme.text};
text-align: center;
border-radius: 4px;
@@ -72,7 +72,7 @@ const Wrapper = styled.div`
margin-left: -4px;
border-width: 4px;
border-style: solid;
border-color: ${(props) => props.theme.requestTabs.active.bg} transparent transparent transparent;
border-color: ${(props) => props.theme.background.surface2} transparent transparent transparent;
}
.shortcut {

View File

@@ -9,7 +9,7 @@ const Wrapper = styled.div`
border-radius: 3px;
.selected-body-mode {
color: ${(props) => props.theme.colors.text.yellow};
color: ${(props) => props.theme.brand};
}
}

View File

@@ -6,7 +6,7 @@ const StyledWrapper = styled.div`
}
div.title {
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
}
`;

View File

@@ -2,7 +2,7 @@ import styled from 'styled-components';
const StyledWrapper = styled.div`
div.title {
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
}
`;

View File

@@ -7,7 +7,7 @@ const StyledWrapper = styled.div`
border: none;
border-bottom: solid 2px transparent;
margin-right: ${(props) => props.theme.tabs.marginRight};
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
cursor: pointer;
&:focus,

View File

@@ -17,13 +17,13 @@ const Wrapper = styled.div`
}
.selected-body-mode {
color: ${(props) => props.theme.colors.text.yellow};
color: ${(props) => props.theme.brand};
}
}
.caret {
color: rgb(140, 140, 140);
fill: rgb(140 140 140);
color: ${(props) => props.theme.colors.text.muted};
fill: ${(props) => props.theme.colors.text.muted};
}
`;

View File

@@ -0,0 +1,72 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
display: flex;
flex-direction: column;
&.single {
height: 100%;
.editor-container {
height: calc(100% - 32px);
}
}
&:not(.single) {
min-height: 240px;
margin-bottom: 8px;
&.last {
margin-bottom: 0;
}
}
.message-toolbar {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 4px;
padding: 4px 0px;
height: 32px;
flex-shrink: 0;
.message-label {
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext1};
margin-right: auto;
}
.toolbar-actions {
display: flex;
align-items: center;
gap: 2px;
}
.toolbar-btn {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 4px;
color: ${(props) => props.theme.colors.text.muted};
transition: all 0.15s ease;
&:hover {
background-color: ${(props) => props.theme.dropdown.hoverBg};
color: ${(props) => props.theme.text};
}
&.delete:hover {
color: ${(props) => props.theme.colors.text.danger};
}
}
}
.editor-container {
flex: 1;
min-height: 0;
}
`;
export default StyledWrapper;

View File

@@ -1,4 +1,4 @@
import { IconChevronDown, IconChevronUp, IconTrash, IconWand } from '@tabler/icons';
import { IconTrash, IconWand } from '@tabler/icons';
import CodeEditor from 'components/CodeEditor/index';
import ToolHint from 'components/ToolHint/index';
import { get } from 'lodash';
@@ -13,6 +13,7 @@ import { toastError } from 'utils/common/error';
import { prettifyJsonString } from 'utils/common/index';
import xmlFormat from 'xml-formatter';
import WSRequestBodyMode from '../BodyMode/index';
import StyledWrapper from './StyledWrapper';
export const TYPE_BY_DECODER = {
base64: 'binary',
@@ -28,10 +29,9 @@ export const SingleWSMessage = ({
collection,
index,
methodType,
isCollapsed,
onToggleCollapse,
handleRun,
canClientSendMultipleMessages
canClientSendMultipleMessages,
isLast
}) => {
const dispatch = useDispatch();
const { displayedTheme } = useTheme();
@@ -88,9 +88,6 @@ export const SingleWSMessage = ({
}));
};
const getContainerHeight
= canClientSendMultipleMessages && body.ws.length > 1 ? `${isCollapsed ? '' : 'h-80'}` : 'h-full';
let codeType = messageFormat;
if (TYPE_BY_DECODER[type]) {
codeType = TYPE_BY_DECODER[type];
@@ -144,60 +141,44 @@ export const SingleWSMessage = ({
}
};
const isSingleMessage = !canClientSendMultipleMessages || body.ws.length === 1;
return (
<div
className={`flex flex-col mb-3 border border-neutral-200 dark:border-neutral-800 rounded-md overflow-hidden ${getContainerHeight} relative`}
>
<div
className="ws-message-header flex items-center justify-between px-3 py-2 bg-neutral-100 dark:bg-neutral-700 cursor-pointer"
onClick={onToggleCollapse}
>
<div className="flex items-center gap-2">
{isCollapsed ? (
<IconChevronDown size={16} strokeWidth={1.5} className="text-zinc-700 dark:text-zinc-300" />
) : (
<IconChevronUp size={16} strokeWidth={1.5} className="text-zinc-700 dark:text-zinc-300" />
)}
</div>
<div className="flex items-center gap-2" onClick={(e) => e.stopPropagation()}>
<StyledWrapper className={`message-container ${isSingleMessage ? 'single' : ''} ${isLast ? 'last' : ''}`}>
<div className="message-toolbar">
<span className="message-label">Message {index + 1}</span>
<div className="toolbar-actions">
<WSRequestBodyMode mode={messageFormat} onModeChange={onUpdateMessageType} />
<ToolHint text="Prettify" toolhintId={`prettify-msg-${index}`}>
<button
onClick={onPrettify}
className="p-1 rounded hover:bg-zinc-200 dark:hover:bg-zinc-600 transition-colors"
>
<IconWand size={16} strokeWidth={1.5} className="text-zinc-700 dark:text-zinc-300" />
<ToolHint text="Format" toolhintId={`prettify-msg-${index}`}>
<button onClick={onPrettify} className="toolbar-btn">
<IconWand size={16} strokeWidth={1.5} />
</button>
</ToolHint>
{index > 0 && (
<ToolHint text="Delete this message" toolhintId={`delete-msg-${index}`}>
<button
onClick={onDeleteMessage}
className="p-1 rounded hover:bg-zinc-200 dark:hover:bg-zinc-600 transition-colors"
>
<IconTrash size={16} strokeWidth={1.5} className="text-zinc-700 dark:text-zinc-300" />
<ToolHint text="Delete message" toolhintId={`delete-msg-${index}`}>
<button onClick={onDeleteMessage} className="toolbar-btn delete">
<IconTrash size={16} strokeWidth={1.5} />
</button>
</ToolHint>
)}
</div>
</div>
{!isCollapsed && (
<div className={`flex ${body.ws.length === 1 || !canClientSendMultipleMessages ? 'h-full' : 'h-80'} relative`}>
<CodeEditor
collection={collection}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
value={content}
onEdit={onEdit}
onRun={handleRun}
onSave={onSave}
mode={codemirrorMode[codeType] ?? 'text/plain'}
enableVariableHighlighting={true}
/>
</div>
)}
</div>
<div className="editor-container">
<CodeEditor
collection={collection}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
value={content}
onEdit={onEdit}
onRun={handleRun}
onSave={onSave}
mode={codemirrorMode[codeType] ?? 'text/plain'}
enableVariableHighlighting={true}
/>
</div>
</StyledWrapper>
);
};

View File

@@ -4,49 +4,45 @@ const Wrapper = styled.div`
display: flex;
flex-direction: column;
width: 100%;
flex: 1;
height: 100%;
position: relative;
.ws-message-header {
.font-medium {
color: ${(props) => props.theme.text};
.messages-container {
flex: 1;
display: flex;
flex-direction: column;
&.single {
height: 100%;
}
button {
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
&:hover {
transform: scale(1.1);
}
&:active {
transform: scale(0.95);
}
&.multi {
overflow-y: auto;
padding-bottom: 48px;
}
}
.add-message-btn-container {
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
gap: 12px;
p {
color: ${(props) => props.theme.colors.text.muted};
font-size: 13px;
}
}
.add-message-footer {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding-top: 8px;
background: ${(props) => props.theme.bg || '#fff'};
z-index: 15;
border-top: 1px solid ${(props) => props.theme.border || 'rgba(0, 0, 0, 0.1)'};
.add-message-btn {
width: 100%;
}
}
.CodeMirror {
border-top: 0;
border-top-left-radius: 0;
border-top-right-radius: 0;
padding: 8px;
background: ${(props) => props.theme.bg};
}
`;

View File

@@ -1,17 +1,14 @@
import { get } from 'lodash';
import { updateRequestBody } from 'providers/ReduxStore/slices/collections';
import { IconPlus } from '@tabler/icons';
import React, { useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import ToolHint from 'components/ToolHint/index';
import React, { useEffect, useRef } from 'react';
import { useDispatch } from 'react-redux';
import Button from 'ui/Button';
import StyledWrapper from './StyledWrapper';
import { SingleWSMessage } from './SingleWSMessage/index';
const WSBody = ({ item, collection, handleRun }) => {
const preferences = useSelector((state) => state.app.preferences);
const isVerticalLayout = preferences?.layout?.responsePaneOrientation === 'vertical';
const dispatch = useDispatch();
const [collapsedMessages, setCollapsedMessages] = useState([]);
const messagesContainerRef = useRef(null);
const body = item.draft ? get(item, 'draft.request.body') : get(item, 'request.body');
@@ -26,16 +23,6 @@ const WSBody = ({ item, collection, handleRun }) => {
}
}, [body?.ws?.length]);
const toggleMessageCollapse = (index) => {
setCollapsedMessages((prev) => {
if (prev.includes(index)) {
return prev.filter((i) => i !== index);
} else {
return [...prev, index];
}
});
};
const addNewMessage = () => {
const currentMessages = Array.isArray(body.ws) ? [...body.ws] : [];
@@ -53,60 +40,58 @@ const WSBody = ({ item, collection, handleRun }) => {
if (!body?.ws || !Array.isArray(body.ws)) {
return (
<StyledWrapper isVerticalLayout={isVerticalLayout}>
<div className="flex flex-col items-center justify-center py-8">
<p className="text-zinc-500 dark:text-zinc-400 mb-4">No WebSocket messages available</p>
<ToolHint text="Add the first message to your WebSocket request" toolhintId="add-first-msg">
<button
onClick={addNewMessage}
className="flex items-center justify-center gap-2 py-2 px-4 rounded-md border border-neutral-200 dark:border-neutral-800 bg-neutral-100 dark:bg-neutral-700 hover:bg-neutral-200 dark:hover:bg-neutral-600 transition-colors"
>
<IconPlus size={16} strokeWidth={1.5} className="text-neutral-700 dark:text-neutral-300" />
<span className="font-medium text-neutral-700 dark:text-neutral-300">Add First Message</span>
</button>
</ToolHint>
<StyledWrapper>
<div className="empty-state">
<p>No WebSocket messages available</p>
<Button
onClick={addNewMessage}
variant="filled"
color="secondary"
size="sm"
icon={<IconPlus size={14} strokeWidth={1.5} />}
>
Add Message
</Button>
</div>
</StyledWrapper>
);
}
const messagesToShow = body.ws.filter((_, index) => canClientSendMultipleMessages || index === 0);
return (
<StyledWrapper isVerticalLayout={isVerticalLayout}>
<StyledWrapper>
<div
ref={messagesContainerRef}
id="ws-messages-container"
className={`flex-1 ${body.ws.length === 1 || !canClientSendMultipleMessages ? 'h-full' : 'overflow-y-auto'} ${canClientSendMultipleMessages && 'pb-16'
}`}
className={`messages-container ${canClientSendMultipleMessages && messagesToShow.length > 1 ? 'multi' : 'single'}`}
>
{body.ws
.filter((_, index) => canClientSendMultipleMessages || index === 0)
.map((message, index) => (
<SingleWSMessage
key={index}
message={message}
item={item}
collection={collection}
index={index}
methodType={methodType}
isCollapsed={collapsedMessages.includes(index)}
onToggleCollapse={() => toggleMessageCollapse(index)}
handleRun={handleRun}
canClientSendMultipleMessages={canClientSendMultipleMessages}
/>
))}
{messagesToShow.map((message, index) => (
<SingleWSMessage
key={index}
message={message}
item={item}
collection={collection}
index={index}
methodType={methodType}
handleRun={handleRun}
canClientSendMultipleMessages={canClientSendMultipleMessages}
isLast={index === messagesToShow.length - 1}
/>
))}
</div>
{canClientSendMultipleMessages && (
<div className="add-message-btn-container">
<ToolHint text="Add a new WebSocket message to the request" toolhintId="add-msg-fixed">
<button
onClick={addNewMessage}
className="add-message-btn flex items-center justify-center gap-2 py-2 px-4 rounded-md border border-neutral-200 dark:border-neutral-800 bg-neutral-100 dark:bg-neutral-700 hover:bg-neutral-200 dark:hover:bg-neutral-600 transition-colors shadow-md"
>
<IconPlus size={16} strokeWidth={1.5} className="text-neutral-700 dark:text-neutral-300" />
<span className="font-medium text-neutral-700 dark:text-neutral-300">Add Message</span>
</button>
</ToolHint>
<div className="add-message-footer">
<Button
onClick={addNewMessage}
variant="filled"
color="secondary"
size="sm"
fullWidth
icon={<IconPlus size={14} strokeWidth={1.5} />}
>
Add Message
</Button>
</div>
)}
</StyledWrapper>

View File

@@ -62,7 +62,7 @@ const StyledWrapper = styled.div`
.infotip-text {
visibility: hidden;
width: auto;
background-color: ${(props) => props.theme.requestTabs.active.bg};
background-color: ${(props) => props.theme.background.surface2};
color: ${(props) => props.theme.text};
text-align: center;
border-radius: 4px;
@@ -85,7 +85,7 @@ const StyledWrapper = styled.div`
margin-left: -4px;
border-width: 4px;
border-style: solid;
border-color: ${(props) => props.theme.requestTabs.active.bg} transparent transparent transparent;
border-color: ${(props) => props.theme.background.surface2} transparent transparent transparent;
}
.shortcut {
@@ -95,7 +95,7 @@ const StyledWrapper = styled.div`
.connection-controls {
.infotip {
&:hover {
background-color: ${(props) => props.theme.requestTabPanel.url.errorHoverBg};
background-color: color-mix(in srgb, ${(props) => props.theme.colors.text.danger} 6%, transparent);
}
}
}

View File

@@ -150,7 +150,7 @@ const WsQueryUrl = ({ item, collection, handleRun }) => {
<IconDeviceFloppy
color={hasChanges ? theme.colors.text.yellow : theme.requestTabs.icon.color}
strokeWidth={1.5}
size={22}
size={20}
className={`${hasChanges ? 'cursor-pointer' : 'cursor-default'}`}
/>
<span className="infotip-text text-xs">
@@ -164,7 +164,7 @@ const WsQueryUrl = ({ item, collection, handleRun }) => {
<IconPlugConnectedX
color={theme.colors.text.danger}
strokeWidth={1.5}
size={22}
size={20}
className="cursor-pointer"
/>
<span className="infotip-text text-xs">Close Connection</span>
@@ -181,7 +181,7 @@ const WsQueryUrl = ({ item, collection, handleRun }) => {
})}
color={theme.colors.text.green}
strokeWidth={1.5}
size={22}
size={20}
/>
<span className="infotip-text text-xs">Connect</span>
</div>
@@ -189,7 +189,7 @@ const WsQueryUrl = ({ item, collection, handleRun }) => {
)}
<div data-testid="run-button" className="cursor-pointer" onClick={handleRunClick}>
<IconArrowRight color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={22} />
<IconArrowRight color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={20} />
</div>
</div>
</div>

View File

@@ -7,8 +7,6 @@ import { clearGlobalEnvironmentDraft } from 'providers/ReduxStore/slices/global-
import { saveGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
import { useTheme } from 'providers/Theme';
import { useDispatch, useSelector } from 'react-redux';
import darkTheme from 'themes/dark';
import lightTheme from 'themes/light';
import { findItemInCollection, hasRequestChanges } from 'utils/collections';
import ConfirmRequestClose from './ConfirmRequestClose';
import ConfirmCollectionClose from './ConfirmCollectionClose';
@@ -28,8 +26,7 @@ import toast from 'react-hot-toast';
const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUid, hasOverflow, setHasOverflow, dropdownContainerRef }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const theme = storedTheme === 'dark' ? darkTheme : lightTheme;
const { theme } = useTheme();
const tabNameRef = useRef(null);
const tabLabelRef = useRef(null);
const lastOverflowStateRef = useRef(null);

View File

@@ -26,7 +26,7 @@ const StyledWrapper = styled.div`
}
.selected-body-mode {
color: ${(props) => props.theme.colors.text.yellow};
color: ${(props) => props.theme.brand};
}
&.cursor-default {

View File

@@ -18,7 +18,7 @@ const StyledWrapper = styled.div`
}
.selected-body-mode {
color: ${(props) => props.theme.colors.text.yellow};
color: ${(props) => props.theme.brand};
}
}

View File

@@ -61,7 +61,7 @@ const StyledWrapper = styled.div`
cursor: pointer;
transition: background-color 0.15s ease;
font-size: ${(props) => props.theme.font.size.base};
color: ${(props) => props.theme.dropdown.primaryText};
color: ${(props) => props.theme.text};
width: 100%;
box-sizing: border-box;

View File

@@ -7,7 +7,7 @@ const StyledWrapper = styled.div`
border: none;
border-bottom: solid 2px transparent;
margin-right: ${(props) => props.theme.tabs.marginRight};
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
cursor: pointer;
&:focus,

View File

@@ -1,42 +1,39 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
border-left: 4px solid ${(props) => props.theme.colors.text.danger};
border-top: 1px solid transparent;
border-right: 1px solid transparent;
border-bottom: 1px solid transparent;
border-radius: 0.375rem;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
max-height: 200px;
min-height: 70px;
border-left: 3px solid ${(props) => props.theme.colors.text.danger};
border-radius: ${(props) => props.theme.border.radius.sm};
max-height: 160px;
overflow-y: auto;
background-color: ${(props) => (props.theme.bg === '#1e1e1e' ? 'rgba(40, 40, 40, 0.5)' : 'rgba(250, 250, 250, 0.9)')};
background-color: color-mix(in srgb, ${(props) => props.theme.colors.text.danger} 6%, transparent);
margin-bottom: 8px;
.close-button {
opacity: 0.7;
transition: opacity 0.2s;
opacity: 0.6;
transition: opacity 0.15s ease;
&:hover {
opacity: 1;
}
svg {
color: ${(props) => props.theme.text};
color: ${(props) => props.theme.colors.text.muted};
}
}
.error-title {
font-size: ${(props) => props.theme.font.size.sm};
font-weight: 500;
margin-bottom: 0.375rem;
margin-bottom: 4px;
color: ${(props) => props.theme.colors.text.danger};
}
.error-message {
font-family: monospace;
font-size: ${(props) => props.theme.font.size.xs};
line-height: 1.25rem;
line-height: 1.4;
white-space: pre-wrap;
word-break: break-all;
word-break: break-word;
color: ${(props) => props.theme.text};
}
`;

View File

@@ -6,7 +6,7 @@ const GrpcError = ({ error, onClose }) => {
if (!error) return null;
return (
<StyledWrapper className="mt-4 mb-2">
<StyledWrapper>
<div className="flex items-start gap-3 px-4 py-3">
<div className="flex-1 min-w-0">
<div className="error-title">gRPC Server Error</div>

View File

@@ -3,94 +3,79 @@ import styled from 'styled-components';
const StyledWrapper = styled.div`
height: 100%;
overflow: hidden;
background: ${(props) => props.theme.bg};
border-radius: 4px;
.CodeMirror {
height: 100%;
font-family: ${(props) => (props.font === 'default' ? 'monospace' : props.font)};
font-size: ${(props) => (props.fontSize ? props.fontSize : '13px')};
.empty-state {
color: ${(props) => props.theme.colors.text.muted};
padding: 1rem;
}
.accordion-header {
background-color: ${(props) => props.theme.requestTabPanel.card.bg};
.responses-container {
height: 100%;
&.single {
height: 100%;
}
&.multi {
overflow-y: auto;
}
}
.messages-list {
display: flex;
flex-direction: column;
}
.message-item {
display: flex;
flex-direction: column;
&:not(.last) {
border-bottom: 1px solid ${(props) => props.theme.border.border1};
}
}
.message-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 0;
cursor: pointer;
user-select: none;
&:hover {
background-color: ${(props) => props.theme.plainGrid.hoverBg};
}
&.open {
background-color: ${(props) => props.theme.plainGrid.hoverBg};
}
}
.error-header {
background-color: ${(props) => (props.theme.bg === '#1e1e1e' ? 'rgba(185, 28, 28, 0.1)' : '#fee2e2')};
}
.error-text {
color: ${(props) => props.theme.colors.text.danger};
}
div.tabs {
div.tab {
padding: 6px 0px;
border: none;
border-bottom: solid 2px transparent;
margin-right: ${(props) => props.theme.tabs.marginRight};
color: var(--color-tab-inactive);
cursor: pointer;
&:focus,
&:active,
&:focus-within,
&:focus-visible,
&:target {
outline: none !important;
box-shadow: none !important;
}
&.active {
font-weight: ${(props) => props.theme.tabs.active.fontWeight} !important;
color: ${(props) => props.theme.tabs.active.color} !important;
border-bottom: solid 2px ${(props) => props.theme.tabs.active.border} !important;
.toggle-btn {
color: ${(props) => props.theme.text};
}
}
}
.stream-status {
display: inline-flex;
.message-label {
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.subtext1};
}
.latest-badge {
margin-left: 8px;
padding: 2px 6px;
font-size: ${(props) => props.theme.font.size.xs};
font-weight: 500;
color: ${(props) => props.theme.colors.text.green};
background-color: color-mix(in srgb, ${(props) => props.theme.colors.text.green} 10%, transparent);
border-radius: ${(props) => props.theme.border.radius.sm};
}
.toggle-btn {
display: flex;
align-items: center;
&.complete {
color: ${(props) => props.theme.colors.text.green};
}
&.cancelled {
color: ${(props) => props.theme.colors.text.danger};
}
&.streaming {
color: ${(props) => props.theme.colors.text.blue};
}
justify-content: center;
color: ${(props) => props.theme.colors.text.muted};
transition: color 0.15s ease;
}
.message-counter {
display: inline-flex;
align-items: center;
margin-left: 10px;
}
.response-list {
max-height: 500px;
overflow-y: auto;
}
.response-message {
.message-content {
height: 240px;
margin-bottom: 8px;
padding: 8px;
border-radius: 4px;
background-color: var(--color-panel-background);
}
`;

View File

@@ -1,46 +1,31 @@
import React, { useState, useEffect } from 'react';
import Accordion from 'components/Accordion';
import CodeEditor from 'components/CodeEditor';
import { get } from 'lodash';
import { useSelector } from 'react-redux';
import { useTheme } from 'providers/Theme/index';
import StyledWrapper from './StyledWrapper';
import { formatISO9075 } from 'date-fns';
import GrpcError from '../GrpcError';
import { IconChevronDown, IconChevronUp } from '@tabler/icons';
const GrpcQueryResult = ({ item, collection }) => {
const { displayedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const [showErrorMessage, setShowErrorMessage] = useState(true);
const [expandedIndex, setExpandedIndex] = useState(0);
const response = item.response || {};
const responsesList = response?.responses || [];
// Reverse the responses list to show the most recent at the top
const reversedResponsesList = [...responsesList].reverse();
const hasError = response.isError;
const hasResponses = responsesList.length > 0;
const errorMessage = response.error;
// Reset error visibility when a new response is received
useEffect(() => {
if (hasError) {
setShowErrorMessage(true);
}
}, [response, hasError]);
// Format a timestamp to a human-readable format
const formatTimestamp = (timestamp) => {
if (!timestamp) return 'Unknown time';
try {
const date = new Date(timestamp);
return formatISO9075(date);
} catch (e) {
return 'Invalid time';
}
};
// Format JSON for display
const formatJSON = (data) => {
try {
if (typeof data === 'string') {
@@ -55,7 +40,7 @@ const GrpcQueryResult = ({ item, collection }) => {
if (!hasResponses && !hasError) {
return (
<StyledWrapper className="w-full h-full relative flex flex-col">
<div className="text-gray-500 dark:text-gray-400 p-4">No messages received</div>
<div className="empty-state">No messages received</div>
</StyledWrapper>
);
}
@@ -64,9 +49,8 @@ const GrpcQueryResult = ({ item, collection }) => {
<StyledWrapper className="w-full h-full relative flex flex-col mt-2" data-testid="grpc-response-content">
{hasError && showErrorMessage && <GrpcError error={errorMessage} onClose={() => setShowErrorMessage(false)} />}
{hasResponses && (
<div className={`overflow-y-auto ${responsesList.length === 1 ? 'flex-1' : ''}`} data-testid="grpc-responses-container">
<div className={`responses-container ${responsesList.length === 1 ? 'single' : 'multi'}`} data-testid="grpc-responses-container">
{responsesList.length === 1 ? (
// Single message - render directly without accordion
<div className="h-full" data-testid="grpc-single-response">
<CodeEditor
collection={collection}
@@ -79,43 +63,55 @@ const GrpcQueryResult = ({ item, collection }) => {
/>
</div>
) : (
// Multiple messages - use accordion
<Accordion defaultIndex={0} dataTestId="grpc-responses-accordion">
{reversedResponsesList.map((response, index) => {
// Calculate the original response number (for display purposes)
<div className="messages-list" data-testid="grpc-responses-list">
{reversedResponsesList.map((resp, index) => {
const originalIndex = responsesList.length - index - 1;
const isExpanded = expandedIndex === index;
return (
<Accordion.Item key={originalIndex} index={index} data-testid={`grpc-response-item-${originalIndex}`}>
<Accordion.Header index={index} style={{ padding: '8px 12px', minHeight: '40px' }}>
<div className="flex justify-between w-full">
<div className="font-medium">
Response {originalIndex + 1} {index === 0 ? '(Latest)' : ''}
</div>
</div>
</Accordion.Header>
<Accordion.Content index={index} style={{ padding: '0px' }}>
<div className="h-60">
<div
key={originalIndex}
className={`message-item ${isExpanded ? 'expanded' : 'collapsed'} ${index === reversedResponsesList.length - 1 ? 'last' : ''}`}
data-testid={`grpc-response-item-${originalIndex}`}
>
<div
className="message-header"
onClick={() => setExpandedIndex(isExpanded ? -1 : index)}
>
<span className="message-label">
Response {originalIndex + 1}
{index === 0 && <span className="latest-badge">Latest</span>}
</span>
<button className="toggle-btn">
{isExpanded ? (
<IconChevronUp size={16} strokeWidth={1.5} />
) : (
<IconChevronDown size={16} strokeWidth={1.5} />
)}
</button>
</div>
{isExpanded && (
<div className="message-content">
<CodeEditor
collection={collection}
font={get(preferences, 'font.codeFont', 'default')}
fontSize={get(preferences, 'font.codeFontSize')}
theme={displayedTheme}
value={formatJSON(response)}
value={formatJSON(resp)}
mode="application/json"
readOnly={true}
/>
</div>
</Accordion.Content>
</Accordion.Item>
)}
</div>
);
})}
</Accordion>
</div>
)}
</div>
)}
{hasError && !hasResponses && !showErrorMessage && (
<div className="text-gray-500 dark:text-gray-400 p-4">
<div className="empty-state">
No messages received. A server error occurred but has been dismissed.
</div>
)}

View File

@@ -6,17 +6,24 @@ const StyledWrapper = styled.div`
border-collapse: collapse;
thead {
color: #777777;
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.muted};
font-size: ${(props) => props.theme.font.size.xs};
font-weight: 500;
text-transform: uppercase;
}
td {
padding: 6px 10px;
padding: 8px 10px;
font-size: ${(props) => props.theme.font.size.sm};
&.key {
color: ${(props) => props.theme.colors.text.subtext1};
font-weight: 500;
}
&.value {
word-break: break-all;
color: ${(props) => props.theme.text};
}
}
@@ -25,6 +32,10 @@ const StyledWrapper = styled.div`
background-color: ${(props) => props.theme.table.striped};
}
}
.empty-message {
color: ${(props) => props.theme.colors.text.muted};
}
}
`;

View File

@@ -24,7 +24,7 @@ const GrpcResponseHeaders = ({ metadata }) => {
))
) : (
<tr>
<td colSpan="2" className="text-center py-4 text-gray-500">
<td colSpan="2" className="text-center py-4 empty-message">
No metadata received
</td>
</tr>

View File

@@ -6,17 +6,24 @@ const StyledWrapper = styled.div`
border-collapse: collapse;
thead {
color: #777777;
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.colors.text.muted};
font-size: ${(props) => props.theme.font.size.xs};
font-weight: 500;
text-transform: uppercase;
}
td {
padding: 6px 10px;
padding: 8px 10px;
font-size: ${(props) => props.theme.font.size.sm};
&.key {
color: ${(props) => props.theme.colors.text.subtext1};
font-weight: 500;
}
&.value {
word-break: break-all;
color: ${(props) => props.theme.text};
}
}
@@ -25,6 +32,10 @@ const StyledWrapper = styled.div`
background-color: ${(props) => props.theme.table.striped};
}
}
.empty-message {
color: ${(props) => props.theme.colors.text.muted};
}
}
`;

View File

@@ -23,7 +23,7 @@ const ResponseTrailers = ({ trailers }) => {
))
) : (
<tr>
<td colSpan="2" className="text-center py-4 text-gray-500">
<td colSpan="2" className="text-center py-4 empty-message">
No trailers received
</td>
</tr>

View File

@@ -12,7 +12,7 @@ const StyledWrapper = styled.div`
border: none;
border-bottom: solid 2px transparent;
margin-right: ${(props) => props.theme.tabs.marginRight};
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
cursor: pointer;
&:focus,

View File

@@ -7,7 +7,7 @@ const StyledWrapper = styled.div`
width: 100%;
.send-icon {
color: ${(props) => props.theme.requestTabPanel.responseSendIcon};
color: ${(props) => props.theme.background.surface2};
}
&.vertical-layout {

View File

@@ -7,7 +7,7 @@ const StyledWrapper = styled.div`
}
.button-dropdown-button {
color: ${(props) => props.theme.dropdown.primaryText};
color: ${(props) => props.theme.text};
border-color: ${(props) => props.theme.workspace.border};
}

View File

@@ -2,7 +2,7 @@ import styled from 'styled-components';
const StyledWrapper = styled.div`
button {
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
cursor: pointer;
&:hover {
@@ -18,7 +18,7 @@ const StyledWrapper = styled.div`
.cursor-pointer {
display: flex;
align-items: center;
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
&:hover {
color: var(--color-tab-active);

View File

@@ -2,7 +2,7 @@ import styled from 'styled-components';
const Wrapper = styled.div`
font-size: ${(props) => props.theme.font.size.sm};
font-weight: 600;
font-weight: 500;
color: ${(props) => props.theme.requestTabPanel.responseStatus};
text-align: center;
`;

View File

@@ -2,7 +2,7 @@ import styled from 'styled-components';
const Wrapper = styled.div`
font-size: ${(props) => props.theme.font.size.sm};
font-weight: 600;
font-weight: 500;
color: ${(props) => props.theme.requestTabPanel.responseStatus};
`;

View File

@@ -4,7 +4,7 @@ const StyledWrapper = styled.div`
padding-top: 20%;
width: 100%;
.send-icon {
color: ${(props) => props.theme.requestTabPanel.responseSendIcon};
color: ${(props) => props.theme.background.surface2};
}
`;

View File

@@ -25,7 +25,7 @@ const StyledWrapper = styled.div`
border: none;
border-bottom: solid 2px transparent;
margin-right: ${(props) => props.theme.tabs.marginRight};
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
cursor: pointer;
flex-shrink: 1;
overflow: hidden;

View File

@@ -12,7 +12,7 @@ const StyledWrapper = styled.div`
border: none;
border-bottom: solid 2px transparent;
margin-right: ${(props) => props.theme.tabs.marginRight};
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
cursor: pointer;
&:focus,

View File

@@ -1,31 +1,59 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
overflow-y: auto;
overflow-y: auto;
.ws-message.new {
background-color: ${({ theme }) => theme.table.striped};
.empty-state {
padding: 1rem;
color: ${(props) => props.theme.colors.text.muted};
}
.ws-message:not(:last-child) {
border-bottom: 1px solid ${({ theme }) => theme.table.border};
}
.ws-message:not(:last-child).open {
border-bottom-width: 0px;
}
.ws-incoming {
.ws-message {
background: ${(props) => props.theme.bg};
border-color: ${(props) => props.theme.table.border};
&.new {
background-color: ${({ theme }) => theme.table.striped};
}
&:not(:last-child) {
border-bottom: 1px solid ${({ theme }) => theme.border.border1};
}
&:not(:last-child).open {
border-bottom-width: 0px;
}
.message-content {
color: ${(props) => props.theme.text};
}
.message-timestamp {
font-size: ${(props) => props.theme.font.size.xs};
color: ${(props) => props.theme.colors.text.muted};
}
.chevron-icon {
color: ${(props) => props.theme.colors.text.muted};
}
}
.ws-outgoing {
background: ${(props) => props.theme.bg};
border-color: ${(props) => props.theme.table.border};
.ws-incoming .message-type-icon {
color: ${(props) => props.theme.colors.text.green};
}
.CodeMirror {
.ws-outgoing .message-type-icon {
color: ${(props) => props.theme.colors.text.yellow};
}
.ws-info .message-type-icon {
color: ${(props) => props.theme.colors.text.blue};
}
.ws-error .message-type-icon {
color: ${(props) => props.theme.colors.text.danger};
}
.CodeMirror {
border-radius: 0.25rem;
}
@@ -34,11 +62,12 @@ const StyledWrapper = styled.div`
}
div[role='tablist'] {
color: ${(props) => props.theme.colors.text.muted};
.active {
color: ${(props) => props.theme.colors.text.yellow};
}
}
`;
export default StyledWrapper;

View File

@@ -100,15 +100,17 @@ const WSMessageItem = ({ message, inFocus }) => {
}}
className={classnames('ws-message flex flex-col p-2', {
'ws-incoming': isIncoming,
'ws-outgoing': !isIncoming,
'ws-outgoing': isOutgoing,
'ws-info': isInfo,
'ws-error': isError,
'open': isOpen,
'new': isNew
})}
>
<div
className={classnames('flex items-center justify-between', {
'cursor-pointer': !isInfo,
'cursor-not-allowed': isInfo
'cursor-pointer': canOpenMessage,
'cursor-not-allowed': !canOpenMessage
})}
onClick={(e) => {
if (!canOpenMessage) return;
@@ -116,35 +118,26 @@ const WSMessageItem = ({ message, inFocus }) => {
}}
>
<div className="flex min-w-0 shrink">
<span
className={classnames('font-medium flex items-center gap-1',
{
'text-green-700': isIncoming,
'text-yellow-700': isOutgoing,
'text-blue-700': isInfo,
'text-red-700': isError,
'text-red-700': isError
})}
>
<span className="message-type-icon">
<TypeIcon type={message.type} />
</span>
<span className="ml-3 text-ellipsis max-w-full overflow-hidden text-nowrap">{parsedContent.content}</span>
<span className="ml-3 text-ellipsis max-w-full overflow-hidden text-nowrap message-content">{parsedContent.content}</span>
</div>
<div className="flex shrink-0 gap-2">
<div className="flex shrink-0 gap-2 items-center">
{message.timestamp && (
<span className="text-xs text-gray-400">{new Date(message.timestamp).toISOString()}</span>
<span className="message-timestamp">{new Date(message.timestamp).toISOString()}</span>
)}
{canOpenMessage
? (
<span className="text-gray-600">
<span className="chevron-icon">
{isOpen ? (
<IconChevronDown size={16} strokeWidth={1.5} className="text-zinc-700 dark:text-zinc-300" />
<IconChevronDown size={16} strokeWidth={1.5} />
) : (
<IconChevronRight size={16} strokeWidth={1.5} className="text-zinc-700 dark:text-zinc-300" />
<IconChevronRight size={16} strokeWidth={1.5} />
)}
</span>
)
: <span class="w-4"></span>}
: <span className="w-4"></span>}
</div>
</div>
{isOpen && (
@@ -188,11 +181,11 @@ const WSMessageItem = ({ message, inFocus }) => {
const WSMessagesList = ({ order = -1, messages = [] }) => {
if (!messages.length) {
return <div className="p-4 text-gray-500">No messages yet.</div>;
return <StyledWrapper><div className="empty-state">No messages yet.</div></StyledWrapper>;
}
const ordered = order === -1 ? messages : messages.slice().reverse();
return (
<StyledWrapper className="ws-messages-list mt-1 flex flex-col">
<StyledWrapper className="ws-messages-list mt-2 flex flex-col">
{ordered.map((msg, idx, src) => {
const inFocus = order === -1 ? src.length - 1 === idx : idx === 0;
return <WSMessageItem key={msg.timestamp} inFocus={inFocus} id={idx} message={msg} />;

View File

@@ -7,7 +7,7 @@ const StyledWrapper = styled.div`
border: none;
border-bottom: solid 2px transparent;
margin-right: ${(props) => props.theme.tabs.marginRight};
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
cursor: pointer;
&:focus,

View File

@@ -88,7 +88,7 @@ const StyledWrapper = styled.div`
transition: transform 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease;
&.is-selected {
background-color: ${(props) => props.theme.requestTabs.active.bg};
background-color: ${(props) => props.theme.background.surface2};
}
&.is-dragging {

View File

@@ -1,4 +1,5 @@
import styled from 'styled-components';
import { rgba } from 'polished';
const StyledWrapper = styled.div`
.tabs {
@@ -7,7 +8,7 @@ const StyledWrapper = styled.div`
border: none;
border-bottom: solid 2px transparent;
margin-right: ${(props) => props.theme.tabs.marginRight};
color: var(--color-tab-inactive);
color: ${(props) => props.theme.colors.text.subtext0};
cursor: pointer;
&:focus,
@@ -31,14 +32,24 @@ const StyledWrapper = styled.div`
display: flex;
border-radius: ${(props) => props.theme.border.radius.base};
padding: 10px;
border: 1px solid ${(props) => props.theme.sidebar.collection.item.indentBorder};
background-color: ${(props) => props.theme.sidebar.bg};
border: 1px solid ${(props) => props.theme.border.border0};
background-color: ${(props) => props.theme.background.base};
color: ${(props) => props.theme.text};
cursor: pointer;
transition: all 0.1s ease;
&.no-padding {
padding: 0px;
}
.note-warning {
color: ${(props) => props.theme.colors.text.warning};
background-color: ${(props) => rgba(props.theme.colors.text.warning, 0.06)};
}
&:hover {
background-color: ${(props) => props.theme.listItem.hoverBg};
background-color: ${(props) => props.theme.background.mantle};
border-color: ${(props) => props.theme.border.border2};
}
}
`;

View File

@@ -105,7 +105,7 @@ const ShareCollection = ({ onClose, collectionUid }) => {
</div>
<div
className={`flex !flex-col share-button ${
className={`flex !flex-col share-button no-padding ${
isCollectionLoading
? 'opacity-50 cursor-not-allowed'
: 'cursor-pointer'
@@ -113,7 +113,7 @@ const ShareCollection = ({ onClose, collectionUid }) => {
onClick={isCollectionLoading ? undefined : handleExportPostmanCollection}
>
{hasNonExportableRequestTypes.has && (
<div className="px-3 py-2 bg-yellow-50 w-full dark:bg-yellow-900/20 text-yellow-700 dark:text-yellow-400 text-xs border-b border-yellow-100 dark:border-yellow-800/20 flex items-center">
<div className="px-3 py-2 w-full flex items-center note-warning">
<IconAlertTriangle size={16} className="mr-2 flex-shrink-0" />
<span>
Note:

View File

@@ -1,11 +0,0 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
.api-specs-badge {
margin-inline: 0.5rem;
background-color: ${(props) => props.theme.sidebar.badge.bg};
border-radius: 5px;
}
`;
export default StyledWrapper;

View File

@@ -1,21 +0,0 @@
import { IconFileCode } from '@tabler/icons';
import StyledWrapper from './StyledWrapper';
const ApiSpecsBadge = () => {
return (
<StyledWrapper>
<div className="items-center mt-2 relative">
<div className="api-specs-badge flex items-center justify-between px-2">
<div className="flex items-center py-1 select-none">
<span className="mr-2">
<IconFileCode size={18} strokeWidth={1.5} />
</span>
<span>APIs</span>
</div>
</div>
</div>
</StyledWrapper>
);
};
export default ApiSpecsBadge;

View File

@@ -5,7 +5,7 @@ const StyledWrapper = styled.div`
height: 50vh;
display: flex;
flex-direction: column;
background-color: ${(props) => props.theme.collection.environment.settings.bg};
background-color: ${(props) => props.theme.background.base};
.code-generator {
display: flex;

View File

@@ -25,7 +25,7 @@ const StyledWrapper = styled.div`
display: inline-flex;
align-items: center;
padding: 6px 12px;
background-color: ${(props) => props.theme.requestTabs.active.bg};
background-color: ${(props) => props.theme.background.surface2};
border: 1px solid ${(props) => props.theme.requestTabs.bottomBorder};
border-radius: 4px;
font-size: ${(props) => props.theme.font.size.sm};

View File

@@ -320,12 +320,12 @@ const CreateCollection = ({ onClose, defaultLocation: propDefaultLocation }) =>
</div>
<div className="flex justify-end">
<span className="mr-2">
<Button type="button" size="sm" color="secondary" variant="ghost" onClick={onClose}>
<Button type="button" color="secondary" variant="ghost" onClick={onClose}>
Cancel
</Button>
</span>
<span>
<Button type="submit" size="sm">
<Button type="submit">
Create
</Button>
</span>

View File

@@ -1,17 +1,18 @@
import styled from 'styled-components';
import { darken, rgba } from 'polished';
const Wrapper = styled.div`
.current-group {
background-color: ${(props) => props.theme.sidebar.badge.bg};
background-color: ${(props) => props.theme.background.surface1};
border-radius: 4px;
padding: 0.4rem;
padding: 0.3rem 0.6rem;
cursor: pointer;
border: 1px solid ${(props) => props.theme.sidebar.badge.border || 'transparent'};
border: 1px solid ${(props) => props.theme.background.surface2};
}
.current-group:hover {
background-color: ${(props) => props.theme.sidebar.badge.hoverBg || props.theme.sidebar.badge.bg};
}
background-color: ${(props) => darken(0.03, props.theme.background.surface1)};
border-color: ${(props) => darken(0.03, props.theme.background.surface2)};
/* Fix dropdown positioning */
[data-tippy-root] {

View File

@@ -0,0 +1,81 @@
import styled from 'styled-components';
import { rgba } from 'polished';
const StyledWrapper = styled.div`
.theme-menu {
min-width: 160px;
padding: 4px 0;
background: ${(props) => props.theme.dropdown.bg};
border: 1px solid ${(props) => props.theme.dropdown.separator};
border-radius: ${(props) => props.theme.border.radius.md};
box-shadow: ${(props) => props.theme.dropdown.shadow};
}
.menu-label {
padding: 6px 12px 4px;
font-size: 11px;
font-weight: 500;
text-transform: uppercase;
color: ${(props) => props.theme.dropdown.mutedText};
letter-spacing: 0.5px;
}
.menu-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 12px;
cursor: pointer;
color: ${(props) => props.theme.dropdown.color};
font-size: ${(props) => props.theme.font.size.sm};
&:hover {
background: ${(props) => props.theme.dropdown.hoverBg};
}
&.active {
color: ${(props) => props.theme.dropdown.selectedColor};
background: ${(props) => rgba(props.theme.dropdown.selectedColor, 0.07)};
}
&.has-submenu {
padding-right: 8px;
}
}
.menu-item-icon {
margin-right: 8px;
opacity: 0.7;
}
.menu-item-label {
flex: 1;
}
.check-icon {
color: ${(props) => props.theme.dropdown.selectedColor};
margin-left: 8px;
}
.chevron-icon {
opacity: 0.6;
margin-left: 8px;
}
.menu-divider {
height: 1px;
background: ${(props) => props.theme.dropdown.separator};
margin: 4px 0;
}
.submenu {
min-width: 180px;
padding: 4px 0;
background: ${(props) => props.theme.dropdown.bg};
border: 1px solid ${(props) => props.theme.dropdown.separator};
border-radius: ${(props) => props.theme.border.radius.md};
box-shadow: ${(props) => props.theme.dropdown.shadow};
}
`;
export default StyledWrapper;

View File

@@ -0,0 +1,181 @@
import React, { useState } from 'react';
import Tippy from '@tippyjs/react';
import { IconChevronRight, IconCheck, IconSun, IconMoon, IconDeviceDesktop } from '@tabler/icons';
import ToolHint from 'components/ToolHint';
import { useTheme } from 'providers/Theme';
import { getLightThemes, getDarkThemes } from 'themes/index';
import StyledWrapper from './StyledWrapper';
const ThemeDropdown = ({ children }) => {
const [isOpen, setIsOpen] = useState(false);
const [lightSubmenuOpen, setLightSubmenuOpen] = useState(false);
const [darkSubmenuOpen, setDarkSubmenuOpen] = useState(false);
const [tooltipEnabled, setTooltipEnabled] = useState(true);
const {
storedTheme,
setStoredTheme,
themeVariantLight,
themeVariantDark,
setThemeVariantLight,
setThemeVariantDark
} = useTheme();
const lightThemes = getLightThemes();
const darkThemes = getDarkThemes();
const handleModeSelect = (mode) => {
setStoredTheme(mode);
};
const handleThemeSelect = (themeId, isLight) => {
if (isLight) {
setThemeVariantLight(themeId);
} else {
setThemeVariantDark(themeId);
}
setIsOpen(false);
setLightSubmenuOpen(false);
setDarkSubmenuOpen(false);
};
const renderSubmenu = (themes, isLight, currentVariant) => (
<div className="submenu">
{themes.map((theme) => (
<div
key={theme.id}
className={`menu-item ${currentVariant === theme.id ? 'active' : ''}`}
onClick={() => handleThemeSelect(theme.id, isLight)}
>
<span className="menu-item-label">{theme.name}</span>
{currentVariant === theme.id && (
<IconCheck size={14} strokeWidth={2} className="check-icon" />
)}
</div>
))}
</div>
);
const menuContent = (
<StyledWrapper>
<div className="theme-menu">
{/* Mode Section */}
<div className="menu-label">Mode</div>
<div
className={`menu-item ${storedTheme === 'light' ? 'active' : ''}`}
onClick={() => handleModeSelect('light')}
>
<IconSun size={14} strokeWidth={1.5} className="menu-item-icon" />
<span className="menu-item-label">Light</span>
{storedTheme === 'light' && (
<IconCheck size={14} strokeWidth={2} className="check-icon" />
)}
</div>
<div
className={`menu-item ${storedTheme === 'dark' ? 'active' : ''}`}
onClick={() => handleModeSelect('dark')}
>
<IconMoon size={14} strokeWidth={1.5} className="menu-item-icon" />
<span className="menu-item-label">Dark</span>
{storedTheme === 'dark' && (
<IconCheck size={14} strokeWidth={2} className="check-icon" />
)}
</div>
<div
className={`menu-item ${storedTheme === 'system' ? 'active' : ''}`}
onClick={() => handleModeSelect('system')}
>
<IconDeviceDesktop size={14} strokeWidth={1.5} className="menu-item-icon" />
<span className="menu-item-label">System</span>
{storedTheme === 'system' && (
<IconCheck size={14} strokeWidth={2} className="check-icon" />
)}
</div>
<div className="menu-divider" />
{/* Light Themes with Submenu */}
<Tippy
content={renderSubmenu(lightThemes, true, themeVariantLight)}
placement="right-start"
interactive={true}
arrow={false}
offset={[0, 2]}
animation={false}
visible={lightSubmenuOpen}
onClickOutside={() => setLightSubmenuOpen(false)}
appendTo="parent"
>
<div
className="menu-item has-submenu"
onMouseEnter={() => {
setLightSubmenuOpen(true);
setDarkSubmenuOpen(false);
}}
>
<span className="menu-item-label">Light Themes</span>
<IconChevronRight size={14} strokeWidth={2} className="chevron-icon" />
</div>
</Tippy>
{/* Dark Themes with Submenu */}
<Tippy
content={renderSubmenu(darkThemes, false, themeVariantDark)}
placement="right-start"
interactive={true}
arrow={false}
offset={[0, 2]}
animation={false}
visible={darkSubmenuOpen}
onClickOutside={() => setDarkSubmenuOpen(false)}
appendTo="parent"
>
<div
className="menu-item has-submenu"
onMouseEnter={() => {
setDarkSubmenuOpen(true);
setLightSubmenuOpen(false);
}}
>
<span className="menu-item-label">Dark Themes</span>
<IconChevronRight size={14} strokeWidth={2} className="chevron-icon" />
</div>
</Tippy>
</div>
</StyledWrapper>
);
const handleOpen = () => {
setTooltipEnabled(false);
setIsOpen(true);
};
const handleClose = () => {
setIsOpen(false);
setLightSubmenuOpen(false);
setDarkSubmenuOpen(false);
// Small delay before re-enabling tooltip to prevent flash
setTimeout(() => setTooltipEnabled(true), 100);
};
return (
<ToolHint text="Theme" toolhintId="ThemeDropdown" place="top" offset={10} hidden={!tooltipEnabled}>
<Tippy
content={menuContent}
placement="top-start"
interactive={true}
arrow={false}
animation={false}
visible={isOpen}
onClickOutside={handleClose}
appendTo="parent"
>
<div onClick={() => isOpen ? handleClose() : handleOpen()}>
{children}
</div>
</Tippy>
</ToolHint>
);
};
export default ThemeDropdown;

View File

@@ -1,14 +1,14 @@
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { IconSettings, IconCookie, IconTool, IconSearch } from '@tabler/icons';
import { IconSettings, IconCookie, IconTool, IconSearch, IconPalette } from '@tabler/icons';
import Mousetrap from 'mousetrap';
import { getKeyBindingsForActionAllOS } from 'providers/Hotkeys/keyMappings';
import ToolHint from 'components/ToolHint';
import Preferences from 'components/Preferences';
import IconSidebarToggle from 'components/Icons/IconSidebarToggle';
import Cookies from 'components/Cookies';
import Notifications from 'components/Notifications';
import Portal from 'components/Portal';
import ThemeDropdown from './ThemeDropdown';
import { showPreferences } from 'providers/ReduxStore/slices/app';
import { openConsole } from 'providers/ReduxStore/slices/logs';
import { useApp } from 'providers/App';
@@ -81,6 +81,17 @@ const StatusBar = () => {
</button>
</ToolHint>
<ThemeDropdown>
<button
className="status-bar-button"
data-trigger="theme"
tabIndex={0}
aria-label="Change Theme"
>
<IconPalette size={16} strokeWidth={1.5} aria-hidden="true" />
</button>
</ThemeDropdown>
<ToolHint text="Notifications" toolhintId="Notifications" place="top" offset={10}>
<div className="status-bar-button">
<Notifications />

View File

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

View File

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

View File

@@ -25,14 +25,14 @@ const StyledWrapper = styled.div`
table th {
position: relative;
border-bottom: 1px solid ${(props) => props.theme.collection.environment.settings.gridBorder}77;
border-bottom: 1px solid ${(props) => props.theme.border.BORDER0};
}
table tr td {
padding: 0.5rem;
text-align: left;
border-top: 1px solid ${(props) => props.theme.collection.environment.settings.gridBorder}77;
border-right: 1px solid ${(props) => props.theme.collection.environment.settings.gridBorder}77;
border-top: 1px solid ${(props) => props.theme.border.BORDER0};
border-right: 1px solid ${(props) => props.theme.border.BORDER0};
}
tr {
@@ -50,11 +50,11 @@ const StyledWrapper = styled.div`
table tr th {
padding: 0.5rem;
text-align: left;
border-top: 1px solid ${(props) => props.theme.collection.environment.settings.gridBorder}77;
border-right: 1px solid ${(props) => props.theme.collection.environment.settings.gridBorder}77;
border-top: 1px solid ${(props) => props.theme.border.BORDER0};
border-right: 1px solid ${(props) => props.theme.border.BORDER0};
&:nth-child(1) {
border-left: 1px solid ${(props) => props.theme.collection.environment.settings.gridBorder}77;
border-left: 1px solid ${(props) => props.theme.border.BORDER0};
}
}
`;

View File

@@ -26,7 +26,7 @@ const StyledWrapper = styled.div`
cursor: default;
&:has(.tag-remove:hover) {
background-color: ${(props) => props.theme.requestTabs.active.bg};
background-color: ${(props) => props.theme.background.surface2};
border-color: ${(props) => props.theme.requestTabs.active.border || props.theme.requestTabs.bottomBorder};
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transform: translateY(-1px);

View File

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

View File

@@ -1,6 +1,5 @@
import React from 'react';
import { Tooltip as ReactToolHint } from 'react-tooltip';
import StyledWrapper from './StyledWrapper';
import { useTheme } from 'providers/Theme';
const ToolHint = ({
@@ -20,8 +19,8 @@ const ToolHint = ({
const { theme: contextTheme } = useTheme();
const appliedTheme = theme || contextTheme;
const toolhintBackgroundColor = appliedTheme?.sidebar.badge.bg || 'black';
const toolhintTextColor = appliedTheme?.text || 'white';
const toolhintBackgroundColor = appliedTheme?.background.surface1;
const toolhintTextColor = appliedTheme?.text;
const combinedToolhintStyle = {
...tooltipStyle,
@@ -40,21 +39,19 @@ const ToolHint = ({
<>
{!anchorSelect && <span id={toolhintId} className={className}>{children}</span>}
{anchorSelect && children}
<StyledWrapper theme={appliedTheme}>
<ReactToolHint
{...toolhintProps_final}
content={anchorSelect ? undefined : text}
className="toolhint"
offset={offset}
place={place}
hidden={hidden}
positionStrategy={positionStrategy}
noArrow={true}
delayShow={delayShow}
style={combinedToolhintStyle}
/>
</StyledWrapper>
<ReactToolHint
{...toolhintProps_final}
content={anchorSelect ? undefined : text}
className="toolhint"
offset={offset}
place={place}
hidden={hidden}
positionStrategy={positionStrategy}
noArrow={true}
delayShow={delayShow}
style={combinedToolhintStyle}
opacity={1}
/>
</>
);
};

View File

@@ -88,8 +88,8 @@ const VariablesEditor = ({ collection }) => {
const reactInspectorTheme
= displayedTheme === 'light'
? { ...chromeLight, OBJECT_VALUE_STRING_COLOR: theme.variables.runtime.color }
: { ...chromeDark, OBJECT_VALUE_STRING_COLOR: theme.variables.runtime.color };
? { ...chromeLight, OBJECT_VALUE_STRING_COLOR: theme.text.base }
: { ...chromeDark, OBJECT_VALUE_STRING_COLOR: theme.text.base };
return (
<StyledWrapper className="px-4 py-4 overflow-auto">

View File

@@ -1,4 +1,5 @@
import styled from 'styled-components';
import { rgba } from 'polished';
const StyledWrapper = styled.div`
.main-content {
@@ -67,7 +68,7 @@ const StyledWrapper = styled.div`
color: ${(props) => props.theme.colors.text.green};
&:hover {
background: ${(props) => props.theme.colors.text.green}1A;
background-color: ${(props) => rgba(props.theme.colors.text.green, 0.1)};
}
}
@@ -75,7 +76,7 @@ const StyledWrapper = styled.div`
color: ${(props) => props.theme.colors.text.danger};
&:hover {
background: ${(props) => props.theme.colors.text.danger}1A;
background-color: ${(props) => rgba(props.theme.colors.text.danger, 0.1)};
}
}
}
@@ -93,21 +94,6 @@ const StyledWrapper = styled.div`
min-width: 140px;
}
.dropdown-item {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
cursor: pointer;
transition: background 0.15s;
color: ${(props) => props.theme.text};
font-size: ${(props) => props.theme.font.size.sm};
&:hover {
background: ${(props) => props.theme.listItem.hoverBg};
}
}
.tab-content {
flex: 1;
overflow: hidden;

View File

@@ -88,7 +88,7 @@ const StyledWrapper = styled.div`
width: 52px;
height: 52px;
border-radius: 8px;
background: ${(props) => props.theme.workspace.card.bg};
background: ${(props) => props.theme.background.mantle};
border: 1px solid ${(props) => props.theme.workspace.border};
color: ${(props) => props.theme.colors.text.muted};
margin-bottom: 16px;

View File

@@ -8,6 +8,7 @@ import Markdown from 'components/MarkDown';
import CodeEditor from 'components/CodeEditor';
import StyledWrapper from './StyledWrapper';
import { IconFileText, IconEdit, IconX } from '@tabler/icons';
import Button from 'ui/Button';
import toast from 'react-hot-toast';
const WorkspaceDocs = ({ workspace }) => {
@@ -113,9 +114,9 @@ const WorkspaceDocs = ({ workspace }) => {
<li>Key workflows</li>
<li>Resources & FAQs</li>
</ul>
<button className="add-docs-btn" onClick={handleAddDocumentation}>
<Button color="secondary" size="md" onClick={handleAddDocumentation}>
Add Documentation
</button>
</Button>
</div>
)}
</div>

View File

@@ -9,7 +9,7 @@ const Wrapper = styled.div`
.table-container {
overflow-y: auto;
border-radius: 8px;
border: ${(props) => props.theme.workspace.environments.indentBorder};
border: solid 1px ${(props) => props.theme.border.border0};
}
table {
@@ -45,8 +45,8 @@ const Wrapper = styled.div`
td {
padding: 5px 10px !important;
border-bottom: ${(props) => props.theme.workspace.environments.indentBorder};
border-right: ${(props) => props.theme.workspace.environments.indentBorder};
border-bottom: solid 1px ${(props) => props.theme.border.border0};
border-right: ${(props) => props.theme.border.border0};
&:last-child {
border-right: none;
@@ -63,8 +63,8 @@ const Wrapper = styled.div`
}
td {
border-bottom: ${(props) => props.theme.workspace.environments.indentBorder};
border-right: ${(props) => props.theme.workspace.environments.indentBorder};
border-bottom: solid 1px ${(props) => props.theme.border.border0};
border-right: solid 1px ${(props) => props.theme.border.border0};
&:last-child {
border-right: none;

View File

@@ -1,4 +1,5 @@
import styled from 'styled-components';
import { rgba } from 'polished';
const StyledWrapper = styled.div`
display: flex;
@@ -174,7 +175,7 @@ const StyledWrapper = styled.div`
}
&.active {
background: ${(props) => props.theme.workspace.environments.activeBg};
background: ${(props) => props.theme.background.surface0};
color: ${(props) => props.theme.text};
}
@@ -250,19 +251,18 @@ const StyledWrapper = styled.div`
transition: all 0.15s ease;
&.save {
color: ${(props) => props.theme.textLink};
color: ${(props) => props.theme.colors.text.green};
&:hover {
background: ${(props) => props.theme.listItem.hoverBg};
background: ${(props) => rgba(props.theme.colors.text.green, 0.1)};
}
}
&.cancel {
color: ${(props) => props.theme.colors.text.muted};
color: ${(props) => props.theme.colors.text.danger};
&:hover {
background: ${(props) => props.theme.listItem.hoverBg};
color: ${(props) => props.theme.text};
background: ${(props) => rgba(props.theme.colors.text.danger, 0.1)};
}
}
}

View File

@@ -11,8 +11,7 @@ const StyledWrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 1;
padding-top: 10%;
color: ${(props) => props.theme.colors.text.muted};
svg {
@@ -32,22 +31,6 @@ const StyledWrapper = styled.div`
gap: 8px;
}
}
.shared-button {
padding: 5px 10px;
font-size: 12px;
border-radius: 5px;
border: 1px solid ${(props) => props.theme.sidebar.collection.item.indentBorder};
background: ${(props) => props.theme.sidebar.bg};
color: ${(props) => props.theme.text};
cursor: pointer;
transition: all 0.1s ease;
&:hover {
background: ${(props) => props.theme.listItem.hoverBg};
border-color: ${(props) => props.theme.textLink};
}
}
`;
export default StyledWrapper;

View File

@@ -6,18 +6,19 @@ import StyledWrapper from './StyledWrapper';
import { IconFileAlert } from '@tabler/icons';
import ImportEnvironmentModal from 'components/Environments/Common/ImportEnvironmentModal';
import ExportEnvironmentModal from 'components/Environments/Common/ExportEnvironmentModal';
import Button from 'ui/Button';
const DefaultTab = ({ setTab }) => (
<div className="empty-state">
<IconFileAlert size={48} strokeWidth={1.5} />
<div className="title">No Environments</div>
<div className="actions">
<button className="shared-button" onClick={() => setTab('create')}>
<Button size="sm" color="secondary" onClick={() => setTab('create')}>
Create Environment
</button>
<button className="shared-button" onClick={() => setTab('import')}>
</Button>
<Button size="sm" color="secondary" onClick={() => setTab('import')}>
Import Environment
</button>
</Button>
</div>
</div>
);

View File

@@ -97,25 +97,6 @@ const StyledWrapper = styled.div`
.collection-dropdown {
min-width: 120px;
}
.dropdown-item {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
cursor: pointer;
transition: background 0.15s ease;
color: ${(props) => props.theme.text};
font-size: ${(props) => props.theme.font.size.sm};
&:hover {
background: ${(props) => props.theme.listItem.hoverBg};
}
&.dropdown-item-danger {
color: ${(props) => props.theme.colors.text.danger};
}
}
`;
export default StyledWrapper;

View File

@@ -288,7 +288,7 @@ const CollectionsList = ({ workspace }) => {
<span>Share</span>
</div>
<div
className="dropdown-item dropdown-item-danger"
className="dropdown-item delete-item"
onClick={(e) => {
e.stopPropagation();
handleRemoveCollection(collection);

View File

@@ -6,6 +6,7 @@ import toast from 'react-hot-toast';
import CreateCollection from 'components/Sidebar/CreateCollection';
import ImportCollection from 'components/Sidebar/ImportCollection';
import ImportCollectionLocation from 'components/Sidebar/ImportCollectionLocation';
import Button from 'ui/Button';
import CollectionsList from './CollectionsList';
import WorkspaceDocs from '../WorkspaceDocs';
import StyledWrapper from './StyledWrapper';
@@ -107,18 +108,30 @@ const WorkspaceOverview = ({ workspace }) => {
<div className="quick-actions-section">
<div className="section-title">Quick Actions</div>
<div className="quick-actions-buttons">
<button className="quick-action-btn" onClick={handleCreateCollection}>
<IconPlus size={14} strokeWidth={1.5} />
<span>Create Collection</span>
</button>
<button className="quick-action-btn" onClick={handleOpenCollection}>
<IconFolder size={14} strokeWidth={1.5} />
<span>Open Collection</span>
</button>
<button className="quick-action-btn" onClick={handleImportCollection}>
<IconDownload size={14} strokeWidth={1.5} />
<span>Import Collection</span>
</button>
<Button
color="secondary"
size="sm"
icon={<IconPlus size={14} strokeWidth={1.5} />}
onClick={handleCreateCollection}
>
Create Collection
</Button>
<Button
color="secondary"
size="sm"
icon={<IconFolder size={14} strokeWidth={1.5} />}
onClick={handleOpenCollection}
>
Open Collection
</Button>
<Button
color="secondary"
size="sm"
icon={<IconDownload size={14} strokeWidth={1.5} />}
onClick={handleImportCollection}
>
Import Collection
</Button>
</div>
</div>

View File

@@ -11,6 +11,7 @@ const initialState = {
screenWidth: 500,
showHomePage: false,
showPreferences: false,
preferencesTab: null,
showApiSpecPage: false,
showManageWorkspacePage: false,
isEnvironmentSettingsModalOpen: false,
@@ -95,7 +96,13 @@ export const appSlice = createSlice({
state.showApiSpecPage = false;
},
showPreferences: (state, action) => {
state.showPreferences = action.payload;
if (typeof action.payload === 'object') {
state.showPreferences = action.payload.show;
state.preferencesTab = action.payload.tab || null;
} else {
state.showPreferences = action.payload;
state.preferencesTab = null;
}
},
updatePreferences: (state, action) => {
state.preferences = action.payload;

View File

@@ -1,15 +1,22 @@
import React from 'react';
import { Validator } from 'jsonschema';
import toast from 'react-hot-toast';
import themes from 'themes/index';
import themeSchema from 'themes/schema';
import useLocalStorage from 'hooks/useLocalStorage/index';
import { createContext, useContext, useEffect, useState } from 'react';
import { createContext, useContext, useEffect, useState, useMemo } from 'react';
import { ThemeProvider as SCThemeProvider } from 'styled-components';
const validator = new Validator();
export const ThemeContext = createContext();
export const ThemeProvider = (props) => {
const isBrowserThemeLight = window.matchMedia('(prefers-color-scheme: light)').matches;
const [displayedTheme, setDisplayedTheme] = useState(isBrowserThemeLight ? 'light' : 'dark');
const [storedTheme, setStoredTheme] = useLocalStorage('bruno.theme', 'system');
const [themeVariantLight, setThemeVariantLight] = useLocalStorage('bruno.themeVariantLight', 'light');
const [themeVariantDark, setThemeVariantDark] = useLocalStorage('bruno.themeVariantDark', 'dark');
const toggleHtml = () => {
const html = document.querySelector('html');
if (html) {
@@ -45,14 +52,50 @@ export const ThemeProvider = (props) => {
// storedTheme can have 3 values: 'light', 'dark', 'system'
// displayedTheme can have 2 values: 'light', 'dark'
const theme = storedTheme === 'system' ? themes[displayedTheme] : themes[storedTheme];
const themeOptions = Object.keys(themes);
// Get the appropriate variant based on the current display mode
const theme = useMemo(() => {
const isLightMode = displayedTheme === 'light';
const variantName = isLightMode ? themeVariantLight : themeVariantDark;
const fallbackTheme = isLightMode ? themes.light : themes.dark;
const fallbackName = isLightMode ? 'light' : 'dark';
// Check if the variant exists in themes
const selectedTheme = themes[variantName];
if (!selectedTheme) {
// Only show toast if using a non-default variant that doesn't exist
if (variantName !== fallbackName) {
toast.error(`Theme "${variantName}" not found. Using default ${fallbackName} theme.`, {
duration: 4000,
id: `theme-not-found-${variantName}` // Prevent duplicate toasts
});
}
return fallbackTheme;
}
// Validate the theme against the schema
const validationResult = validator.validate(selectedTheme, themeSchema);
if (!validationResult.valid) {
const errors = validationResult.errors?.map((e) => e.stack).join(', ') || 'Unknown validation error';
console.error(`Theme "${variantName}" validation failed:`, errors);
toast.error(`Invalid theme "${variantName}". Using default ${fallbackName} theme.`, {
duration: 4000,
id: `theme-invalid-${variantName}` // Prevent duplicate toasts
});
return fallbackTheme;
}
return selectedTheme;
}, [displayedTheme, themeVariantLight, themeVariantDark]);
const value = {
theme,
themeOptions,
storedTheme,
displayedTheme,
setStoredTheme
setStoredTheme,
themeVariantLight,
setThemeVariantLight,
themeVariantDark,
setThemeVariantDark
};
return (

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