Skip to content

Commit b015d7c

Browse files
fix: Refactor and fix clipboard (#274)
1 parent 03ec511 commit b015d7c

File tree

6 files changed

+51
-49
lines changed

6 files changed

+51
-49
lines changed

public/locales/en.json

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,6 @@
8888
},
8989
"CopyKubeconfigButton": {
9090
"kubeconfigButton": "Kubeconfig",
91-
"copiedMessage": "Copied to Clipboard",
92-
"failedMessage": "Failed to copy, Error:",
9391
"menuDownload": "Download",
9492
"menuCopy": "Copy to clipboard"
9593
},
@@ -220,10 +218,6 @@
220218
"treeExecution": "Execution",
221219
"noExecutionFound": "No Executions found"
222220
},
223-
"CopyButton": {
224-
"copiedMessage": "Copied To Clipboard",
225-
"failedMessage": "Failed to copy"
226-
},
227221
"DeleteWorkspaceDialog": {
228222
"title": "Delete a Workspace",
229223
"introSection1": "The below instructions will help you delete the workspace \"{{workspaceName}}\" from project namespace \"{{projectNamespace}}\" using kubectl.",
@@ -357,7 +351,9 @@
357351
"unhealthy": "Unhealthy",
358352
"progress": "Managed",
359353
"remaining": "Remaining",
360-
"active": "Active"
354+
"active": "Active",
355+
"copyToClipboardSuccessToast": "Copied to clipboard",
356+
"copyToClipboardFailedToast": "Failed to copy to clipboard"
361357
},
362358
"errors": {
363359
"installError": "Install error",
@@ -377,7 +373,6 @@
377373
"update": "Update"
378374
},
379375
"yaml": {
380-
"copiedToClipboard": "YAML copied to clipboard!",
381376
"YAML": "File"
382377
},
383378
"createMCP": {

src/components/ControlPlanes/CopyKubeconfigButton.tsx

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Button, Menu, MenuItem } from '@ui5/webcomponents-react';
2-
import { useToast } from '../../context/ToastContext.tsx';
2+
import { useCopyToClipboard } from '../../hooks/useCopyToClipboard.ts';
33
import { useRef, useState } from 'react';
44
import '@ui5/webcomponents-icons/dist/copy';
55
import '@ui5/webcomponents-icons/dist/accept';
@@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next';
99
export default function CopyKubeconfigButton() {
1010
const popoverRef = useRef(null);
1111
const [open, setOpen] = useState(false);
12-
const { show } = useToast();
12+
const { copyToClipboard } = useCopyToClipboard();
1313
const { t } = useTranslation();
1414

1515
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -37,14 +37,7 @@ export default function CopyKubeconfigButton() {
3737
return;
3838
}
3939
if (event.detail.item.dataset.action === 'copy') {
40-
try {
41-
navigator.clipboard.writeText(mcp.kubeconfig ?? '');
42-
show(t('CopyKubeconfigButton.copiedMessage'));
43-
} catch (error) {
44-
//TODO: handle error, show error to user
45-
show(`${t('CopyKubeconfigButton.failedMessage')} ${error}`);
46-
console.error(error);
47-
}
40+
void copyToClipboard(mcp.kubeconfig ?? '');
4841
}
4942

5043
setOpen(false);

src/components/Dialogs/KubectlCommandInfo/KubectlTerminal.tsx

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { FlexBox, Button } from '@ui5/webcomponents-react';
2-
import { useToast } from '../../../context/ToastContext';
2+
import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard.ts';
33
import '@ui5/webcomponents-icons/dist/copy';
44
import { ThemingParameters } from '@ui5/webcomponents-react-base';
55

@@ -8,18 +8,7 @@ interface KubeCtlTerminalProps {
88
}
99

1010
export const KubectlTerminal = ({ command }: KubeCtlTerminalProps) => {
11-
const { show } = useToast();
12-
13-
const handleCopy = () => {
14-
navigator.clipboard.writeText(command).then(
15-
() => {
16-
show('Command copied to clipboard');
17-
},
18-
(err) => {
19-
console.error('Could not copy text: ', err);
20-
},
21-
);
22-
};
11+
const { copyToClipboard } = useCopyToClipboard();
2312

2413
const FormattedCommand = () => {
2514
if (command.startsWith("echo '") && command.includes('apiVersion:')) {
@@ -91,7 +80,7 @@ export const KubectlTerminal = ({ command }: KubeCtlTerminalProps) => {
9180
}}
9281
/>
9382
</FlexBox>
94-
<Button icon="copy" design="Transparent" tooltip="Copy to clipboard" onClick={handleCopy} />
83+
<Button icon="copy" design="Transparent" tooltip="Copy to clipboard" onClick={() => copyToClipboard(command)} />
9584
</FlexBox>
9685

9786
<div style={{ padding: '12px 16px', overflowX: 'auto' }}>

src/components/Shared/CopyButton.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Button, ButtonPropTypes } from '@ui5/webcomponents-react';
2-
import { useToast } from '../../context/ToastContext.tsx';
2+
import { useCopyToClipboard } from '../../hooks/useCopyToClipboard.ts';
33
import { useId, CSSProperties } from 'react';
44
import { useCopyButton } from '../../context/CopyButtonContext.tsx';
55
import { ThemingParameters } from '@ui5/webcomponents-react-base';
@@ -11,20 +11,15 @@ interface CopyButtonProps extends ButtonPropTypes {
1111
}
1212

1313
export const CopyButton = ({ text, style = {}, ...buttonProps }: CopyButtonProps) => {
14-
const { show } = useToast();
14+
const { copyToClipboard } = useCopyToClipboard();
1515
const { activeCopyId, setActiveCopyId } = useCopyButton();
1616
const uniqueId = useId();
1717
const isCopied = activeCopyId === uniqueId;
1818
const { t } = useTranslation();
1919

2020
const handleCopy = async () => {
21-
try {
22-
await navigator.clipboard.writeText(text);
23-
setActiveCopyId(uniqueId);
24-
} catch (err) {
25-
console.error(`Failed to copy text: ${text}. Error: ${err}`);
26-
show(`${t('CopyButton.copiedMessage')} ${err}`);
27-
}
21+
await copyToClipboard(text, { showToastOnSuccess: false });
22+
setActiveCopyId(uniqueId);
2823
};
2924

3025
const defaultStyle: CSSProperties = {
@@ -40,7 +35,7 @@ export const CopyButton = ({ text, style = {}, ...buttonProps }: CopyButtonProps
4035
onClick={handleCopy}
4136
{...buttonProps}
4237
>
43-
{isCopied ? t('CopyButton.copiedMessage') : text}
38+
{isCopied ? t('common.copyToClipboardSuccessToast') : text}
4439
</Button>
4540
);
4641
};

src/components/Yaml/YamlViewer.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,14 @@ import { materialLight, materialDark } from 'react-syntax-highlighter/dist/esm/s
44

55
import { Button, FlexBox } from '@ui5/webcomponents-react';
66
import styles from './YamlViewer.module.css';
7-
import { useToast } from '../../context/ToastContext.tsx';
7+
import { useCopyToClipboard } from '../../hooks/useCopyToClipboard.ts';
88
import { useTranslation } from 'react-i18next';
99
import { useTheme } from '../../hooks/useTheme.ts';
1010
type YamlViewerProps = { yamlString: string; filename: string };
1111
const YamlViewer: FC<YamlViewerProps> = ({ yamlString, filename }) => {
12-
const toast = useToast();
1312
const { t } = useTranslation();
1413
const { isDarkTheme } = useTheme();
15-
const copyToClipboard = () => {
16-
navigator.clipboard.writeText(yamlString);
17-
toast.show(t('yaml.copiedToClipboard'));
18-
};
14+
const { copyToClipboard } = useCopyToClipboard();
1915
const downloadYaml = () => {
2016
const blob = new Blob([yamlString], { type: 'text/yaml' });
2117
const url = window.URL.createObjectURL(blob);
@@ -31,7 +27,7 @@ const YamlViewer: FC<YamlViewerProps> = ({ yamlString, filename }) => {
3127
return (
3228
<div className={styles.container}>
3329
<FlexBox className={styles.buttons} direction="Row" justifyContent="End" alignItems="Baseline" gap={16}>
34-
<Button icon="copy" onClick={copyToClipboard}>
30+
<Button icon="copy" onClick={() => copyToClipboard(yamlString)}>
3531
{t('buttons.copy')}
3632
</Button>
3733
<Button icon="download" onClick={downloadYaml}>

src/hooks/useCopyToClipboard.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { useCallback } from 'react';
2+
import { useToast } from '../context/ToastContext.tsx';
3+
import { useTranslation } from 'react-i18next';
4+
5+
export type CopyFn = (text: string, options?: { showToastOnSuccess: boolean }) => Promise<boolean>;
6+
7+
export function useCopyToClipboard(): { copyToClipboard: CopyFn } {
8+
const toast = useToast();
9+
const { t } = useTranslation();
10+
11+
const copyToClipboard: CopyFn = useCallback(
12+
async (text, options = { showToastOnSuccess: true }) => {
13+
if (!navigator.clipboard) {
14+
toast.show(t('common.copyToClipboardFailedToast'));
15+
return false;
16+
}
17+
18+
try {
19+
await navigator.clipboard.writeText(text);
20+
if (options.showToastOnSuccess) {
21+
toast.show(t('common.copyToClipboardSuccessToast'));
22+
}
23+
return true;
24+
} catch (error) {
25+
toast.show(t('common.copyToClipboardFailedToast'));
26+
console.error(error);
27+
return false;
28+
}
29+
},
30+
[toast, t],
31+
);
32+
33+
return { copyToClipboard };
34+
}

0 commit comments

Comments
 (0)