Skip to content

Commit 6fc9c8c

Browse files
committed
Adding copy to clipboard functionality to flow nodes
1 parent 39397f2 commit 6fc9c8c

2 files changed

Lines changed: 71 additions & 38 deletions

File tree

src/components/molecules/flow/nodes/RequestBody.js

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { Fragment, useState, useRef } from 'react';
22
import { PropTypes } from 'prop-types';
33
import { EllipsisVerticalIcon } from '@heroicons/react/24/solid';
4-
import { DocumentArrowUpIcon } from '@heroicons/react/24/outline';
4+
import { DocumentArrowUpIcon, ClipboardDocumentCheckIcon } from '@heroicons/react/24/outline';
55
import { Menu, Transition } from '@headlessui/react';
66
import useCanvasStore from 'stores/CanvasStore';
77
import { toast } from 'react-toastify';
@@ -13,13 +13,19 @@ import { BUTTON_TYPES } from 'constants/Common';
1313
import useCollectionStore from 'stores/CollectionStore';
1414
import { useTabStore } from 'stores/TabStore';
1515
import { cloneDeep } from 'lodash';
16+
import { CopyToClipboard } from 'react-copy-to-clipboard';
17+
import Tippy from '@tippyjs/react';
1618

1719
const requestBodyTypeOptions = ['None', 'form-data', 'raw-json'];
1820

1921
const RequestBody = ({ nodeId, nodeData }) => {
2022
const setRequestNodeBody = useCanvasStore((state) => state.setRequestNodeBody);
2123
const [cachedValues, setCachedValues] = React.useState({});
2224

25+
const [textToCopy, setTextToCopy] = useState(nodeData.requestBody.body); // The text you want to copy
26+
const [copyStatus, setCopyStatus] = useState(false); // To indicate if the text was copied
27+
const [showCopiedToClipboardToolTip, setShowCopiedToClipboardToolTip] = useState(false);
28+
2329
const uploadFileForRequestNode = useRef(null);
2430

2531
const updateCachedValues = () => {
@@ -67,7 +73,7 @@ const RequestBody = ({ nodeId, nodeData }) => {
6773
});
6874
};
6975

