From 5e5872698a6c2b6555a6e02a166b6729bd29623d Mon Sep 17 00:00:00 2001 From: Carl Smith <5456533+CarlosNZ@users.noreply.github.com> Date: Wed, 29 Jan 2025 15:50:35 +1300 Subject: [PATCH 1/2] Handle clipboard errors --- README.md | 4 +++- demo/src/App.tsx | 25 +++++++++++++++++-------- src/ButtonPanels.tsx | 40 ++++++++++++++++++++++++++++++++++++---- src/types.ts | 4 +++- 4 files changed, 59 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index a5074dfb..e8902bd5 100644 --- a/README.md +++ b/README.md @@ -246,9 +246,11 @@ A similar callback is executed whenever an item is copied to the clipboard (if p type // Either "path" or "value" depending on whether "Cmd/Ctrl" was pressed stringValue // A nicely stringified version of `value` // (i.e. what the clipboard actually receives) + success // true/false -- whether clipboard copy action actually succeeded + errorMessage// Error detail if success === false ``` -Since there is very little user feedback when clicking "Copy", a good idea would be to present some kind of notification in this callback. +Since there is very little user feedback when clicking "Copy", a good idea would be to present some kind of notification in this callback. There are situations (such as an insecure environment) where the browser won't actually permit any clipboard actions. In this case, the `success` property will be `false`, so you can handle it appropriately. ### Custom Buttons diff --git a/demo/src/App.tsx b/demo/src/App.tsx index 1547db26..c75887ac 100644 --- a/demo/src/App.tsx +++ b/demo/src/App.tsx @@ -356,14 +356,23 @@ function App() { } enableClipboard={ allowCopy - ? ({ stringValue, type }) => - toast({ - title: `${type === 'value' ? 'Value' : 'Path'} copied to clipboard:`, - description: truncate(String(stringValue)), - status: 'success', - duration: 5000, - isClosable: true, - }) + ? ({ stringValue, type, success, errorMessage }) => { + success + ? toast({ + title: `${type === 'value' ? 'Value' : 'Path'} copied to clipboard:`, + description: truncate(String(stringValue)), + status: 'success', + duration: 5000, + isClosable: true, + }) + : toast({ + title: 'Problem copying to clipboard', + description: errorMessage, + status: 'error', + duration: 5000, + isClosable: true, + }) + } : false } restrictEdit={restrictEdit} diff --git a/src/ButtonPanels.tsx b/src/ButtonPanels.tsx index 3b6ecb57..a44c84f7 100644 --- a/src/ButtonPanels.tsx +++ b/src/ButtonPanels.tsx @@ -10,6 +10,7 @@ import { type NodeData, type CustomButtonDefinition, type KeyboardControlsFull, + JsonData, } from './types' import { getModifier } from './helpers' @@ -67,8 +68,10 @@ export const EditButtons: React.FC = ({ const handleCopy = (e: React.MouseEvent) => { e.stopPropagation() let copyType: CopyType = 'value' - let value + let value: JsonData let stringValue = '' + let success: boolean + let errorMessage: string | null = null if (enableClipboard) { const modifier = getModifier(e) if (modifier && keyboardControls.clipboardModifier.includes(modifier)) { @@ -79,10 +82,39 @@ export const EditButtons: React.FC = ({ value = data stringValue = type ? JSON.stringify(data, null, 2) : String(value) } - void navigator.clipboard?.writeText(stringValue) - if (typeof enableClipboard === 'function') { - enableClipboard({ value, stringValue, path, key, type: copyType }) + if (!navigator.clipboard) { + if (typeof enableClipboard === 'function') + enableClipboard({ + success: false, + value, + stringValue, + path, + key, + type: copyType, + errorMessage: "Can't access clipboard API", + }) + return } + navigator.clipboard + ?.writeText(stringValue) + .then(() => (success = true)) + .catch((err) => { + success = false + errorMessage = err.message + }) + .finally(() => { + if (typeof enableClipboard === 'function') { + enableClipboard({ + success, + errorMessage, + value, + stringValue, + path, + key, + type: copyType, + }) + } + }) } } diff --git a/src/types.ts b/src/types.ts index e24e7c1b..52b2415b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -132,6 +132,8 @@ export type SearchFilterInputFunction = ( export type CopyType = 'path' | 'value' export type CopyFunction = (input: { + success: boolean + errorMessage: string | null key: CollectionKey path: CollectionKey[] value: unknown @@ -201,7 +203,7 @@ export interface NodeData { path: CollectionKey[] level: number index: number - value: unknown + value: JsonData size: number | null parentData: object | null fullData: JsonData From 3324d2da5031600463395f4e39675cf33ddeb444 Mon Sep 17 00:00:00 2001 From: Carl Smith <5456533+CarlosNZ@users.noreply.github.com> Date: Wed, 29 Jan 2025 22:01:46 +1300 Subject: [PATCH 2/2] Update ButtonPanels.tsx --- src/ButtonPanels.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ButtonPanels.tsx b/src/ButtonPanels.tsx index a44c84f7..01a0ef94 100644 --- a/src/ButtonPanels.tsx +++ b/src/ButtonPanels.tsx @@ -10,7 +10,7 @@ import { type NodeData, type CustomButtonDefinition, type KeyboardControlsFull, - JsonData, + type JsonData, } from './types' import { getModifier } from './helpers'