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 new file mode 100644 index 000000000..8c32a8bab --- /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; 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..7c59301ef --- /dev/null +++ b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js @@ -0,0 +1,46 @@ +import React, { useState, useEffect } from 'react'; +import StyledWrapper from './StyledWrapper'; +import toast from 'react-hot-toast'; +import { IconCopy, IconCheck } from '@tabler/icons'; + +const ResponseCopy = ({ item }) => { + const response = item.response || {}; + const [copied, setCopied] = useState(false); + + 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); + + await navigator.clipboard.writeText(textToCopy); + toast.success('Response copied to clipboard'); + setCopied(true); + } catch (error) { + toast.error('Failed to copy response'); + } + }; + + return ( + + + + ); +}; + +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 (