70-
const handleRawJson = (value) => {
76+
const handleRawJson = async (value) => {
7177
setRequestNodeBody(nodeId, 'raw-json', value);
7278
};
7379

@@ -108,13 +114,20 @@ const RequestBody = ({ nodeId, nodeData }) => {
108114
return [];
109115
};
110116

117+
const onCopyText = () => {
118+
setCopyStatus(true);
119+
setShowCopiedToClipboardToolTip(true);
120+
setTimeout(() => setCopyStatus(false), 2000); // Reset status after 2 seconds
121+
setTimeout(() => setShowCopiedToClipboardToolTip(false), 2000); // Reset status after 2 seconds
122+
};
123+
111124
return (
112125
<>
113-
<div className='flex items-center justify-between p-4 bg-background'>
126+
<div className='flex items-center justify-between bg-background p-4'>
114127
<h3>Body</h3>
115128
<Menu as='div' className='relative inline-block text-left'>
116129
<Menu.Button data-click-from='body-type-menu'>
117-
<EllipsisVerticalIcon className='w-4 h-4' aria-hidden='true' data-click-from='body-type-menu' />
130+
<EllipsisVerticalIcon className='h-4 w-4' aria-hidden='true' data-click-from='body-type-menu' />
118131
</Menu.Button>
119132
<Transition
120133
as={Fragment}
@@ -126,13 +139,13 @@ const RequestBody = ({ nodeId, nodeData }) => {
126139
leaveTo='transform opacity-0 scale-95'
127140
>
128141
<Menu.Items
129-
className='absolute right-0 z-10 w-56 px-1 py-1 mt-2 origin-top-right bg-white divide-y divide-gray-100 rounded-md shadow-lg ring-1 ring-black/5 focus:outline-none'
142+
className='absolute right-0 z-10 mt-2 w-56 origin-top-right divide-y divide-gray-100 rounded-md bg-white px-1 py-1 shadow-lg ring-1 ring-black/5 focus:outline-none'
130143
data-click-from='body-type-menu'
131144
>
132145
{requestBodyTypeOptions.map((bodyTypeOption, index) => (
133146
<Menu.Item key={index} data-click-from='body-type-menu' onClick={() => handleClose(bodyTypeOption)}>
134147
<button
135-
className='flex items-center w-full px-2 py-2 text-sm text-gray-900 rounded-md group hover:bg-background-light'
148+
className='group flex w-full items-center rounded-md px-2 py-2 text-sm text-gray-900 hover:bg-background-light'
136149
data-click-from='body-type-menu'
137150
>
138151
{bodyTypeOption}
@@ -146,16 +159,34 @@ const RequestBody = ({ nodeId, nodeData }) => {
146159
{nodeData.requestBody && nodeData.requestBody.type === 'raw-json' && (
147160
<>
148161
<NodeHorizontalDivider />
149-
<div className='p-4 bg-background'>
150-
<div className='w-full nodrag nowheel min-w-72'>
151-
<div className='bg-background-lighter'>
162+
<div className='bg-background p-4'>
163+
<div className='nodrag nowheel w-full min-w-72'>
164+
<div className='relative bg-background-lighter'>
152165
<Editor
153166
name='request-body-json'
154-
onChange={(e) => handleRawJson(e)}
167+
onChange={async (e) => {
168+
await handleRawJson(e);
169+
setTextToCopy(e);
170+
}}
155171
value={nodeData.requestBody.body}
156172
classes={'w-full max-h-96'}
157173
completionOptions={getActiveVariables()}
158174
/>
175+
176+
<div className='absolute right-5 top-0 cursor-pointer text-slate-400 hover:text-cyan-900'>
177+
<CopyToClipboard text={textToCopy} onCopy={onCopyText}>
178+
<button>
179+
<Tippy
180+
content='Copied to Clipboard'
181+
placement='top'
182+
visible={showCopiedToClipboardToolTip}
183+
onClickOutside={() => setShowCopiedToClipboardToolTip(false)}
184+
>
185+
<ClipboardDocumentCheckIcon className='h-6 w-6' />
186+
</Tippy>
187+
</button>
188+
</CopyToClipboard>
189+
</div>
159190
</div>
160191
<Button
161192
btnType={BUTTON_TYPES.secondary}
@@ -181,7 +212,7 @@ const RequestBody = ({ nodeId, nodeData }) => {
181212
{nodeData.requestBody && nodeData.requestBody.type === 'form-data' && (
182213
<>
183214
<NodeHorizontalDivider />
184-
<div className='p-4 bg-background'>
215+
<div className='bg-background p-4'>
185216
<TextInputWithLabel
186217
placeHolder='key'
187218
onChangeHandler={(e) => handleFormDataKey(e)}
@@ -198,7 +229,7 @@ const RequestBody = ({ nodeId, nodeData }) => {
198229
}}
199230
fullWidth={true}
200231
>
201-
<DocumentArrowUpIcon className='w-4 h-4 text-center' />
232+
<DocumentArrowUpIcon className='h-4 w-4 text-center' />
202233
Upload File
203234
{/* Ref: https://stackoverflow.com/questions/37457128/react-open-file-browser-on-click-a-div */}
204235
<div className='hidden'>

src/components/molecules/flow/nodes/RequestNode.js

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ const RequestNode = ({ id, data }) => {
7171
width='14'
7272
height='14'
7373
fill='currentColor'
74-
className='inline-block ml-2 cursor-pointer'
74+
className='ml-2 inline-block cursor-pointer'
7575
viewBox='0 0 16 16'
7676
style={{ marginTop: 1 }}
7777
>
@@ -87,32 +87,32 @@ const RequestNode = ({ id, data }) => {
8787
return (
8888
<div>
8989
{variables && Object.keys(variables).length > 0 ? (
90-
<table className='leading-normal'>
90+
<table className='border-collapse border-2 border-background-dark leading-normal'>
9191
<thead>
92-
<tr className='text-xs font-bold tracking-wider text-left bg-ghost-50 text-ghost-600'>
93-
<th className='w-2/6 p-1 border border-ghost-200'>Name</th>
94-
<th className='w-4/6 p-1 border border-ghost-200 '>Value</th>
95-
<th className='w-1/6 p-1 border border-ghost-200 '></th>
92+
<tr className='bg-ghost-50 text-ghost-600 text-left text-xs font-bold tracking-wider'>
93+
<th className='border-2 border-background-dark p-2'>Name</th>
94+
<th className='border-2 border-background-dark p-2'>Value</th>
95+
<th className='border-2 border-background-dark p-2'></th>
9696
</tr>
9797
</thead>
9898
<tbody>
9999
{Object.keys(variables).map((id, index) => (
100-
<tr key={index} className='text-sm border-b border-gray-200 text-ghost-700 hover:bg-ghost-50'>
101-
<td className='p-1 whitespace-no-wrap'>
100+
<tr key={index} className='text-ghost-700 hover:bg-ghost-50 border-b border-gray-200 text-sm'>
101+
<td className='whitespace-no-wrap border-2 border-background-dark'>
102102
<input
103103
type='text'
104-
className='nodrag nowheel block h-9 w-full p-2.5'
104+
className='nodrag nowheel block h-9 w-full bg-transparent p-2.5 outline-none'
105105
name='variable-name'
106106
value={id}
107107
readOnly
108108
/>
109109
</td>
110-
<td className='p-1 whitespace-no-wrap'>
110+
<td className='whitespace-no-wrap border-2 border-background-dark'>
111111
{variables[id].type === 'Boolean' ? (
112112
<select
113113
onChange={(e) => handleVariableChange(e, vType, id)}
114114
name='boolean-val'
115-
className=' nodrag nowheel h-9 w-full p-2.5 px-1'
115+
className=' nodrag nowheel h-9 w-full bg-background-light p-2.5 px-1 outline-none'
116116
value={variables[id].value}
117117
>
118118
<option value='true'>True</option>
@@ -121,26 +121,28 @@ const RequestNode = ({ id, data }) => {
121121
) : variables[id].type === 'Now' ? (
122122
<input
123123
type='text'
124-
className='nodrag nowheel block h-9 w-full p-2.5'
124+
className='nodrag nowheel block h-9 w-full bg-background-light p-2.5 outline-none'
125125
name='variable-name'
126126
value='Date.now()'
127127
readOnly
128128
/>
129129
) : (
130130
<input
131131
type={getInputType(variables[id].type)}
132-
className='nodrag nowheel block h-9 w-full p-2.5'
132+
className='nodrag nowheel block h-9 w-full bg-background-light p-2.5 outline-none'
133133
name='variable-value'
134134
data-type={getInputType(variables[id].type)}
135135
onChange={(e) => handleVariableChange(e, vType, id)}
136136
value={variables[id].value}
137137
/>
138138
)}
139139
</td>
140-
<td className='flex p-1 whitespace-no-wrap'>
141-
<Tooltip text={variables[id].type} />
142-
<div onClick={(e) => handleDeleteVariable(e, vType, id)} className='pl-2 text-neutral-500'>
143-
<TrashIcon className='w-4 h-4' />
140+
<td className='border-2 border-background-dark p-2'>
141+
<div className='flex items-center gap-4'>
142+
<Tooltip text={variables[id].type} />
143+
<div onClick={(e) => handleDeleteVariable(e, vType, id)} className='cursor-pointer'>
144+
<TrashIcon className='h-4 w-4' />
145+
</div>
144146
</div>
145147
</td>
146148
</tr>
@@ -178,14 +180,14 @@ const RequestNode = ({ id, data }) => {
178180
className='text-xl'
179181
>
180182
<div>
181-
<Listbox.Button className='relative flex text-left cursor-default border-cyan-950'>
183+
<Listbox.Button className='relative flex cursor-default border-cyan-950 text-left'>
182184
<span className='block truncate'>{data.requestType}</span>
183185
<span className='p-1'>
184-
<ChevronUpDownIcon className='w-5 h-5' aria-hidden='true' />
186+
<ChevronUpDownIcon className='h-5 w-5' aria-hidden='true' />
185187
</span>
186188
</Listbox.Button>
187189
<Transition as={Fragment} leave='transition ease-in duration-100' leaveFrom='opacity-100' leaveTo='opacity-0'>
188-
<Listbox.Options className='absolute z-50 py-1 mt-1 overflow-auto text-base bg-white max-h-60 w-36 focus:outline-none'>
190+
<Listbox.Options className='absolute z-50 mt-1 max-h-60 w-36 overflow-auto bg-white py-1 text-base focus:outline-none'>
189191
{requestNodes
190192
.map((el) => el.requestType)
191193
.map((reqType) => (
@@ -203,7 +205,7 @@ const RequestNode = ({ id, data }) => {
203205
<span className={`block`}>{reqType}</span>
204206
{selected ? (
205207
<span className='absolute inset-y-0 left-0 flex items-center pl-1 font-semibold'>
206-
<CheckIcon className='w-5 h-5' aria-hidden='true' />
208+
<CheckIcon className='h-5 w-5' aria-hidden='true' />
207209
</span>
208210
) : null}
209211
</>
@@ -239,9 +241,9 @@ const RequestNode = ({ id, data }) => {
239241
<NodeHorizontalDivider />
240242
<div className='bg-background'>
241243
<h3 className='p-2'>Variables</h3>
242-
<div>
244+
<div className='px-2'>
243245
<NodeHorizontalDivider />
244-
<div>
246+
<div className='pb-2'>
245247
<div className='flex items-center justify-between'>
246248
<div className='p-2'>Pre Request</div>
247249
<button
@@ -250,13 +252,13 @@ const RequestNode = ({ id, data }) => {
250252
setVariableDialogOpen(true);
251253
}}
252254
>
253-
<PlusIcon className='w-4 h-4' />
255+
<PlusIcon className='h-4 w-4' />
254256
</button>
255257
</div>
256258
{renderVariables('pre-request')}
257259
</div>
258260
<NodeHorizontalDivider />
259-
<div>
261+
<div className='pb-2'>
260262
<div className='flex items-center justify-between'>
261263
<div className='p-2'>Post Response</div>
262264
<button
@@ -265,7 +267,7 @@ const RequestNode = ({ id, data }) => {
265267
setVariableDialogOpen(true);
266268
}}
267269
>
268-
<PlusIcon className='w-4 h-4' />
270+
<PlusIcon className='h-4 w-4' />
269271
</button>
270272
</div>
271273
{renderVariables('post-response')}

0 commit comments

Comments
 (0)