mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-24 21:25:45 +00:00
439
package-lock.json
generated
439
package-lock.json
generated
@@ -10422,420 +10422,6 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/color-convert": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/color-convert/-/color-convert-2.9.2.tgz",
|
||||
"integrity": "sha512-ibw9OS29S7GlL+vDwU3p5XG3vhR7XdzUecydpZbakUeg2Td6nfsnrCAX9sbLwQ73p0abO42v+V4qRaWq+7/BjQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": ">=7.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/react-color": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color/-/react-color-2.9.2.tgz",
|
||||
"integrity": "sha512-nIrw4Ol6boAkr1CUcCAkrUFVrKbT9T7/0qaSDpXmiDgKYf77gbXTWTsqVuXVDCSoCn28LvurpASS4AW8oiSBtg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uiw/color-convert": "2.9.2",
|
||||
"@uiw/react-color-alpha": "2.9.2",
|
||||
"@uiw/react-color-block": "2.9.2",
|
||||
"@uiw/react-color-chrome": "2.9.2",
|
||||
"@uiw/react-color-circle": "2.9.2",
|
||||
"@uiw/react-color-colorful": "2.9.2",
|
||||
"@uiw/react-color-compact": "2.9.2",
|
||||
"@uiw/react-color-editable-input": "2.9.2",
|
||||
"@uiw/react-color-editable-input-hsla": "2.9.2",
|
||||
"@uiw/react-color-editable-input-rgba": "2.9.2",
|
||||
"@uiw/react-color-github": "2.9.2",
|
||||
"@uiw/react-color-hue": "2.9.2",
|
||||
"@uiw/react-color-material": "2.9.2",
|
||||
"@uiw/react-color-name": "2.9.2",
|
||||
"@uiw/react-color-saturation": "2.9.2",
|
||||
"@uiw/react-color-shade-slider": "2.9.2",
|
||||
"@uiw/react-color-sketch": "2.9.2",
|
||||
"@uiw/react-color-slider": "2.9.2",
|
||||
"@uiw/react-color-swatch": "2.9.2",
|
||||
"@uiw/react-color-wheel": "2.9.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": ">=7.19.0",
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/react-color-alpha": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color-alpha/-/react-color-alpha-2.9.2.tgz",
|
||||
"integrity": "sha512-a2ACkE2vZIS4xnN9DaRfkQtAX/t8oK5NRSbX2QeOL23WIMHP1VNs7Yq5gXB68RHYenFgvs2JHuMOxZ2mK1W5Mw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uiw/color-convert": "2.9.2",
|
||||
"@uiw/react-drag-event-interactive": "2.9.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": ">=7.19.0",
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/react-color-block": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color-block/-/react-color-block-2.9.2.tgz",
|
||||
"integrity": "sha512-0EIZTELA5pnxyMlBOFo3hrpy73db+Qeq6E+QptNfD/8izor8OvY1Uquj2VqD6gDz+iVHMELIoKxpaQ8sZR7NOg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uiw/color-convert": "2.9.2",
|
||||
"@uiw/react-color-editable-input": "2.9.2",
|
||||
"@uiw/react-color-swatch": "2.9.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": ">=7.19.0",
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/react-color-chrome": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color-chrome/-/react-color-chrome-2.9.2.tgz",
|
||||
"integrity": "sha512-p7OZB7VWrkVbHxcTHsAq5U2vt3hAP3VvKEiDi592LKxS11IMnSd15ta8ngbJaXZWatqEpJSNgj12581yHtx+Bg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uiw/color-convert": "2.9.2",
|
||||
"@uiw/react-color-alpha": "2.9.2",
|
||||
"@uiw/react-color-editable-input": "2.9.2",
|
||||
"@uiw/react-color-editable-input-hsla": "2.9.2",
|
||||
"@uiw/react-color-editable-input-rgba": "2.9.2",
|
||||
"@uiw/react-color-github": "2.9.2",
|
||||
"@uiw/react-color-hue": "2.9.2",
|
||||
"@uiw/react-color-saturation": "2.9.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": ">=7.19.0",
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/react-color-circle": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color-circle/-/react-color-circle-2.9.2.tgz",
|
||||
"integrity": "sha512-7XaeX3LfCRkZKffHL/KtYps7I9hNpmx9sJOuwi0ML+3urToFD8s7Iuq3upYZt8REYt1Y84SBjuUqx2YYmjUEjA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uiw/color-convert": "2.9.2",
|
||||
"@uiw/react-color-swatch": "2.9.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": ">=7.19.0",
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/react-color-colorful": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color-colorful/-/react-color-colorful-2.9.2.tgz",
|
||||
"integrity": "sha512-tz/xeHayna2wpLipkZmcMgL1rmLMxfAmlOyBhUeWrpvqb9Fx59C/wL+5IYJA4rdsQvr9WyWjWmU/GhVKsEkW9w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uiw/color-convert": "2.9.2",
|
||||
"@uiw/react-color-alpha": "2.9.2",
|
||||
"@uiw/react-color-hue": "2.9.2",
|
||||
"@uiw/react-color-saturation": "2.9.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": ">=7.19.0",
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/react-color-compact": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color-compact/-/react-color-compact-2.9.2.tgz",
|
||||
"integrity": "sha512-fSSgRRBjkYuGRebZ7XM5XGVFvFwEyEGaV6mOhUpr3JFKhIES0/9oPbd80GDbRdj57Zxxrj76MCtd/aCFfwQSWA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uiw/color-convert": "2.9.2",
|
||||
"@uiw/react-color-editable-input": "2.9.2",
|
||||
"@uiw/react-color-editable-input-rgba": "2.9.2",
|
||||
"@uiw/react-color-swatch": "2.9.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": ">=7.19.0",
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/react-color-editable-input": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color-editable-input/-/react-color-editable-input-2.9.2.tgz",
|
||||
"integrity": "sha512-DY7pu12+LDRn6cxDMvsy1/quaPTxicAPz/kfODV7KBf8+Hq4rFWeJ4KS6m22IKIbQxrBQgmQG0WFJLaPeY7cPw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": ">=7.19.0",
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/react-color-editable-input-hsla": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color-editable-input-hsla/-/react-color-editable-input-hsla-2.9.2.tgz",
|
||||
"integrity": "sha512-kZfj3W20msLeP8/HY498rG30eBHRPAyxduhu94HLa9XggT/0ogwA9xZJZgWd6B7FYPeRlhRDY7dnF7caND63GQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uiw/color-convert": "2.9.2",
|
||||
"@uiw/react-color-editable-input-rgba": "2.9.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": ">=7.19.0",
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/react-color-editable-input-rgba": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color-editable-input-rgba/-/react-color-editable-input-rgba-2.9.2.tgz",
|
||||
"integrity": "sha512-sQW3tSao754954aQuVK4qvn1i+KC2piE4UftaBubD3QxC02gg5VfgZRoI6AV+nLr73Ifv3mCXewjN1BcP/+x4A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uiw/color-convert": "2.9.2",
|
||||
"@uiw/react-color-editable-input": "2.9.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": ">=7.19.0",
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/react-color-github": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color-github/-/react-color-github-2.9.2.tgz",
|
||||
"integrity": "sha512-pKR94swzWxqRFUEZJBQ8WHcw6CklxLWhDyjvGpxiieAlwUAL0mlmtCcctRgsmJRuAKQlZx4WNslRgNX5aVcoZw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uiw/color-convert": "2.9.2",
|
||||
"@uiw/react-color-swatch": "2.9.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": ">=7.19.0",
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/react-color-hue": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color-hue/-/react-color-hue-2.9.2.tgz",
|
||||
"integrity": "sha512-vDGN5YCzw09BfxkQeDvAeQ7zAy141uJ3HkFk1lsXL7ha8xZ35AItE1s/C6d60vFjGdoloKShh0yA7df3pnjmxA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uiw/color-convert": "2.9.2",
|
||||
"@uiw/react-color-alpha": "2.9.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": ">=7.19.0",
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/react-color-material": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color-material/-/react-color-material-2.9.2.tgz",
|
||||
"integrity": "sha512-R9MI9IlTof/L1rdxUFQAWgAgUSNJGXQsPujo8UGpwR7o5d+A3wwybUnPBsGKRnZwDy5zW7x+lPxY46GXE9aU9Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uiw/color-convert": "2.9.2",
|
||||
"@uiw/react-color-editable-input": "2.9.2",
|
||||
"@uiw/react-color-editable-input-rgba": "2.9.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": ">=7.19.0",
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/react-color-name": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color-name/-/react-color-name-2.9.2.tgz",
|
||||
"integrity": "sha512-knqRUkAe3pv6rB+tGzaURtwQkBqjRG62YNlzUx8Ty7g+pskWpLSPiMikW+9H5sLPq7wU3ichZiygqIp4BRgQzA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"colors-named": "^1.0.1",
|
||||
"colors-named-hex": "^1.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": ">=7.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/react-color-saturation": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color-saturation/-/react-color-saturation-2.9.2.tgz",
|
||||
"integrity": "sha512-w1aUU+g6Axwbr1nLvF8k/zg5v7UW8z80eH6C7w+tdiOFOQKkKQlXqeOG0IIUUIj3v/ji+yM90IuOH+Ku7zsJrg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uiw/color-convert": "2.9.2",
|
||||
"@uiw/react-drag-event-interactive": "2.9.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": ">=7.19.0",
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/react-color-shade-slider": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color-shade-slider/-/react-color-shade-slider-2.9.2.tgz",
|
||||
"integrity": "sha512-VE59rWv5ixqCN2CTpoe33j4SOCGU62bKguizx4HxgKczE/X0ySeEas8iP5XLg/4fYWl3EZN4uI+M8mNRnB0DPw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uiw/color-convert": "2.9.2",
|
||||
"@uiw/react-color-alpha": "2.9.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": ">=7.19.0",
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/react-color-sketch": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color-sketch/-/react-color-sketch-2.9.2.tgz",
|
||||
"integrity": "sha512-PEvwaqDVDdBI/+fWASBWQOvx3ows7dIcv6Z06VHgEXk2chi95Fkrbd0YUUXMcp7ESsmUK1j5ozGMLAf9Nvx6Nw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uiw/color-convert": "2.9.2",
|
||||
"@uiw/react-color-alpha": "2.9.2",
|
||||
"@uiw/react-color-editable-input": "2.9.2",
|
||||
"@uiw/react-color-editable-input-rgba": "2.9.2",
|
||||
"@uiw/react-color-hue": "2.9.2",
|
||||
"@uiw/react-color-saturation": "2.9.2",
|
||||
"@uiw/react-color-swatch": "2.9.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": ">=7.19.0",
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/react-color-slider": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color-slider/-/react-color-slider-2.9.2.tgz",
|
||||
"integrity": "sha512-vsi0AwmFJpb+flF8XCkacbX+MwLGOzrDKMqR29XE5sO8ERaezoT5mmYXzXXFcjyZYIuXse4C3JT38nsmOBp1vQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uiw/color-convert": "2.9.2",
|
||||
"@uiw/react-color-alpha": "2.9.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": ">=7.19.0",
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/react-color-swatch": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color-swatch/-/react-color-swatch-2.9.2.tgz",
|
||||
"integrity": "sha512-6zBy+E9NzZR672M2wPsbbNRqKy9Wi9jOuuxxyzov1CEZp+pPX7UwMlCX6RUhKdO0PzTSPCVQmbz5bplu5vsW0w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uiw/color-convert": "2.9.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": ">=7.19.0",
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/react-color-wheel": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-color-wheel/-/react-color-wheel-2.9.2.tgz",
|
||||
"integrity": "sha512-ayGzQyMZM3Cp+sX7LNElQ/QQWMO7YG4k/RQwVJAhxNQ+4lJ/p4LLSnI85D7NxILkk+jiXnjxRroxxZ2eJhWo+g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uiw/color-convert": "2.9.2",
|
||||
"@uiw/react-drag-event-interactive": "2.9.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": ">=7.19.0",
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uiw/react-drag-event-interactive": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@uiw/react-drag-event-interactive/-/react-drag-event-interactive-2.9.2.tgz",
|
||||
"integrity": "sha512-6gxQz+Ij7JkXlEOpfZhOu+Gdp/sI9VnMeDl8AJeYl3+0YXP31lXGmyb0NkNYnoUmJO+RrAf68c1raMpaDWs+Ow==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/runtime": ">=7.19.0",
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@usebruno/app": {
|
||||
"resolved": "packages/bruno-app",
|
||||
"link": true
|
||||
@@ -13760,30 +13346,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/colors-named": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/colors-named/-/colors-named-1.0.4.tgz",
|
||||
"integrity": "sha512-R254qrKSxFJNa7QmM7vrRaz5Hygr7MIaNbXcIx7WfmlYJ9OjZQ+aczGlnKS8lLtNT0GM9aGZ4EcmNXrh5ttv6g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/colors-named-hex": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/colors-named-hex/-/colors-named-hex-1.0.3.tgz",
|
||||
"integrity": "sha512-vhUoMdCdOKgD9Ni3p6uV3ET1JJCHzlcK6lN3/yl+6TUHinDE6HUFlmnvkh/NDZ2M9049Ipn3mX85qu6akRiC1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
@@ -30456,7 +30018,6 @@
|
||||
"@tabler/icons": "^1.46.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@uiw/react-color": "^2.9.2",
|
||||
"@usebruno/common": "0.1.0",
|
||||
"@usebruno/graphql-docs": "0.1.0",
|
||||
"@usebruno/schema": "0.7.0",
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
"@tabler/icons": "^1.46.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@uiw/react-color": "^2.9.2",
|
||||
"@usebruno/common": "0.1.0",
|
||||
"@usebruno/graphql-docs": "0.1.0",
|
||||
"@usebruno/schema": "0.7.0",
|
||||
|
||||
24
packages/bruno-app/src/components/ColorBadge/index.js
Normal file
24
packages/bruno-app/src/components/ColorBadge/index.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
|
||||
const ColorBadge = ({ color, size = 10, showEmptyBorder = true }) => {
|
||||
const sizeValue = typeof size === 'string' ? size : `${size}px`;
|
||||
const { theme } = useTheme();
|
||||
|
||||
const showBorder = !color && showEmptyBorder;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex-shrink-0 rounded-full"
|
||||
style={{
|
||||
width: sizeValue,
|
||||
height: sizeValue,
|
||||
backgroundColor: color || 'transparent',
|
||||
border: showBorder ? '1px solid' : 'none',
|
||||
borderColor: showBorder ? theme.background.surface1 : 'transparent'
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColorBadge;
|
||||
@@ -0,0 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
164
packages/bruno-app/src/components/ColorPicker/index.js
Normal file
164
packages/bruno-app/src/components/ColorPicker/index.js
Normal file
@@ -0,0 +1,164 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { IconBan, IconBrush } from '@tabler/icons';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import ColorBadge from 'components/ColorBadge';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { parseToRgb, toColorString } from 'polished';
|
||||
import ColorRangePicker from 'components/ColorRange/index';
|
||||
|
||||
const PRESET_COLORS = [
|
||||
'#CE4F3B',
|
||||
'#2E8A54',
|
||||
'#346AB2',
|
||||
'#C77A0F',
|
||||
'#B83D7F',
|
||||
'#8D44B2'
|
||||
];
|
||||
|
||||
const COLOR_RANGE_SEQUENCE = ['#D85D43', '#F4BB74', '#61DCB1', '#7EBDF2', '#D48ADE', '#B491E5'];
|
||||
|
||||
/**
|
||||
* @param {string} hex
|
||||
* @returns {red:string,green:string,blue:string}
|
||||
*/
|
||||
const hexToRgb = (hex) => {
|
||||
try {
|
||||
return parseToRgb(hex);
|
||||
} catch (err) {
|
||||
return { red: 0, green: 0, blue: 0 };
|
||||
}
|
||||
};
|
||||
|
||||
const rgbToHex = (r, g, b) => {
|
||||
return toColorString({ red: Math.round(r), green: Math.round(g), blue: Math.round(b) });
|
||||
};
|
||||
|
||||
const interpolateColor = (position) => {
|
||||
const numColors = COLOR_RANGE_SEQUENCE.length;
|
||||
const scaledPos = (position / 100) * (numColors - 1);
|
||||
const index = Math.floor(scaledPos);
|
||||
const fraction = scaledPos - index;
|
||||
|
||||
if (index >= numColors - 1) {
|
||||
return COLOR_RANGE_SEQUENCE[numColors - 1];
|
||||
}
|
||||
|
||||
const color1 = hexToRgb(COLOR_RANGE_SEQUENCE[index]);
|
||||
const color2 = hexToRgb(COLOR_RANGE_SEQUENCE[index + 1]);
|
||||
|
||||
const r = color1.red + (color2.red - color1.red) * fraction;
|
||||
const g = color1.green + (color2.green - color1.green) * fraction;
|
||||
const b = color1.blue + (color2.blue - color1.blue) * fraction;
|
||||
|
||||
return rgbToHex(r, g, b);
|
||||
};
|
||||
|
||||
const findClosestPosition = (hex) => {
|
||||
if (!hex) return 0;
|
||||
const target = hexToRgb(hex);
|
||||
let closestPos = 0;
|
||||
let minDistance = Infinity;
|
||||
|
||||
for (let pos = 0; pos <= 100; pos++) {
|
||||
const color = hexToRgb(interpolateColor(pos));
|
||||
const distance = Math.sqrt(
|
||||
Math.pow(target.red - color.red, 2) + Math.pow(target.green - color.green, 2) + Math.pow(target.blue - color.blue, 2)
|
||||
);
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
closestPos = pos;
|
||||
}
|
||||
}
|
||||
return closestPos;
|
||||
};
|
||||
|
||||
const ColorPickerIcon = ({ color }) => {
|
||||
if (color) {
|
||||
return <ColorBadge color={color} size={8} />;
|
||||
}
|
||||
return <IconBrush size={14} strokeWidth={1.5} className="opacity-70" />;
|
||||
};
|
||||
|
||||
const ColorPicker = ({ color, onChange, icon }) => {
|
||||
const [sliderPosition, setSliderPosition] = useState(() =>
|
||||
color && !PRESET_COLORS.includes(color) ? findClosestPosition(color) : 0
|
||||
);
|
||||
const [customColor, setCustomColor] = useState(() =>
|
||||
color && !PRESET_COLORS.includes(color) ? color : COLOR_RANGE_SEQUENCE[0]
|
||||
);
|
||||
const pendingColorRef = useRef(customColor);
|
||||
|
||||
const handleColorSelect = (selectedColor) => {
|
||||
onChange(selectedColor);
|
||||
};
|
||||
|
||||
const handleSliderChange = (e) => {
|
||||
const newPosition = parseInt(e.target.value, 10);
|
||||
setSliderPosition(newPosition);
|
||||
const newColor = interpolateColor(newPosition);
|
||||
setCustomColor(newColor);
|
||||
pendingColorRef.current = newColor;
|
||||
};
|
||||
|
||||
const handleSliderEnd = () => {
|
||||
onChange(pendingColorRef.current);
|
||||
};
|
||||
|
||||
const defaultIcon = (
|
||||
<div className="cursor-pointer flex items-center" title="Change color">
|
||||
<ColorPickerIcon color={color} />
|
||||
</div>
|
||||
);
|
||||
|
||||
const colorPickerContent = (
|
||||
<StyledWrapper>
|
||||
<div className="p-2">
|
||||
<div className="flex flex-wrap gap-1.5 justify-between items-center">
|
||||
<div
|
||||
className="w-5 h-5 cursor-pointer flex items-center justify-center transition-transform duration-100 hover:scale-110"
|
||||
onClick={() => handleColorSelect(null)}
|
||||
title="No color"
|
||||
>
|
||||
<IconBan size={20} strokeWidth={1.5} />
|
||||
</div>
|
||||
{PRESET_COLORS.map((presetColor, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`w-5 h-5 rounded cursor-pointer flex items-center justify-center transition-transform duration-100 hover:scale-110 border-2 border-transparent
|
||||
${color === presetColor ? 'border-solid !border-current' : ''}
|
||||
`}
|
||||
style={{ backgroundColor: presetColor }}
|
||||
onClick={() => handleColorSelect(presetColor)}
|
||||
title={presetColor}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 mt-2 pt-2">
|
||||
<div
|
||||
className="w-4 h-4 rounded-full flex-shrink-0 cursor-pointer"
|
||||
style={{ backgroundColor: customColor }}
|
||||
onClick={() => handleColorSelect(customColor)}
|
||||
title="Custom color"
|
||||
/>
|
||||
<ColorRangePicker
|
||||
className="flex-1"
|
||||
value={sliderPosition}
|
||||
onChange={handleSliderChange}
|
||||
onMouseUp={handleSliderEnd}
|
||||
selectedColor={customColor}
|
||||
colorRange={COLOR_RANGE_SEQUENCE}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown icon={icon || defaultIcon} placement="bottom-start">
|
||||
{colorPickerContent}
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColorPicker;
|
||||
@@ -0,0 +1,45 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
.hue-slider {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
height: 4px;
|
||||
border-radius: 2px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.hue-slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
background: ${(props) => props.color ?? props.theme.bg};
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
transition: transform 0.1s ease;
|
||||
}
|
||||
|
||||
.hue-slider::-webkit-slider-thumb:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.hue-slider::-moz-range-thumb {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
background: ${(props) => props.color ?? props.theme.bg};
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
transition: transform 0.1s ease;
|
||||
}
|
||||
|
||||
.hue-slider::-moz-range-thumb:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
23
packages/bruno-app/src/components/ColorRange/index.js
Normal file
23
packages/bruno-app/src/components/ColorRange/index.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const ColorRangePicker = ({ selectedColor, className, value, onChange, colorRange, ...props }) => {
|
||||
return (
|
||||
<StyledWrapper color={selectedColor}>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className={`hue-slider ${className}`}
|
||||
style={{
|
||||
background: `linear-gradient(to right, ${colorRange.join(',')})`
|
||||
}}
|
||||
title="Adjust color"
|
||||
{...props}
|
||||
/>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColorRangePicker;
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { IconPlus, IconDownload, IconSettings, IconDatabase } from '@tabler/icons';
|
||||
import { IconPlus, IconDownload, IconSettings } from '@tabler/icons';
|
||||
import ToolHint from 'components/ToolHint';
|
||||
import ColorBadge from 'components/ColorBadge';
|
||||
|
||||
const EnvironmentListContent = ({
|
||||
environments,
|
||||
@@ -38,7 +39,7 @@ const EnvironmentListContent = ({
|
||||
data-tooltip-content={env.name}
|
||||
data-tooltip-hidden={env.name?.length < 90}
|
||||
>
|
||||
<IconDatabase size={16} strokeWidth={1.5} color={env.color} />
|
||||
<ColorBadge color={env.color} size={8} showEmptyBorder={false} />
|
||||
<span className="max-w-100% truncate no-wrap">{env.name}</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -5,8 +5,8 @@ const Wrapper = styled.div`
|
||||
border-radius: ${(props) => props.theme.border.radius.base};
|
||||
padding: 0.25rem 0.3rem 0.25rem 0.5rem;
|
||||
user-select: none;
|
||||
background-color: ${(props) => props.color ? undefined : 'transparent'};
|
||||
border: 2px solid ${(props) => props.color ?? props.theme.dropdown.selectedColor};
|
||||
background-color: ${(props) => props.theme.app.collection.toolbar.environmentSelector.bg};
|
||||
border: 1px solid ${(props) => props.theme.app.collection.toolbar.environmentSelector.border};
|
||||
line-height: 1rem;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
@@ -15,11 +15,6 @@ const Wrapper = styled.div`
|
||||
background-color: ${(props) => props.theme.app.collection.toolbar.environmentSelector.hoverBg};
|
||||
}
|
||||
|
||||
.active-env-toolhint {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.caret {
|
||||
margin-left: 0.25rem;
|
||||
color: ${(props) => props.theme.app.collection.toolbar.environmentSelector.caret};
|
||||
@@ -29,18 +24,16 @@ const Wrapper = styled.div`
|
||||
|
||||
.env-icon {
|
||||
margin-right: 0.25rem;
|
||||
color: ${(props) => props.color ?? props.theme.dropdown.selectedColor};
|
||||
color: ${(props) => props.theme.app.collection.toolbar.environmentSelector.icon};
|
||||
}
|
||||
|
||||
.env-text {
|
||||
color: ${(props) => props.color ?? props.theme.dropdown.selectedColor};
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
color: ${(props) => props.theme.app.collection.toolbar.environmentSelector.text};
|
||||
display: block;
|
||||
}
|
||||
|
||||
.env-separator {
|
||||
color: ${(props) => props.theme.app.collection.toolbar.environmentSelector.separator};
|
||||
margin: 0 0.35rem;
|
||||
background-color: ${(props) => props.theme.app.collection.toolbar.environmentSelector.separator};
|
||||
}
|
||||
|
||||
.env-text-inactive {
|
||||
@@ -71,37 +64,6 @@ const Wrapper = styled.div`
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tippy-box .tippy-content {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.dropdown-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
column-gap: 0.35em;
|
||||
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.color ?? props.theme.dropdown.selectedColor} !important;
|
||||
}
|
||||
|
||||
&.no-environment {
|
||||
color: ${(props) => props.theme.dropdown.mutedText};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.configure-button {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
|
||||
@@ -13,6 +13,166 @@ import ImportEnvironmentModal from 'components/Environments/Common/ImportEnviron
|
||||
import CreateGlobalEnvironment from 'components/WorkspaceHome/WorkspaceEnvironments/CreateEnvironment';
|
||||
import ToolHint from 'components/ToolHint';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { transparentize, toColorString, parseToRgb } from 'polished';
|
||||
|
||||
const TABS = [
|
||||
{ id: 'collection', label: 'Collection', icon: <IconDatabase size={16} strokeWidth={1.5} /> },
|
||||
{ id: 'global', label: 'Global', icon: <IconWorld size={16} strokeWidth={1.5} /> }
|
||||
];
|
||||
|
||||
const EMPTY_STATE_DESCRIPTIONS = {
|
||||
collection: 'Create your first environment to begin working with your collection.',
|
||||
global: 'Create your first global environment to begin working across collections.'
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates background color with transparency for environment badges
|
||||
*/
|
||||
const getEnvBackgroundColor = (color) => (color ? transparentize(1 - 0.12, color) : 'transparent');
|
||||
|
||||
/**
|
||||
* Calculates the style for an environment badge section
|
||||
*/
|
||||
const getEnvBadgeStyle = (environment, position, hasOtherEnv) => {
|
||||
const color = environment?.color;
|
||||
const isLeft = position === 'left';
|
||||
|
||||
// Determine border radius based on position and whether other env exists
|
||||
let borderRadius = '0.3rem';
|
||||
if (hasOtherEnv) {
|
||||
borderRadius = isLeft ? '0.3rem 0 0 0.3rem' : '0 0.3rem 0.3rem 0';
|
||||
}
|
||||
|
||||
// Determine padding based on position
|
||||
const padding = isLeft
|
||||
? hasOtherEnv
|
||||
? '0.25rem 0.5rem 0.25rem 0.5rem'
|
||||
: '0.25rem 0.3rem 0.25rem 0.5rem'
|
||||
: '0.25rem 0.3rem 0.25rem 0.5rem';
|
||||
|
||||
return {
|
||||
backgroundColor: getEnvBackgroundColor(color),
|
||||
padding,
|
||||
borderRadius
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates dropdown width based on longest environment name
|
||||
*/
|
||||
const calculateDropdownWidth = (environments, globalEnvironments) => {
|
||||
const allEnvironments = [...environments, ...globalEnvironments];
|
||||
if (allEnvironments.length === 0) return 0;
|
||||
|
||||
const maxCharLength = Math.max(...allEnvironments.map((env) => env.name?.length || 0));
|
||||
// 8 pixels per character (rough estimate for average character width)
|
||||
return maxCharLength * 8;
|
||||
};
|
||||
|
||||
/**
|
||||
* Displays a single environment with icon, name, and optional color styling
|
||||
*/
|
||||
const EnvironmentBadge = ({ environment, icon: Icon }) => {
|
||||
if (!environment) return null;
|
||||
|
||||
const colorStyle = environment.color ? { color: environment.color } : {};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Icon size={14} strokeWidth={1.5} className="env-icon" style={colorStyle} />
|
||||
<ToolHint
|
||||
text={environment.name}
|
||||
toolhintId={`env-${environment.uid}`}
|
||||
place="bottom-start"
|
||||
delayShow={1000}
|
||||
hidden={environment.name?.length < 7}
|
||||
>
|
||||
<span className="env-text max-w-24 truncate overflow-hidden" style={colorStyle}>
|
||||
{environment.name}
|
||||
</span>
|
||||
</ToolHint>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Dropdown trigger component showing active environments
|
||||
*/
|
||||
const DropdownTrigger = forwardRef(({ collectionEnv, globalEnv }, ref) => {
|
||||
const hasAnyEnv = collectionEnv || globalEnv;
|
||||
|
||||
// Empty state - no environments selected
|
||||
if (!hasAnyEnv) {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="current-environment flex align-center justify-center cursor-pointer bg-transparent no-environments"
|
||||
data-testid="environment-selector-trigger"
|
||||
>
|
||||
<span className="env-text-inactive max-w-36 truncate no-wrap">No Environment</span>
|
||||
<IconCaretDown className="caret flex items-center justify-center" size={12} strokeWidth={2} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Only collection env selected - caret goes with collection env
|
||||
if (collectionEnv && !globalEnv) {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="current-environment flex align-center justify-center cursor-pointer bg-transparent"
|
||||
style={{ padding: 0 }}
|
||||
data-testid="environment-selector-trigger"
|
||||
>
|
||||
<div className="flex items-center" style={getEnvBadgeStyle(collectionEnv, 'left', false)}>
|
||||
<EnvironmentBadge environment={collectionEnv} icon={IconDatabase} />
|
||||
<IconCaretDown className="caret flex items-center justify-center" size={12} strokeWidth={2} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Only global env selected - caret goes with global env
|
||||
if (!collectionEnv && globalEnv) {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="current-environment flex align-center justify-center cursor-pointer bg-transparent"
|
||||
style={{ padding: 0 }}
|
||||
data-testid="environment-selector-trigger"
|
||||
>
|
||||
<div className="flex items-center" style={getEnvBadgeStyle(globalEnv, 'right', false)}>
|
||||
<EnvironmentBadge environment={globalEnv} icon={IconWorld} />
|
||||
<IconCaretDown className="caret flex items-center justify-center" size={12} strokeWidth={2} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Both environments selected
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="current-environment flex align-center justify-center cursor-pointer bg-transparent"
|
||||
style={{ padding: 0 }}
|
||||
data-testid="environment-selector-trigger"
|
||||
>
|
||||
{/* Collection Environment Section */}
|
||||
<div className="flex items-center" style={getEnvBadgeStyle(collectionEnv, 'left', true)}>
|
||||
<EnvironmentBadge environment={collectionEnv} icon={IconDatabase} />
|
||||
</div>
|
||||
|
||||
{/* Separator */}
|
||||
<div className="env-separator" style={{ width: '1px', alignSelf: 'stretch' }} />
|
||||
|
||||
{/* Global Environment Section + Caret */}
|
||||
<div className="flex items-center" style={getEnvBadgeStyle(globalEnv, 'right', true)}>
|
||||
<EnvironmentBadge environment={globalEnv} icon={IconWorld} />
|
||||
<IconCaretDown className="caret flex items-center justify-center" size={12} strokeWidth={2} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const EnvironmentSelector = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -35,160 +195,82 @@ const EnvironmentSelector = ({ collection }) => {
|
||||
? find(environments, (e) => e.uid === activeEnvironmentUid)
|
||||
: null;
|
||||
|
||||
const tabs = [
|
||||
{ id: 'collection', label: 'Collection', icon: <IconDatabase size={16} strokeWidth={1.5} /> },
|
||||
{ id: 'global', label: 'Global', icon: <IconWorld size={16} strokeWidth={1.5} /> }
|
||||
];
|
||||
const dropdownWidth = useMemo(
|
||||
() => calculateDropdownWidth(environments, globalEnvironments),
|
||||
[environments, globalEnvironments]
|
||||
);
|
||||
|
||||
const onDropdownCreate = (ref) => {
|
||||
dropdownTippyRef.current = ref;
|
||||
};
|
||||
const description = EMPTY_STATE_DESCRIPTIONS[activeTab];
|
||||
|
||||
// Get description based on active tab
|
||||
const description
|
||||
= activeTab === 'collection'
|
||||
? 'Create your first environment to begin working with your collection.'
|
||||
: 'Create your first global environment to begin working across collections.';
|
||||
const hideDropdown = () => dropdownTippyRef.current?.hide();
|
||||
|
||||
// Environment selection handler
|
||||
const handleEnvironmentSelect = (environment) => {
|
||||
const action
|
||||
= activeTab === 'collection'
|
||||
? selectEnvironment(environment ? environment.uid : null, collection.uid)
|
||||
: selectGlobalEnvironment({ environmentUid: environment ? environment.uid : null });
|
||||
? selectEnvironment(environment?.uid || null, collection.uid)
|
||||
: selectGlobalEnvironment({ environmentUid: environment?.uid || null });
|
||||
|
||||
dispatch(action)
|
||||
.then(() => {
|
||||
if (environment) {
|
||||
toast.success(`Environment changed to ${environment.name}`);
|
||||
} else {
|
||||
toast.success('No Environments are active now');
|
||||
}
|
||||
dropdownTippyRef.current.hide();
|
||||
toast.success(environment ? `Environment changed to ${environment.name}` : 'No Environments are active now');
|
||||
hideDropdown();
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(() => {
|
||||
toast.error('An error occurred while selecting the environment');
|
||||
});
|
||||
};
|
||||
|
||||
// Settings handler - opens environment settings tab
|
||||
const handleSettingsClick = () => {
|
||||
if (activeTab === 'collection') {
|
||||
dispatch(
|
||||
addTab({
|
||||
uid: `${collection.uid}-environment-settings`,
|
||||
collectionUid: collection.uid,
|
||||
type: 'environment-settings'
|
||||
})
|
||||
);
|
||||
} else {
|
||||
dispatch(
|
||||
addTab({
|
||||
uid: `${collection.uid}-global-environment-settings`,
|
||||
collectionUid: collection.uid,
|
||||
type: 'global-environment-settings'
|
||||
})
|
||||
);
|
||||
}
|
||||
dropdownTippyRef.current.hide();
|
||||
const isCollection = activeTab === 'collection';
|
||||
dispatch(
|
||||
addTab({
|
||||
uid: `${collection.uid}-${isCollection ? 'environment' : 'global-environment'}-settings`,
|
||||
collectionUid: collection.uid,
|
||||
type: isCollection ? 'environment-settings' : 'global-environment-settings'
|
||||
})
|
||||
);
|
||||
hideDropdown();
|
||||
};
|
||||
|
||||
// Create handler
|
||||
const handleCreateClick = () => {
|
||||
if (activeTab === 'collection') {
|
||||
setShowCreateCollectionModal(true);
|
||||
} else {
|
||||
setShowCreateGlobalModal(true);
|
||||
}
|
||||
dropdownTippyRef.current.hide();
|
||||
hideDropdown();
|
||||
};
|
||||
|
||||
// Import handler
|
||||
const handleImportClick = () => {
|
||||
if (activeTab === 'collection') {
|
||||
setShowImportCollectionModal(true);
|
||||
} else {
|
||||
setShowImportGlobalModal(true);
|
||||
}
|
||||
dropdownTippyRef.current.hide();
|
||||
hideDropdown();
|
||||
};
|
||||
|
||||
// Calculate dropdown width based on the longest environment name.
|
||||
// To prevent resizing while switching between collection and global environments.
|
||||
const dropdownWidth = useMemo(() => {
|
||||
const allEnvironments = [...environments, ...globalEnvironments];
|
||||
if (allEnvironments.length === 0) return 0;
|
||||
|
||||
const maxCharLength = Math.max(...allEnvironments.map((env) => env.name?.length || 0));
|
||||
// 8 pixels per character: This is a rough estimate for the average character width in most fonts
|
||||
// (monospace fonts are typically 8-10px, proportional fonts vary but 8px is a safe average)
|
||||
return maxCharLength * 8;
|
||||
}, [environments, globalEnvironments]);
|
||||
|
||||
// Create icon component for dropdown trigger
|
||||
const Icon = forwardRef((props, ref) => {
|
||||
const hasAnyEnv = activeGlobalEnvironment || activeCollectionEnvironment;
|
||||
|
||||
const displayContent = hasAnyEnv ? (
|
||||
<>
|
||||
{activeCollectionEnvironment && (
|
||||
<>
|
||||
<div className="flex items-center">
|
||||
<ToolHint
|
||||
className="active-env-toolhint"
|
||||
text={activeCollectionEnvironment.name}
|
||||
toolhintId={`collection-env-${activeCollectionEnvironment.uid}`}
|
||||
place="bottom-start"
|
||||
delayShow={1000}
|
||||
hidden={activeCollectionEnvironment.name?.length < 7}
|
||||
>
|
||||
<IconDatabase size={14} strokeWidth={1.5} className="env-icon" />
|
||||
<span className="env-text max-w-24 truncate overflow-hidden">{activeCollectionEnvironment.name}</span>
|
||||
</ToolHint>
|
||||
</div>
|
||||
{activeGlobalEnvironment && <span className="env-separator">|</span>}
|
||||
</>
|
||||
)}
|
||||
{activeGlobalEnvironment && (
|
||||
<div className="flex items-center">
|
||||
<IconWorld size={14} strokeWidth={1.5} className="env-icon" />
|
||||
<ToolHint
|
||||
text={activeGlobalEnvironment.name}
|
||||
toolhintId={`global-env-${activeGlobalEnvironment.uid}`}
|
||||
place="bottom-start"
|
||||
delayShow={1000}
|
||||
hidden={activeGlobalEnvironment.name?.length < 7}
|
||||
>
|
||||
<span className="env-text max-w-24 truncate overflow-hidden">{activeGlobalEnvironment.name}</span>
|
||||
</ToolHint>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<span className="env-text-inactive max-w-36 truncate no-wrap">No Environment</span>
|
||||
const openEnvironmentSettingsTab = (type) => {
|
||||
dispatch(
|
||||
addTab({
|
||||
uid: `${collection.uid}-${type}-settings`,
|
||||
collectionUid: collection.uid,
|
||||
type: `${type}-settings`
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={`current-environment flex align-center justify-center cursor-pointer bg-transparent ${
|
||||
!hasAnyEnv ? 'no-environments' : ''
|
||||
}`}
|
||||
data-testid="environment-selector-trigger"
|
||||
>
|
||||
{displayContent}
|
||||
<IconCaretDown className="caret flex items-center justify-center" size={12} strokeWidth={2} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper color={activeCollectionEnvironment?.color} width={dropdownWidth}>
|
||||
<StyledWrapper width={dropdownWidth}>
|
||||
<div className="environment-selector flex align-center cursor-pointer">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
||||
<Dropdown
|
||||
onCreate={(ref) => (dropdownTippyRef.current = ref)}
|
||||
icon={<DropdownTrigger collectionEnv={activeCollectionEnvironment} globalEnv={activeGlobalEnvironment} />}
|
||||
placement="bottom-end"
|
||||
>
|
||||
{/* Tab Headers */}
|
||||
<div className="tab-header flex pt-3 pb-2 px-3">
|
||||
{tabs.map((tab) => (
|
||||
{TABS.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
className={`tab-button whitespace-nowrap pb-[0.375rem] border-b-[0.125rem] bg-transparent flex align-center cursor-pointer transition-all duration-200 mr-[1.25rem] ${
|
||||
@@ -223,15 +305,7 @@ const EnvironmentSelector = ({ collection }) => {
|
||||
{showCreateGlobalModal && (
|
||||
<CreateGlobalEnvironment
|
||||
onClose={() => setShowCreateGlobalModal(false)}
|
||||
onEnvironmentCreated={() => {
|
||||
dispatch(
|
||||
addTab({
|
||||
uid: `${collection.uid}-global-environment-settings`,
|
||||
collectionUid: collection.uid,
|
||||
type: 'global-environment-settings'
|
||||
})
|
||||
);
|
||||
}}
|
||||
onEnvironmentCreated={() => openEnvironmentSettingsTab('global-environment')}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -239,15 +313,7 @@ const EnvironmentSelector = ({ collection }) => {
|
||||
<ImportEnvironmentModal
|
||||
type="global"
|
||||
onClose={() => setShowImportGlobalModal(false)}
|
||||
onEnvironmentCreated={() => {
|
||||
dispatch(
|
||||
addTab({
|
||||
uid: `${collection.uid}-global-environment-settings`,
|
||||
collectionUid: collection.uid,
|
||||
type: 'global-environment-settings'
|
||||
})
|
||||
);
|
||||
}}
|
||||
onEnvironmentCreated={() => openEnvironmentSettingsTab('global-environment')}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -255,15 +321,7 @@ const EnvironmentSelector = ({ collection }) => {
|
||||
<CreateEnvironment
|
||||
collection={collection}
|
||||
onClose={() => setShowCreateCollectionModal(false)}
|
||||
onEnvironmentCreated={() => {
|
||||
dispatch(
|
||||
addTab({
|
||||
uid: `${collection.uid}-environment-settings`,
|
||||
collectionUid: collection.uid,
|
||||
type: 'environment-settings'
|
||||
})
|
||||
);
|
||||
}}
|
||||
onEnvironmentCreated={() => openEnvironmentSettingsTab('environment')}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -272,15 +330,7 @@ const EnvironmentSelector = ({ collection }) => {
|
||||
type="collection"
|
||||
collection={collection}
|
||||
onClose={() => setShowImportCollectionModal(false)}
|
||||
onEnvironmentCreated={() => {
|
||||
dispatch(
|
||||
addTab({
|
||||
uid: `${collection.uid}-environment-settings`,
|
||||
collectionUid: collection.uid,
|
||||
type: 'environment-settings'
|
||||
})
|
||||
);
|
||||
}}
|
||||
onEnvironmentCreated={() => openEnvironmentSettingsTab('environment')}
|
||||
/>
|
||||
)}
|
||||
</StyledWrapper>
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { saveEnvironmentColor } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { Circle } from '@uiw/react-color';
|
||||
|
||||
const EnvironmentColor = ({ environment, collectionUid }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onColorChange = useCallback(
|
||||
(color) => {
|
||||
if (color == environment.color) return;
|
||||
dispatch(saveEnvironmentColor(color, environment.uid, collectionUid))
|
||||
.then(() => toast.success('Environment color changed successfully'))
|
||||
.catch(() => toast.error('An error occurred while changing the environment color'));
|
||||
},
|
||||
[dispatch, environment.uid, environment.color, collectionUid]
|
||||
);
|
||||
|
||||
return (
|
||||
<Circle
|
||||
id="environment-color"
|
||||
style={{ gap: 3 }}
|
||||
pointProps={{ style: { width: 14, height: 14, borderRadius: 10 } }}
|
||||
colors={['#000000','#9c27b0','#3f51b5','#03a9f4','#009688','#8bc34a','#ffeb3b','#ff9800','#ff5722','#795548','#607d8b']}
|
||||
color={environment.color}
|
||||
onChange={(color) => onColorChange(color.hex)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default EnvironmentColor;
|
||||
@@ -1,14 +1,13 @@
|
||||
import { IconCopy, IconEdit, IconTrash, IconCheck, IconX } from '@tabler/icons';
|
||||
import { useState, useRef } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { renameEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { renameEnvironment, updateEnvironmentColor } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { validateName, validateNameError } from 'utils/common/regex';
|
||||
import toast from 'react-hot-toast';
|
||||
import CopyEnvironment from 'components/Environments/EnvironmentSettings/CopyEnvironment';
|
||||
import DeleteEnvironment from 'components/Environments/EnvironmentSettings/DeleteEnvironment';
|
||||
import EnvironmentVariables from './EnvironmentVariables';
|
||||
import EnvironmentColor from '../EnvironmentDetails/EnvironmentColor';
|
||||
import ToolHint from 'components/ToolHint/index';
|
||||
import ColorPicker from 'components/ColorPicker';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const EnvironmentDetails = ({ environment, setIsModified, collection }) => {
|
||||
@@ -113,6 +112,16 @@ const EnvironmentDetails = ({ environment, setIsModified, collection }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleColorChange = (color) => {
|
||||
dispatch(updateEnvironmentColor(environment.uid, color, collection.uid))
|
||||
.then(() => {
|
||||
toast.success('Environment color updated!');
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error('An error occurred while updating the environment color');
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
{openDeleteModal && (
|
||||
@@ -121,6 +130,7 @@ const EnvironmentDetails = ({ environment, setIsModified, collection }) => {
|
||||
{openCopyModal && (
|
||||
<CopyEnvironment onClose={() => setOpenCopyModal(false)} environment={environment} collection={collection} />
|
||||
)}
|
||||
|
||||
<div className="header">
|
||||
<div className={`title-container ${isRenaming ? 'renaming' : ''}`}>
|
||||
{isRenaming ? (
|
||||
@@ -158,7 +168,10 @@ const EnvironmentDetails = ({ environment, setIsModified, collection }) => {
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<h2 className="title">{environment.name}</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="title">{environment.name}</h2>
|
||||
<ColorPicker color={environment.color} onChange={handleColorChange} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{nameError && isRenaming && <div className="title-error">{nameError}</div>}
|
||||
@@ -175,8 +188,9 @@ const EnvironmentDetails = ({ environment, setIsModified, collection }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<EnvironmentColor environment={environment} collectionUid={collection.uid} />
|
||||
<EnvironmentVariables environment={environment} collection={collection} setIsModified={setIsModified} onClose={onClose} />
|
||||
<div className="content">
|
||||
<EnvironmentVariables environment={environment} setIsModified={setIsModified} collection={collection} />
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -110,6 +110,7 @@ const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 4px 8px;
|
||||
margin-bottom: 1px;
|
||||
font-size: 13px;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { findEnvironmentInCollection, findItem } from 'utils/collections';
|
||||
import usePrevious from 'hooks/usePrevious';
|
||||
import EnvironmentDetails from './EnvironmentDetails';
|
||||
import CreateEnvironment from 'components/Environments/EnvironmentSettings/CreateEnvironment';
|
||||
@@ -7,6 +6,7 @@ import { IconDownload, IconUpload, IconSearch, IconPlus, IconCheck, IconX } from
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import ConfirmSwitchEnv from 'components/WorkspaceHome/WorkspaceEnvironments/EnvironmentList/ConfirmSwitchEnv';
|
||||
import ImportEnvironmentModal from 'components/Environments/Common/ImportEnvironmentModal';
|
||||
import ColorBadge from 'components/ColorBadge';
|
||||
import { isEqual } from 'lodash';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { addEnvironment, renameEnvironment, selectEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||
@@ -25,10 +25,6 @@ const EnvironmentList = ({
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
|
||||
const EnvironmentList = ({ collection, isModified, setIsModified, onClose, setShowExportModal }) => {
|
||||
const { environments, activeEnvironmentUid } = collection;
|
||||
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
|
||||
const [openCreateModal, setOpenCreateModal] = useState(false);
|
||||
const [openImportModal, setOpenImportModal] = useState(false);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
@@ -43,7 +39,7 @@ const EnvironmentList = ({ collection, isModified, setIsModified, onClose, setSh
|
||||
const [switchEnvConfirmClose, setSwitchEnvConfirmClose] = useState(false);
|
||||
const [originalEnvironmentVariables, setOriginalEnvironmentVariables] = useState([]);
|
||||
|
||||
const envUids = environments?.map((env) => env.uid) ?? [];
|
||||
const envUids = environments ? environments.map((env) => env.uid) : [];
|
||||
const prevEnvUids = usePrevious(envUids);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -68,12 +64,10 @@ const EnvironmentList = ({ collection, isModified, setIsModified, onClose, setSh
|
||||
if (hasSelectedEnvironmentChanged || selectedEnvironment.uid !== _selectedEnvironment?.uid) {
|
||||
setSelectedEnvironment(_selectedEnvironment);
|
||||
}
|
||||
setOriginalEnvironmentVariables(selectedEnvironment?.variables||[]);
|
||||
setSelectedEnvironment(findItem(environments, selectedEnvironment.uid));
|
||||
setOriginalEnvironmentVariables(_selectedEnvironment?.variables || []);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const environment = environments?.find((env) => env.uid === activeEnvironmentUid) || environments?.[0];
|
||||
|
||||
setSelectedEnvironment(environment);
|
||||
@@ -81,21 +75,15 @@ const EnvironmentList = ({ collection, isModified, setIsModified, onClose, setSh
|
||||
}, [environments, activeEnvironmentUid, selectedEnvironment]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEnvironment) {
|
||||
setSelectedEnvironment(findEnvironmentInCollection(collection, selectedEnvironment.uid));
|
||||
}
|
||||
}, [environments]);
|
||||
|
||||
useEffect(() => {
|
||||
if (prevEnvUids?.length && envUids.length > prevEnvUids.length) {
|
||||
if (prevEnvUids && prevEnvUids.length && envUids.length > prevEnvUids.length) {
|
||||
const newEnv = environments.find((env) => !prevEnvUids.includes(env.uid));
|
||||
if (newEnv) {
|
||||
setSelectedEnvironment(newEnv);
|
||||
}
|
||||
}
|
||||
|
||||
if (prevEnvUids?.length && envUids.length < prevEnvUids.length) {
|
||||
setSelectedEnvironment(environments?.length ? environments[0] : null);
|
||||
if (prevEnvUids && prevEnvUids.length && envUids.length < prevEnvUids.length) {
|
||||
setSelectedEnvironment(environments && environments.length ? environments[0] : null);
|
||||
}
|
||||
}, [envUids, environments, prevEnvUids]);
|
||||
|
||||
@@ -380,6 +368,7 @@ const EnvironmentList = ({ collection, isModified, setIsModified, onClose, setSh
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<ColorBadge color={env.color} size={8} />
|
||||
<span className="environment-name">{env.name}</span>
|
||||
<div className="environment-actions">
|
||||
{activeEnvironmentUid === env.uid ? (
|
||||
|
||||
@@ -71,8 +71,9 @@ const Wrapper = styled.div`
|
||||
|
||||
&:not(.active) {
|
||||
background: ${(props) => props.theme.requestTabs.bg};
|
||||
border-bottom: 3px solid ${(props) => props.color ?? "transparent"};
|
||||
border-color: transparent;
|
||||
border-radius: ${(props) => props.theme.border.radius.base};
|
||||
|
||||
}
|
||||
|
||||
&:nth-last-child(1) {
|
||||
@@ -112,7 +113,7 @@ const Wrapper = styled.div`
|
||||
&.active {
|
||||
background: ${(props) => props.theme.bg || '#ffffff'};
|
||||
border: 1px solid ${(props) => props.theme.requestTabs.bottomBorder};
|
||||
border-bottom-color: ${(props) => props.color ?? props.theme.bg ?? '#ffffff'};
|
||||
border-bottom-color: ${(props) => props.theme.bg || '#ffffff'};
|
||||
border-radius: 8px 8px 0 0;
|
||||
z-index: 1;
|
||||
margin-bottom: -2px;
|
||||
|
||||
@@ -6,7 +6,6 @@ import { IconChevronRight, IconChevronLeft } from '@tabler/icons';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { focusTab, reorderTabs } from 'providers/ReduxStore/slices/tabs';
|
||||
import NewRequest from 'components/Sidebar/NewRequest';
|
||||
import { findEnvironmentInCollection } from 'utils/collections';
|
||||
import CollectionToolBar from './CollectionToolBar';
|
||||
import RequestTab from './RequestTab';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
@@ -86,17 +85,6 @@ const RequestTabs = () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const activeTab = find(tabs, (t) => t.uid === activeTabUid);
|
||||
if (!activeTab) {
|
||||
return <StyledWrapper>Something went wrong!</StyledWrapper>;
|
||||
}
|
||||
|
||||
const activeCollection = find(collections, (c) => c.uid === activeTab.collectionUid);
|
||||
const activeEnvironment = activeCollection
|
||||
? findEnvironmentInCollection(activeCollection, activeCollection.activeEnvironmentUid)
|
||||
: null;
|
||||
const collectionRequestTabs = filter(tabs, (t) => t.collectionUid === activeTab.collectionUid);
|
||||
|
||||
const effectiveSidebarWidth = sidebarCollapsed ? 0 : leftSidebarWidth;
|
||||
const maxTablistWidth = screenWidth - effectiveSidebarWidth - 150;
|
||||
|
||||
@@ -114,14 +102,9 @@ const RequestTabs = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const getRootClassname = () => {
|
||||
return classnames({
|
||||
'has-chevrons': showChevrons
|
||||
});
|
||||
};
|
||||
// Todo: Must support ephemeral requests
|
||||
return (
|
||||
<StyledWrapper color={activeEnvironment?.color} className={getRootClassname()}>
|
||||
<StyledWrapper>
|
||||
{newRequestModalOpen && (
|
||||
<NewRequest collectionUid={activeCollection?.uid} onClose={() => setNewRequestModalOpen(false)} />
|
||||
)}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { IconCopy, IconEdit, IconTrash, IconCheck, IconX } from '@tabler/icons';
|
||||
import { useState, useRef } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { renameGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
|
||||
import { renameGlobalEnvironment, updateGlobalEnvironmentColor } from 'providers/ReduxStore/slices/global-environments';
|
||||
import { validateName, validateNameError } from 'utils/common/regex';
|
||||
import toast from 'react-hot-toast';
|
||||
import CopyEnvironment from '../../CopyEnvironment';
|
||||
import DeleteEnvironment from '../../DeleteEnvironment';
|
||||
import EnvironmentVariables from './EnvironmentVariables';
|
||||
import ColorPicker from 'components/ColorPicker';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const EnvironmentDetails = ({ environment, setIsModified, collection }) => {
|
||||
@@ -110,6 +111,16 @@ const EnvironmentDetails = ({ environment, setIsModified, collection }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleColorChange = (color) => {
|
||||
dispatch(updateGlobalEnvironmentColor(environment.uid, color))
|
||||
.then(() => {
|
||||
toast.success('Environment color updated!');
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error('An error occurred while updating the environment color');
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
{openDeleteModal && (
|
||||
@@ -159,7 +170,10 @@ const EnvironmentDetails = ({ environment, setIsModified, collection }) => {
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<h2 className="title">{environment.name}</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="title">{environment.name}</h2>
|
||||
<ColorPicker color={environment.color} onChange={handleColorChange} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{nameError && isRenaming && <div className="title-error">{nameError}</div>}
|
||||
|
||||
@@ -110,6 +110,7 @@ const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 4px 8px;
|
||||
margin-bottom: 1px;
|
||||
font-size: 13px;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { IconDownload, IconUpload, IconSearch, IconPlus, IconCheck, IconX } from
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import ConfirmSwitchEnv from './ConfirmSwitchEnv';
|
||||
import ImportEnvironmentModal from 'components/Environments/Common/ImportEnvironmentModal';
|
||||
import ColorBadge from 'components/ColorBadge';
|
||||
import { isEqual } from 'lodash';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { addGlobalEnvironment, renameGlobalEnvironment, selectGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
|
||||
@@ -357,6 +358,7 @@ const EnvironmentList = ({ environments, activeEnvironmentUid, selectedEnvironme
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<ColorBadge color={env.color} size={8} />
|
||||
<span className="environment-name">{env.name}</span>
|
||||
<div className="environment-actions">
|
||||
{activeEnvironmentUid === env.uid ? (
|
||||
|
||||
@@ -36,7 +36,6 @@ import {
|
||||
sortCollections as _sortCollections,
|
||||
updateCollectionMountStatus,
|
||||
moveCollection,
|
||||
saveEnvironmentColor as _saveEnvironmentColor,
|
||||
workspaceEnvUpdateEvent,
|
||||
requestCancelled,
|
||||
resetRunResults,
|
||||
@@ -50,6 +49,7 @@ import {
|
||||
updateActiveConnections,
|
||||
saveRequest as _saveRequest,
|
||||
saveEnvironment as _saveEnvironment,
|
||||
updateEnvironmentColor as _updateEnvironmentColor,
|
||||
saveCollectionDraft,
|
||||
saveFolderDraft,
|
||||
addVar,
|
||||
@@ -1931,6 +1931,31 @@ export const saveEnvironment = (variables, environmentUid, collectionUid) => (di
|
||||
});
|
||||
};
|
||||
|
||||
export const updateEnvironmentColor = (environmentUid, color, collectionUid) => (dispatch, getState) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const state = getState();
|
||||
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||
if (!collection) {
|
||||
return reject(new Error('Collection not found'));
|
||||
}
|
||||
|
||||
const collectionCopy = cloneDeep(collection);
|
||||
const environment = findEnvironmentInCollection(collectionCopy, environmentUid);
|
||||
if (!environment) {
|
||||
return reject(new Error('Environment not found'));
|
||||
}
|
||||
|
||||
environment.color = color;
|
||||
const { ipcRenderer } = window;
|
||||
ipcRenderer.invoke('renderer:update-environment-color', collection.pathname, environment.name, color)
|
||||
.then(() => {
|
||||
dispatch(_updateEnvironmentColor({ environmentUid, color, collectionUid }));
|
||||
resolve();
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update a variable value directly in the file without affecting draft state
|
||||
* @param {string} pathname - File path
|
||||
@@ -2253,27 +2278,6 @@ export const mergeAndPersistEnvironment
|
||||
});
|
||||
};
|
||||
|
||||
export const saveEnvironmentColor = (color, environmentUid, collectionUid) => (dispatch, getState) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const collection =
|
||||
findCollectionByUid(getState().collections.collections, collectionUid) ??
|
||||
reject(new Error('Collection not found'));
|
||||
const environment =
|
||||
findEnvironmentInCollection(collection, environmentUid) ?? reject(new Error('Environment not found'));
|
||||
const updatedEnvironment = { ...environment, color: color };
|
||||
|
||||
const { ipcRenderer } = window;
|
||||
environmentSchema
|
||||
.validate(updatedEnvironment)
|
||||
// save to file
|
||||
.then(() => ipcRenderer.invoke('renderer:save-environment', collection.pathname, updatedEnvironment))
|
||||
// update store
|
||||
.then(() => dispatch(_saveEnvironmentColor({ color, environmentUid, collectionUid })))
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
export const selectEnvironment = (environmentUid, collectionUid) => (dispatch, getState) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const state = getState();
|
||||
|
||||
@@ -258,17 +258,6 @@ export const collectionsSlice = createSlice({
|
||||
|
||||
if (environment) {
|
||||
environment.variables = variables;
|
||||
environment.color = color;
|
||||
}
|
||||
}
|
||||
},
|
||||
saveEnvironmentColor: (state, action) => {
|
||||
const { color, environmentUid, collectionUid } = action.payload;
|
||||
const collection = findCollectionByUid(state.collections, collectionUid);
|
||||
if (collection) {
|
||||
const environment = findEnvironmentInCollection(collection, environmentUid);
|
||||
if (environment) {
|
||||
environment.color = color;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -288,6 +277,18 @@ export const collectionsSlice = createSlice({
|
||||
}
|
||||
}
|
||||
},
|
||||
updateEnvironmentColor: (state, action) => {
|
||||
const { environmentUid, color, collectionUid } = action.payload;
|
||||
const collection = findCollectionByUid(state.collections, collectionUid);
|
||||
|
||||
if (collection) {
|
||||
const environment = findEnvironmentInCollection(collection, environmentUid);
|
||||
|
||||
if (environment) {
|
||||
environment.color = color;
|
||||
}
|
||||
}
|
||||
},
|
||||
newItem: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
|
||||
@@ -3475,8 +3476,8 @@ export const {
|
||||
updatedFolderSettingsSelectedTab,
|
||||
collectionUnlinkEnvFileEvent,
|
||||
saveEnvironment,
|
||||
saveEnvironmentColor,
|
||||
selectEnvironment,
|
||||
updateEnvironmentColor,
|
||||
newItem,
|
||||
deleteItem,
|
||||
renameItem,
|
||||
|
||||
@@ -81,6 +81,12 @@ export const globalEnvironmentsSlice = createSlice({
|
||||
},
|
||||
clearGlobalEnvironmentDraft: (state) => {
|
||||
state.globalEnvironmentDraft = null;
|
||||
},
|
||||
_updateGlobalEnvironmentColor: (state, action) => {
|
||||
const { environmentUid, color } = action.payload;
|
||||
if (environmentUid) {
|
||||
state.globalEnvironments = state.globalEnvironments.map((env) => env?.uid == environmentUid ? { ...env, color } : env);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -93,6 +99,7 @@ export const {
|
||||
_copyGlobalEnvironment,
|
||||
_selectGlobalEnvironment,
|
||||
_deleteGlobalEnvironment,
|
||||
_updateGlobalEnvironmentColor,
|
||||
setGlobalEnvironmentDraft,
|
||||
clearGlobalEnvironmentDraft
|
||||
} = globalEnvironmentsSlice.actions;
|
||||
@@ -303,4 +310,16 @@ export const globalEnvironmentsUpdateEvent = ({ globalEnvironmentVariables }) =>
|
||||
});
|
||||
};
|
||||
|
||||
export const updateGlobalEnvironmentColor = (environmentUid, color) => (dispatch, getState) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { ipcRenderer } = window;
|
||||
const state = getState();
|
||||
const { workspaceUid, workspacePath } = getWorkspaceContext(state);
|
||||
ipcRenderer.invoke('renderer:update-global-environment-color', { environmentUid, color, workspaceUid, workspacePath })
|
||||
.then(() => dispatch(_updateGlobalEnvironmentColor({ environmentUid, color })))
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
export default globalEnvironmentsSlice.reducer;
|
||||
|
||||
@@ -620,6 +620,28 @@ const registerRendererEventHandlers = (mainWindow, watcher) => {
|
||||
}
|
||||
});
|
||||
|
||||
// update environment color
|
||||
ipcMain.handle('renderer:update-environment-color', async (event, collectionPathname, environmentName, color) => {
|
||||
try {
|
||||
const format = getCollectionFormat(collectionPathname);
|
||||
const envDirPath = path.join(collectionPathname, 'environments');
|
||||
const envFilePath = path.join(envDirPath, `${environmentName}.${format}`);
|
||||
|
||||
if (!fs.existsSync(envFilePath)) {
|
||||
throw new Error(`environment: ${envFilePath} does not exist`);
|
||||
}
|
||||
|
||||
// Read, update color, and write back to file
|
||||
const fileContent = fs.readFileSync(envFilePath, 'utf8');
|
||||
const environment = parseEnvironment(fileContent, { format });
|
||||
environment.color = color;
|
||||
const updatedContent = stringifyEnvironment(environment, { format });
|
||||
fs.writeFileSync(envFilePath, updatedContent, 'utf8');
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Generic environment export handler
|
||||
ipcMain.handle('renderer:export-environment', async (event, { environments, environmentType, filePath, exportFormat = 'folder' }) => {
|
||||
try {
|
||||
|
||||
@@ -99,6 +99,19 @@ const registerGlobalEnvironmentsIpc = (mainWindow, workspaceEnvironmentsManager)
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('renderer:update-global-environment-color', async (event, { environmentUid, color, workspacePath }) => {
|
||||
try {
|
||||
if (workspacePath && workspaceEnvironmentsManager) {
|
||||
return await workspaceEnvironmentsManager.updateGlobalEnvironmentColorByPath(workspacePath, { environmentUid, color });
|
||||
}
|
||||
|
||||
globalEnvironmentsStore.updateGlobalEnvironmentColor({ environmentUid, color });
|
||||
} catch (error) {
|
||||
console.error('Error in renderer:update-global-environment-color:', error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = registerGlobalEnvironmentsIpc;
|
||||
|
||||
@@ -162,6 +162,15 @@ class GlobalEnvironmentsStore {
|
||||
}
|
||||
this.setGlobalEnvironments(globalEnvironments);
|
||||
}
|
||||
|
||||
updateGlobalEnvironmentColor({ environmentUid, color }) {
|
||||
let globalEnvironments = this.getGlobalEnvironments();
|
||||
const environment = globalEnvironments.find((env) => env?.uid == environmentUid);
|
||||
if (environment) {
|
||||
environment.color = color;
|
||||
}
|
||||
this.setGlobalEnvironments(globalEnvironments);
|
||||
}
|
||||
}
|
||||
|
||||
const globalEnvironmentsStore = new GlobalEnvironmentsStore();
|
||||
|
||||
@@ -325,6 +325,30 @@ class GlobalEnvironmentsManager {
|
||||
}
|
||||
}
|
||||
|
||||
async updateGlobalEnvironmentColor(workspacePath, environmentUid, color) {
|
||||
try {
|
||||
if (!workspacePath) {
|
||||
throw new Error('Workspace path is required');
|
||||
}
|
||||
|
||||
const envFile = this.findEnvironmentFileByUid(workspacePath, environmentUid);
|
||||
|
||||
if (!envFile) {
|
||||
throw new Error(`Environment file not found for uid: ${environmentUid}`);
|
||||
}
|
||||
|
||||
const environment = await this.parseEnvironmentFile(envFile.filePath, workspacePath);
|
||||
environment.color = color;
|
||||
|
||||
const content = stringifyEnvironment(environment, { format: 'yml' });
|
||||
await writeFile(envFile.filePath, content);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getGlobalEnvironmentsByPath(workspacePath) {
|
||||
return this.getGlobalEnvironments(workspacePath);
|
||||
}
|
||||
@@ -348,6 +372,10 @@ class GlobalEnvironmentsManager {
|
||||
async selectGlobalEnvironmentByPath(workspacePath, params) {
|
||||
return this.selectGlobalEnvironment(workspacePath, params);
|
||||
}
|
||||
|
||||
async updateGlobalEnvironmentColorByPath(workspacePath, { environmentUid, color }) {
|
||||
return this.updateGlobalEnvironmentColor(workspacePath, environmentUid, color);
|
||||
}
|
||||
}
|
||||
|
||||
const globalEnvironmentsManager = new GlobalEnvironmentsManager();
|
||||
|
||||
@@ -43,7 +43,8 @@ const parseEnvironment = (ymlString: string): BrunoEnvironment => {
|
||||
const brunoEnvironment: BrunoEnvironment = {
|
||||
uid: uuid(),
|
||||
name: ensureString(ocEnvironment.name, 'Untitled Environment'),
|
||||
variables: toBrunoEnvironmentVariables(ocEnvironment.variables)
|
||||
variables: toBrunoEnvironmentVariables(ocEnvironment.variables),
|
||||
color: ocEnvironment.color || null
|
||||
};
|
||||
|
||||
return brunoEnvironment;
|
||||
|
||||
@@ -47,7 +47,10 @@ const stringifyEnvironment = (environment: BrunoEnvironment): string => {
|
||||
name: environment.name
|
||||
};
|
||||
|
||||
// Convert variables if they exist
|
||||
if (environment.color) {
|
||||
ocEnvironment.color = environment.color;
|
||||
}
|
||||
|
||||
if (environment.variables?.length) {
|
||||
const ocVariables = toOpenCollectionEnvironmentVariables(environment.variables);
|
||||
if (ocVariables) {
|
||||
|
||||
4
packages/bruno-filestore/test-results/.last-run.json
Normal file
4
packages/bruno-filestore/test-results/.last-run.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"status": "failed",
|
||||
"failedTests": []
|
||||
}
|
||||
@@ -3,6 +3,8 @@ const { getValueString, indentString } = require('./utils');
|
||||
|
||||
const envToJson = (json) => {
|
||||
const variables = _.get(json, 'variables', []);
|
||||
const color = _.get(json, 'color', null);
|
||||
|
||||
const vars = variables
|
||||
.filter((variable) => !variable.secret)
|
||||
.map((variable) => {
|
||||
@@ -20,8 +22,6 @@ const envToJson = (json) => {
|
||||
return indentString(`${prefix}${name}`);
|
||||
});
|
||||
|
||||
const color = _.get(json, 'color', undefined);
|
||||
|
||||
let output = '';
|
||||
|
||||
if (!variables || !variables.length) {
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface Environment {
|
||||
uid: UID;
|
||||
name: string;
|
||||
variables: EnvironmentVariable[];
|
||||
color?: string | null;
|
||||
}
|
||||
|
||||
export type Environments = Environment[];
|
||||
|
||||
@@ -17,7 +17,7 @@ const environmentSchema = Yup.object({
|
||||
uid: uidSchema,
|
||||
name: Yup.string().min(1).required('name is required'),
|
||||
variables: Yup.array().of(environmentVariablesSchema).required('variables are required'),
|
||||
color: Yup.string().optional()
|
||||
color: Yup.string().nullable().optional()
|
||||
})
|
||||
.noUnknown(true)
|
||||
.strict();
|
||||
|
||||
5
tests/environments/color-picker/collection/bruno.json
Normal file
5
tests/environments/color-picker/collection/bruno.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "global-env-config-selection",
|
||||
"type": "collection"
|
||||
}
|
||||
17
tests/environments/color-picker/collection/test-request.bru
Normal file
17
tests/environments/color-picker/collection/test-request.bru
Normal file
@@ -0,0 +1,17 @@
|
||||
meta {
|
||||
name: test-request
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{host}}/api/echo
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
tests {
|
||||
test("should get 200 response", function() {
|
||||
expect(res.getStatus()).to.equal(200);
|
||||
});
|
||||
}
|
||||
156
tests/environments/color-picker/color-picker.spec.ts
Normal file
156
tests/environments/color-picker/color-picker.spec.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import { test, expect } from '../../../playwright';
|
||||
import { closeAllCollections } from '../../utils/page/actions';
|
||||
|
||||
const PRESET_COLORS = [
|
||||
'#CE4F3B',
|
||||
'#2E8A54',
|
||||
'#346AB2',
|
||||
'#C77A0F',
|
||||
'#B83D7F',
|
||||
'#8D44B2'
|
||||
];
|
||||
|
||||
// Convert hex color to RGB format used by CSS
|
||||
const hexToRgb = (hex: string): string => {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
if (!result) return '';
|
||||
const r = parseInt(result[1], 16);
|
||||
const g = parseInt(result[2], 16);
|
||||
const b = parseInt(result[3], 16);
|
||||
return `rgb(${r}, ${g}, ${b})`;
|
||||
};
|
||||
|
||||
test.describe('Color Picker Tests', () => {
|
||||
test.afterAll(async ({ pageWithUserData: page }) => {
|
||||
await closeAllCollections(page);
|
||||
});
|
||||
|
||||
test('should select a preset color for global environment', async ({ pageWithUserData: page }) => {
|
||||
// Open the collection from sidebar
|
||||
await page.locator('#sidebar-collection-name').filter({ hasText: 'global-env-config-selection' }).click();
|
||||
|
||||
// Open global environment configuration
|
||||
await page.getByTestId('environment-selector-trigger').click();
|
||||
await page.getByTestId('env-tab-global').click();
|
||||
await page.getByText('Configure', { exact: true }).click();
|
||||
|
||||
// Wait for the environments tab to be visible
|
||||
const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
|
||||
await expect(envTab).toBeVisible();
|
||||
|
||||
// Click on the color picker icon (brush icon) next to the environment name
|
||||
const colorPickerTrigger = page.locator('[title="Change color"]').first();
|
||||
await colorPickerTrigger.click();
|
||||
|
||||
// Wait for the color picker dropdown to appear
|
||||
const colorPickerDropdown = page.locator('.tippy-box');
|
||||
await expect(colorPickerDropdown).toBeVisible();
|
||||
|
||||
// Select the first preset color (red) using title attribute
|
||||
const presetColor = PRESET_COLORS[0];
|
||||
const colorOption = colorPickerDropdown.locator(`[title="${presetColor}"]`);
|
||||
await colorOption.click();
|
||||
|
||||
// Verify the color badge in the environment list shows the selected color
|
||||
const activeEnvItem = page.locator('.environment-item.active');
|
||||
const colorBadge = activeEnvItem.locator('.rounded-full').first();
|
||||
await expect(colorBadge).toHaveCSS('background-color', hexToRgb(presetColor));
|
||||
});
|
||||
|
||||
test('should remove color from environment', async ({ pageWithUserData: page }) => {
|
||||
// Open global environment configuration
|
||||
await page.getByTestId('environment-selector-trigger').click();
|
||||
await page.getByTestId('env-tab-global').click();
|
||||
await page.getByText('Configure', { exact: true }).click();
|
||||
|
||||
// Wait for the environments tab to be visible
|
||||
const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
|
||||
await expect(envTab).toBeVisible();
|
||||
|
||||
// Click on the color picker icon
|
||||
const colorPickerTrigger = page.locator('[title="Change color"]').first();
|
||||
await colorPickerTrigger.click();
|
||||
|
||||
// Wait for the color picker dropdown to appear
|
||||
const colorPickerDropdown = page.locator('.tippy-box');
|
||||
await expect(colorPickerDropdown).toBeVisible();
|
||||
|
||||
// Click the "No color" option (ban icon)
|
||||
const noColorOption = colorPickerDropdown.locator('[title="No color"]');
|
||||
await noColorOption.click();
|
||||
|
||||
// Verify the color badge becomes transparent (no color)
|
||||
const activeEnvItem = page.locator('.environment-item.active');
|
||||
const colorBadge = activeEnvItem.locator('.rounded-full').first();
|
||||
await expect(colorBadge).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)');
|
||||
});
|
||||
|
||||
test('should select custom color using slider', async ({ pageWithUserData: page }) => {
|
||||
// Open global environment configuration
|
||||
await page.getByTestId('environment-selector-trigger').click();
|
||||
await page.getByTestId('env-tab-global').click();
|
||||
await page.getByText('Configure', { exact: true }).click();
|
||||
|
||||
// Wait for the environments tab to be visible
|
||||
const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
|
||||
await expect(envTab).toBeVisible();
|
||||
|
||||
// Click on the color picker icon
|
||||
const colorPickerTrigger = page.locator('[title="Change color"]').first();
|
||||
await colorPickerTrigger.click();
|
||||
|
||||
// Wait for the color picker dropdown to appear
|
||||
const colorPickerDropdown = page.locator('.tippy-box');
|
||||
await expect(colorPickerDropdown).toBeVisible();
|
||||
|
||||
// Find the slider and change its value
|
||||
const slider = colorPickerDropdown.locator('input[type="range"]');
|
||||
await expect(slider).toBeVisible();
|
||||
|
||||
// Move slider to middle position (50%)
|
||||
await slider.fill('50');
|
||||
|
||||
// Click the custom color preview to apply it
|
||||
const customColorPreview = colorPickerDropdown.locator('[title="Custom color"]');
|
||||
await customColorPreview.click();
|
||||
|
||||
// Verify the color badge has a color applied (not transparent)
|
||||
const activeEnvItem = page.locator('.environment-item.active');
|
||||
const colorBadge = activeEnvItem.locator('.rounded-full').first();
|
||||
const bgColor = await colorBadge.evaluate((el) => getComputedStyle(el).backgroundColor);
|
||||
expect(bgColor).not.toBe('rgba(0, 0, 0, 0)');
|
||||
expect(bgColor).toMatch(/^rgb\(\d+, \d+, \d+\)$/);
|
||||
});
|
||||
|
||||
test('should display color badge in environment list after selecting color', async ({ pageWithUserData: page }) => {
|
||||
// Open global environment configuration
|
||||
await page.getByTestId('environment-selector-trigger').click();
|
||||
await page.getByTestId('env-tab-global').click();
|
||||
await page.getByText('Configure', { exact: true }).click();
|
||||
|
||||
// Wait for the environments tab to be visible
|
||||
const envTab = page.locator('.request-tab').filter({ hasText: 'Global Environments' });
|
||||
await expect(envTab).toBeVisible();
|
||||
|
||||
// Get the currently selected environment name
|
||||
const activeEnvItem = page.locator('.environment-item.active');
|
||||
const envName = await activeEnvItem.locator('.environment-name').textContent();
|
||||
|
||||
// Click on the color picker icon
|
||||
const colorPickerTrigger = page.locator('[title="Change color"]').first();
|
||||
await colorPickerTrigger.click();
|
||||
|
||||
// Wait for the color picker dropdown to appear and select a color
|
||||
const colorPickerDropdown = page.locator('.tippy-box');
|
||||
await expect(colorPickerDropdown).toBeVisible();
|
||||
|
||||
const presetColor = PRESET_COLORS[1]; // green
|
||||
const colorOption = colorPickerDropdown.locator(`[title="${presetColor}"]`);
|
||||
await colorOption.click();
|
||||
|
||||
// Verify the color badge in the environment list shows the selected color
|
||||
const envListItem = page.locator('.environment-item').filter({ hasText: envName as string });
|
||||
const colorBadge = envListItem.locator('.rounded-full').first();
|
||||
await expect(colorBadge).toHaveCSS('background-color', hexToRgb(presetColor));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"collections": [
|
||||
{
|
||||
"path": "{{projectRoot}}/tests/environments/global-env-config-selection/collection",
|
||||
"securityConfig": {
|
||||
"jsSandboxMode": "safe"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"environments": [
|
||||
{
|
||||
"uid": "FlaexlO7lcH7UtEpWsVyz",
|
||||
"name": "Development Environment",
|
||||
"variables": [
|
||||
{
|
||||
"uid": "lflBDSYBdHkUedYhBF4Ty",
|
||||
"name": "env_type",
|
||||
"value": "development",
|
||||
"type": "text",
|
||||
"secret": false,
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"uid": "MsHcnAIonZ3455OfvpTUT",
|
||||
"name": "Production Environment",
|
||||
"variables": [
|
||||
{
|
||||
"uid": "TZljXLErzW1nUWoozntZE",
|
||||
"name": "env_type",
|
||||
"value": "production",
|
||||
"type": "text",
|
||||
"secret": false,
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"uid": "VdUAdMPcfapMCqjKAeUiI",
|
||||
"name": "Staging Environment",
|
||||
"variables": [
|
||||
{
|
||||
"uid": "FwoWhHvu9eLhA8H4brG6f",
|
||||
"name": "env_type",
|
||||
"value": "staging",
|
||||
"type": "text",
|
||||
"secret": false,
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"activeGlobalEnvironmentUid": "MsHcnAIonZ3455OfvpTUT"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"maximized": true,
|
||||
"lastOpenedCollections": [
|
||||
"{{projectRoot}}/tests/environments/global-env-config-selection/collection"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user