From 8cee7bad392f1342103ab58ea63cb644e4ae640a Mon Sep 17 00:00:00 2001 From: Shashank Shekhar <48152748+sha5git@users.noreply.github.com> Date: Tue, 2 Dec 2025 14:53:51 +0530 Subject: [PATCH 1/2] added copy button to copy response (#5409) Co-authored-by: Shashank Shekhar <48152748+sha5-git@users.noreply.github.com> --- .../ResponseCopy/StyledWrapper.js | 8 +++++ .../ResponsePane/ResponseCopy/index.js | 33 +++++++++++++++++++ .../src/components/ResponsePane/index.js | 4 +++ 3 files changed, 45 insertions(+) create mode 100644 packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js new file mode 100644 index 000000000..b2acb52b8 --- /dev/null +++ b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js @@ -0,0 +1,8 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + font-size: 0.8125rem; + color: ${(props) => props.theme.requestTabPanel.responseStatus}; +`; + +export default StyledWrapper; \ No newline at end of file diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js new file mode 100644 index 000000000..80763f131 --- /dev/null +++ b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js @@ -0,0 +1,33 @@ +import React from 'react'; +import StyledWrapper from './StyledWrapper'; +import toast from 'react-hot-toast'; +import { IconCopy } from '@tabler/icons'; + +const ResponseCopy = ({ item }) => { + const response = item.response || {}; + + const copyResponse = () => { + try { + const textToCopy = typeof response.data === 'string' + ? response.data + : JSON.stringify(response.data, null, 2); + + navigator.clipboard.writeText(textToCopy).then(() => { + toast.success('Response copied to clipboard'); + }).catch(() => { + toast.error('Failed to copy response'); + }); + } catch (error) { + toast.error('Failed to copy response'); + } + }; + + return ( + + + + ); +}; +export default ResponseCopy; \ No newline at end of file diff --git a/packages/bruno-app/src/components/ResponsePane/index.js b/packages/bruno-app/src/components/ResponsePane/index.js index b098570e2..859196182 100644 --- a/packages/bruno-app/src/components/ResponsePane/index.js +++ b/packages/bruno-app/src/components/ResponsePane/index.js @@ -18,6 +18,7 @@ import ScriptErrorIcon from './ScriptErrorIcon'; import StyledWrapper from './StyledWrapper'; import ResponseSave from 'src/components/ResponsePane/ResponseSave'; import ResponseClear from 'src/components/ResponsePane/ResponseClear'; +import ResponseCopy from 'src/components/ResponsePane/ResponseCopy'; import ResponseBookmark from 'src/components/ResponsePane/ResponseBookmark'; import SkippedRequest from './SkippedRequest'; import ClearTimeline from './ClearTimeline/index'; @@ -189,6 +190,9 @@ const ResponsePane = ({ item, collection }) => { <> + + + {item.response?.stream?.running From 2a251b1a626d8faaa8d82c0615a12eb53d5d9a0f Mon Sep 17 00:00:00 2001 From: Pooja Date: Tue, 2 Dec 2025 15:06:39 +0530 Subject: [PATCH 2/2] added copy button to copy response (#6131) * added copy button to copy response * add: response action component * fix: lint error * add: playwright test --------- Co-authored-by: Shashank Shekhar <48152748+sha5-git@users.noreply.github.com> Co-authored-by: Sid --- .../ResponseActions/StyledWrapper.js | 29 +++++++++++ .../ResponsePane/ResponseActions/index.js | 35 +++++++++++++ .../ResponsePane/ResponseClear/index.js | 15 +++++- .../ResponseCopy/StyledWrapper.js | 2 +- .../ResponsePane/ResponseCopy/index.js | 39 +++++++++----- .../ResponsePane/ResponseSave/index.js | 18 ++++++- .../src/components/ResponsePane/index.js | 12 ++--- tests/response/response-actions.spec.ts | 51 +++++++++++++++++++ 8 files changed, 176 insertions(+), 25 deletions(-) create mode 100644 packages/bruno-app/src/components/ResponsePane/ResponseActions/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js create mode 100644 tests/response/response-actions.spec.ts diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseActions/StyledWrapper.js b/packages/bruno-app/src/components/ResponsePane/ResponseActions/StyledWrapper.js new file mode 100644 index 000000000..6ba69090e --- /dev/null +++ b/packages/bruno-app/src/components/ResponsePane/ResponseActions/StyledWrapper.js @@ -0,0 +1,29 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + button { + color: var(--color-tab-inactive); + cursor: pointer; + + &:hover { + color: var(--color-tab-active); + } + + &:disabled { + cursor: not-allowed; + opacity: 0.5; + } + } + + .cursor-pointer { + display: flex; + align-items: center; + color: var(--color-tab-inactive); + + &:hover { + color: var(--color-tab-active); + } + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js b/packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js new file mode 100644 index 000000000..a430153a0 --- /dev/null +++ b/packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js @@ -0,0 +1,35 @@ +import React, { useRef, forwardRef } from 'react'; +import { IconDots } from '@tabler/icons'; +import Dropdown from 'components/Dropdown'; +import StyledWrapper from './StyledWrapper'; +import ResponseClear from 'src/components/ResponsePane/ResponseClear'; +import ResponseSave from 'src/components/ResponsePane/ResponseSave'; + +const ResponseActions = ({ collection, item }) => { + const menuDropdownTippyRef = useRef(); + + const onMenuDropdownCreate = (ref) => (menuDropdownTippyRef.current = ref); + + const MenuIcon = forwardRef((_props, ref) => { + return ( +
+ +
+ ); + }); + + const handleClose = () => { + menuDropdownTippyRef.current.hide(); + }; + + return ( + + } placement="bottom-end"> + + + + + ); +}; + +export default ResponseActions; diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js b/packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js index 747543347..b18418592 100644 --- a/packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js +++ b/packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js @@ -4,10 +4,11 @@ import { useDispatch } from 'react-redux'; import StyledWrapper from './StyledWrapper'; import { responseCleared } from 'providers/ReduxStore/slices/collections/index'; -const ResponseClear = ({ collection, item }) => { +const ResponseClear = ({ collection, item, asDropdownItem, onClose }) => { const dispatch = useDispatch(); - const clearResponse = () => + const clearResponse = () => { + if (onClose) onClose(); dispatch( responseCleared({ itemUid: item.uid, @@ -15,6 +16,16 @@ const ResponseClear = ({ collection, item }) => { response: null }) ); + }; + + if (asDropdownItem) { + return ( +
+ + Clear +
+ ); + } return ( diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js index b2acb52b8..8c32a8bab 100644 --- a/packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js +++ b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js @@ -5,4 +5,4 @@ const StyledWrapper = styled.div` color: ${(props) => props.theme.requestTabPanel.responseStatus}; `; -export default StyledWrapper; \ No newline at end of file +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js index 80763f131..7c59301ef 100644 --- a/packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js +++ b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js @@ -1,33 +1,46 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import StyledWrapper from './StyledWrapper'; import toast from 'react-hot-toast'; -import { IconCopy } from '@tabler/icons'; +import { IconCopy, IconCheck } from '@tabler/icons'; const ResponseCopy = ({ item }) => { const response = item.response || {}; + const [copied, setCopied] = useState(false); - const copyResponse = () => { + useEffect(() => { + if (copied) { + const timer = setTimeout(() => { + setCopied(false); + }, 2000); + return () => clearTimeout(timer); + } + }, [copied]); + + const copyResponse = async () => { try { - const textToCopy = typeof response.data === 'string' - ? response.data - : JSON.stringify(response.data, null, 2); + const textToCopy = typeof response.data === 'string' + ? response.data + : JSON.stringify(response.data, null, 2); - navigator.clipboard.writeText(textToCopy).then(() => { + await navigator.clipboard.writeText(textToCopy); toast.success('Response copied to clipboard'); - }).catch(() => { - toast.error('Failed to copy response'); - }); + setCopied(true); } catch (error) { - toast.error('Failed to copy response'); + toast.error('Failed to copy response'); } }; return ( ); }; -export default ResponseCopy; \ No newline at end of file + +export default ResponseCopy; diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js b/packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js index 15eef651b..c791463cd 100644 --- a/packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js +++ b/packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js @@ -4,11 +4,13 @@ import toast from 'react-hot-toast'; import get from 'lodash/get'; import { IconDownload } from '@tabler/icons'; -const ResponseSave = ({ item }) => { +const ResponseSave = ({ item, asDropdownItem, onClose }) => { const { ipcRenderer } = window; const response = item.response || {}; const saveResponseToFile = () => { + if (!response.dataBuffer) return; + if (onClose) onClose(); return new Promise((resolve, reject) => { ipcRenderer .invoke('renderer:save-response-to-file', response, item?.requestSent?.url, item.pathname) @@ -20,6 +22,20 @@ const ResponseSave = ({ item }) => { }); }; + if (asDropdownItem) { + return ( +
+ + Download +
+ ); + } + return (