mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-19 22:09:37 +00:00
## Summary
Fix unhandled promise rejection ("Document is not focused") in Copy
Image and improve clipboard fallback reliability.
## Changes
- **What**: Two clipboard fixes:
1. `litegraphService.ts`: The "Copy Image" context menu passed async
`writeImage` as a callback to `canvas.toBlob()` without awaiting —
errors became unhandled promise rejections reported in [Sentry
CLOUD-FRONTEND-STAGING-AQ](https://comfy-org.sentry.io/issues/6948073569/).
Extracted `convertToPngBlob` helper that wraps `toBlob` in a proper
Promise so errors propagate to the existing outer try/catch and surface
as a user-facing toast instead of a silent Sentry error.
2. `useCopyToClipboard.ts`: Replaced `useClipboard({ legacy: true })`
with explicit modern→legacy fallback that checks
`document.execCommand('copy')` return value. VueUse's `legacyCopy` sets
`copied.value = true` regardless of whether `execCommand` succeeded,
causing false success toasts.
## Review Focus
- The `convertToPngBlob` helper does the same canvas→PNG work as the old
inline code but properly awaited
- The happy path (PNG clipboard write succeeds first try) is unchanged
- No public API surface changes — verified zero custom node dependencies
via ecosystem code search
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9299-fix-handle-clipboard-errors-in-Copy-Image-and-useCopyToClipboard-3156d73d3650817c8608cba861ee64a9)
by [Unito](https://www.unito.io)
66 lines
1.4 KiB
TypeScript
66 lines
1.4 KiB
TypeScript
import { useClipboard } from '@vueuse/core'
|
|
import { useToast } from 'primevue/usetoast'
|
|
|
|
import { t } from '@/i18n'
|
|
|
|
function legacyCopy(text: string): boolean {
|
|
const textarea = document.createElement('textarea')
|
|
textarea.setAttribute('readonly', '')
|
|
textarea.value = text
|
|
textarea.style.position = 'fixed'
|
|
textarea.style.left = '-9999px'
|
|
textarea.style.top = '-9999px'
|
|
document.body.appendChild(textarea)
|
|
textarea.select()
|
|
try {
|
|
return document.execCommand('copy')
|
|
} finally {
|
|
textarea.remove()
|
|
}
|
|
}
|
|
|
|
export function useCopyToClipboard() {
|
|
const { copy, isSupported } = useClipboard()
|
|
const toast = useToast()
|
|
|
|
async function copyToClipboard(text: string) {
|
|
let success = false
|
|
|
|
try {
|
|
if (isSupported.value) {
|
|
await copy(text)
|
|
success = true
|
|
}
|
|
} catch {
|
|
// Modern clipboard API failed, fall through to legacy
|
|
}
|
|
|
|
if (!success) {
|
|
try {
|
|
success = legacyCopy(text)
|
|
} catch {
|
|
// Legacy also failed
|
|
}
|
|
}
|
|
|
|
toast.add(
|
|
success
|
|
? {
|
|
severity: 'success',
|
|
summary: t('g.success'),
|
|
detail: t('clipboard.successMessage'),
|
|
life: 3000
|
|
}
|
|
: {
|
|
severity: 'error',
|
|
summary: t('g.error'),
|
|
detail: t('clipboard.errorMessage')
|
|
}
|
|
)
|
|
}
|
|
|
|
return {
|
|
copyToClipboard
|
|
}
|
|
}
|