From 0bbeb20595e9131e28bf864d26528e1f4c97527e Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Sun, 21 Sep 2025 15:34:10 -0400 Subject: [PATCH] fix(web): only copy images via canvas (#22225) --- i18n/en.json | 1 - .../asset-viewer/asset-viewer-nav-bar.svelte | 3 ++- .../asset-viewer/photo-viewer.svelte | 13 +++------- web/src/lib/utils/asset-utils.ts | 25 +++---------------- 4 files changed, 8 insertions(+), 34 deletions(-) diff --git a/i18n/en.json b/i18n/en.json index c7145df889..b817ab375f 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -920,7 +920,6 @@ "cant_get_number_of_comments": "Can't get number of comments", "cant_search_people": "Can't search people", "cant_search_places": "Can't search places", - "clipboard_unsupported_mime_type": "The system clipboard does not support copying this type of content: {mimeType}", "error_adding_assets_to_album": "Error adding assets to album", "error_adding_users_to_album": "Error adding users to album", "error_deleting_shared_user": "Error deleting shared user", diff --git a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte index a2f4c51683..bbdd0dcd5f 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte @@ -23,6 +23,7 @@ import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; import { AppRoute } from '$lib/constants'; + import { photoViewerImgElement } from '$lib/stores/assets-store.svelte'; import { featureFlags } from '$lib/stores/server-config.store'; import { user } from '$lib/stores/user.store'; import { photoZoomState } from '$lib/stores/zoom-image.store'; @@ -151,7 +152,7 @@ onclick={onZoomImage} /> {/if} - {#if canCopyImageToClipboard() && asset.type === AssetTypeEnum.Image} + {#if canCopyImageToClipboard() && asset.type === AssetTypeEnum.Image && $photoViewerImgElement} { - if (!canCopyImageToClipboard()) { + if (!canCopyImageToClipboard() || !$photoViewerImgElement) { return; } try { - const result = await copyImageToClipboard($photoViewerImgElement ?? assetFileUrl); - if (result.success) { - notificationController.show({ type: NotificationType.Info, message: $t('copied_image_to_clipboard') }); - } else { - notificationController.show({ - type: NotificationType.Error, - message: $t('errors.clipboard_unsupported_mime_type', { values: { mimeType: result.mimeType } }), - }); - } + await copyImageToClipboard($photoViewerImgElement); + notificationController.show({ type: NotificationType.Info, message: $t('copied_image_to_clipboard') }); } catch (error) { handleError(error, $t('copy_error')); } diff --git a/web/src/lib/utils/asset-utils.ts b/web/src/lib/utils/asset-utils.ts index 016ca410a0..25e045c8a1 100644 --- a/web/src/lib/utils/asset-utils.ts +++ b/web/src/lib/utils/asset-utils.ts @@ -620,26 +620,7 @@ const imgToBlob = async (imageElement: HTMLImageElement) => { throw new Error('Canvas context is null'); }; -const urlToBlob = async (imageSource: string) => { - const response = await fetch(imageSource); - return await response.blob(); -}; - -export const copyImageToClipboard = async ( - source: HTMLImageElement | string, -): Promise<{ success: true } | { success: false; mimeType: string }> => { - if (source instanceof HTMLImageElement) { - // do not await, so the Safari clipboard write happens in the context of the user gesture - await navigator.clipboard.write([new ClipboardItem({ ['image/png']: imgToBlob(source) })]); - return { success: true }; - } - - // if we had a way to get the mime type synchronously, we could do the same thing here - const blob = await urlToBlob(source); - if (!ClipboardItem.supports(blob.type)) { - return { success: false, mimeType: blob.type }; - } - - await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]); - return { success: true }; +export const copyImageToClipboard = async (source: HTMLImageElement) => { + // do not await, so the Safari clipboard write happens in the context of the user gesture + await navigator.clipboard.write([new ClipboardItem({ ['image/png']: imgToBlob(source) })]); };