From b0a4c0df4fea176159b4ca70f05dcf620796b526 Mon Sep 17 00:00:00 2001 From: Tania Mathern Date: Wed, 23 Aug 2023 14:53:31 -0700 Subject: [PATCH 1/5] Load high res assets into clipboard --- .../aem-asset-selector-util.js | 104 ++++++++++++++---- 1 file changed, 83 insertions(+), 21 deletions(-) diff --git a/blocks/aem-asset-selector/aem-asset-selector-util.js b/blocks/aem-asset-selector/aem-asset-selector-util.js index 2e633842e..f33b15c97 100644 --- a/blocks/aem-asset-selector/aem-asset-selector-util.js +++ b/blocks/aem-asset-selector/aem-asset-selector-util.js @@ -63,6 +63,13 @@ const REL_RENDITIONS = 'http://ns.adobe.com/adobecloud/rel/rendition'; // TODO: change this to Asset Link IMS client ID const IMS_CLIENT_ID = 'p66302-franklin'; const ASSET_SELECTOR_ID = 'asset-selector'; +const CLIPBOARD_SUPPORTED_BINARY_MIMETYPES = [ + 'image/png', +]; +const SUPPORTED_RENDITIONS_FORMATS = [ + 'image/png', + 'image/jpeg', +]; let imsInstance = null; let imsEnvironment = IMS_ENV_PROD; @@ -200,12 +207,16 @@ function getCopyRendition(asset) { // TODO: the clipboard API only supports using PNGs as the blob, so // only using PNG renditions. Will be fixed to allow altnernate // formats in a follow-up ticket - .filter((rendition) => rendition.type === 'image/png') .forEach((rendition) => { - if ((!maxRendition || maxRendition.width < rendition.width)) { + if (SUPPORTED_RENDITIONS_FORMATS.includes(rendition.type) + && (!maxRendition || maxRendition.width < rendition.width)) { maxRendition = rendition; } }); + + console.log("~~~~~~~~~ Picked rendition: ") + console.log(maxRendition) + console.log("~~~~~~~~~ ") return maxRendition; } @@ -227,6 +238,42 @@ async function copyToClipboardWithHtml(assetPublicUrl) { return navigator.clipboard.write(data); } +async function loadImageIntoHtmlElement(url) { + return new Promise((resolve, reject) => { + const img = new Image(); + /* + crossorigin needs to be anonymous to allow the future canvas export, + otherwise the canvas will be considered tainted and the export will fail + */ + img.setAttribute('crossorigin', 'anonymous'); + img.addEventListener('load', () => resolve(img)); + img.addEventListener('error', (err) => reject(err)); + img.src = url; + }); +} + +/** + * Convert an image using an URL to a another image format + * @param {*} assetPublicUrl Public asset URL + * @param {*} targetMimeType Target mimetype (target format) + * @returns A conversion promise resolving to a blob of the target mimetype + */ +async function convertImage(assetPublicUrl, targetMimeType='image/png', asset) { + const main = document.querySelector('main'); + const imageElement = await loadImageIntoHtmlElement(assetPublicUrl); + + console.log('## convertImage: ', assetPublicUrl) + return new Promise(resolve => { + const canvas = document.createElement('canvas'); + canvas.width = asset['tiff:imageWidth']; + canvas.height = asset['tiff:imageLength']; + + const context = canvas.getContext('2d'); + context.drawImage(imageElement, 0, 0); + canvas.toBlob(resolve, targetMimeType); + }); +} + /** * Uses the navigator global object to write a clipboard item to the clipboard. * The clipboard item's content will be a Blob containing the image binary from @@ -235,22 +282,37 @@ async function copyToClipboardWithHtml(assetPublicUrl) { * @param {string} mimeType Content type of the image being retrieved. * @returns {Promise} Resolves when the item has been written to the clipboard. */ -async function copyToClipboardWithBinary(assetPublicUrl, mimeType) { - const binary = await fetch(assetPublicUrl); +async function copyToClipboardWithBinary(assetPublicUrl, mimeType, asset) { + let data; + console.log("## copyToClipboardWithBinary") + console.log(mimeType); + if (!CLIPBOARD_SUPPORTED_BINARY_MIMETYPES.includes(mimeType)) { + const copiedBlob = await convertImage(assetPublicUrl, 'image/png', asset); - if (!binary || !binary.ok) { - throw new Error(`Unexpected status code ${binary.status} retrieving asset binary`); - } + const clipboardOptions = {}; + clipboardOptions['image/png'] = copiedBlob; + data = [ + new ClipboardItem(clipboardOptions), + ]; + } else { + const binary = await fetch(assetPublicUrl); - const blob = await binary.blob(); - if (!blob) { - throw new Error('No blob provided in asset response'); + if (!binary || !binary.ok) { + throw new Error(`Unexpected status code ${binary.status} retrieving asset binary`); + } + + const blob = await binary.blob(); + if (!blob) { + throw new Error('No blob provided in asset response'); + } + + const clipboardOptions = {}; + clipboardOptions[mimeType] = blob; + data = [ + new ClipboardItem(clipboardOptions), + ]; } - const clipboardOptions = {}; - clipboardOptions[mimeType] = blob; - const data = [ - new ClipboardItem(clipboardOptions), - ]; + return navigator.clipboard.write(data); } @@ -306,7 +368,7 @@ export async function copyAssetWithRapi(asset) { } const download = getRel(rendition, REL_DOWNLOAD); if (!download || !download.href) { - logMessage('Rendition does not contain sufficient information'); + console.log('Rendition does not contain sufficient information'); return false; } try { @@ -317,17 +379,17 @@ export async function copyAssetWithRapi(asset) { }, }); if (!res.ok) { - logMessage(`Download request for rendition binary failed with status code ${res.status}: ${res.statusText}`); + console.log(`Download request for rendition binary failed with status code ${res.status}: ${res.statusText}`); return false; } const downloadJson = await res.json(); if (!downloadJson) { - logMessage('Rendition download JSON not provided'); + console.log('Rendition download JSON not provided'); return false; } - await copyToClipboardWithBinary(downloadJson.href, downloadJson.type); + await copyToClipboardWithBinary(downloadJson.href, downloadJson.type, asset); } catch (e) { - logMessage('error copying asset to clipboard', e); + console.log('Error copying asset using R-API to clipboard', e); return false; } @@ -352,7 +414,7 @@ function getAssetSelector() { function handleAssetSelection(selection, cfg) { if (cfg) { if (selection.length && cfg.onAssetSelected) { - if (selection.length > 0) { + if (selection.length > 1) { logMessage('Multiple items received in selection, but only the first will be used'); } cfg.onAssetSelected(selection[0]); From e6edd5450aa3f00bf3fe247c08113a1004be87a4 Mon Sep 17 00:00:00 2001 From: Tania Mathern Date: Wed, 23 Aug 2023 14:55:16 -0700 Subject: [PATCH 2/5] Remove debug logs --- .../aem-asset-selector-util.js | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/blocks/aem-asset-selector/aem-asset-selector-util.js b/blocks/aem-asset-selector/aem-asset-selector-util.js index f33b15c97..f0d149bcc 100644 --- a/blocks/aem-asset-selector/aem-asset-selector-util.js +++ b/blocks/aem-asset-selector/aem-asset-selector-util.js @@ -213,10 +213,6 @@ function getCopyRendition(asset) { maxRendition = rendition; } }); - - console.log("~~~~~~~~~ Picked rendition: ") - console.log(maxRendition) - console.log("~~~~~~~~~ ") return maxRendition; } @@ -259,10 +255,8 @@ async function loadImageIntoHtmlElement(url) { * @returns A conversion promise resolving to a blob of the target mimetype */ async function convertImage(assetPublicUrl, targetMimeType='image/png', asset) { - const main = document.querySelector('main'); const imageElement = await loadImageIntoHtmlElement(assetPublicUrl); - console.log('## convertImage: ', assetPublicUrl) return new Promise(resolve => { const canvas = document.createElement('canvas'); canvas.width = asset['tiff:imageWidth']; @@ -284,8 +278,7 @@ async function convertImage(assetPublicUrl, targetMimeType='image/png', asset) { */ async function copyToClipboardWithBinary(assetPublicUrl, mimeType, asset) { let data; - console.log("## copyToClipboardWithBinary") - console.log(mimeType); + if (!CLIPBOARD_SUPPORTED_BINARY_MIMETYPES.includes(mimeType)) { const copiedBlob = await convertImage(assetPublicUrl, 'image/png', asset); @@ -368,7 +361,7 @@ export async function copyAssetWithRapi(asset) { } const download = getRel(rendition, REL_DOWNLOAD); if (!download || !download.href) { - console.log('Rendition does not contain sufficient information'); + logMessage('Rendition does not contain sufficient information'); return false; } try { @@ -379,17 +372,17 @@ export async function copyAssetWithRapi(asset) { }, }); if (!res.ok) { - console.log(`Download request for rendition binary failed with status code ${res.status}: ${res.statusText}`); + logMessage(`Download request for rendition binary failed with status code ${res.status}: ${res.statusText}`); return false; } const downloadJson = await res.json(); if (!downloadJson) { - console.log('Rendition download JSON not provided'); + logMessage('Rendition download JSON not provided'); return false; } await copyToClipboardWithBinary(downloadJson.href, downloadJson.type, asset); } catch (e) { - console.log('Error copying asset using R-API to clipboard', e); + logMessage('Error copying asset using R-API to clipboard', e); return false; } From 2af6c8e744f9199613a5d920da1dc23386794d50 Mon Sep 17 00:00:00 2001 From: Tania Mathern Date: Wed, 23 Aug 2023 15:16:25 -0700 Subject: [PATCH 3/5] Make linter happy --- blocks/aem-asset-selector/aem-asset-selector-util.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blocks/aem-asset-selector/aem-asset-selector-util.js b/blocks/aem-asset-selector/aem-asset-selector-util.js index f0d149bcc..fe910f47b 100644 --- a/blocks/aem-asset-selector/aem-asset-selector-util.js +++ b/blocks/aem-asset-selector/aem-asset-selector-util.js @@ -254,10 +254,10 @@ async function loadImageIntoHtmlElement(url) { * @param {*} targetMimeType Target mimetype (target format) * @returns A conversion promise resolving to a blob of the target mimetype */ -async function convertImage(assetPublicUrl, targetMimeType='image/png', asset) { +async function convertImage(assetPublicUrl, targetMimeType, asset) { const imageElement = await loadImageIntoHtmlElement(assetPublicUrl); - return new Promise(resolve => { + return new Promise((resolve) => { const canvas = document.createElement('canvas'); canvas.width = asset['tiff:imageWidth']; canvas.height = asset['tiff:imageLength']; From 8382343d985cfd95707b3e7ab17f5e0224ed027d Mon Sep 17 00:00:00 2001 From: Tania Mathern Date: Wed, 23 Aug 2023 15:24:23 -0700 Subject: [PATCH 4/5] Refactor --- .../aem-asset-selector-util.js | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/blocks/aem-asset-selector/aem-asset-selector-util.js b/blocks/aem-asset-selector/aem-asset-selector-util.js index fe910f47b..896b6224b 100644 --- a/blocks/aem-asset-selector/aem-asset-selector-util.js +++ b/blocks/aem-asset-selector/aem-asset-selector-util.js @@ -254,10 +254,10 @@ async function loadImageIntoHtmlElement(url) { * @param {*} targetMimeType Target mimetype (target format) * @returns A conversion promise resolving to a blob of the target mimetype */ -async function convertImage(assetPublicUrl, targetMimeType, asset) { +async function convertImage(assetPublicUrl, targetMimeType='image/png', asset) { const imageElement = await loadImageIntoHtmlElement(assetPublicUrl); - return new Promise((resolve) => { + return new Promise(resolve => { const canvas = document.createElement('canvas'); canvas.width = asset['tiff:imageWidth']; canvas.height = asset['tiff:imageLength']; @@ -277,16 +277,12 @@ async function convertImage(assetPublicUrl, targetMimeType, asset) { * @returns {Promise} Resolves when the item has been written to the clipboard. */ async function copyToClipboardWithBinary(assetPublicUrl, mimeType, asset) { - let data; + let clipboardOptions = {}; if (!CLIPBOARD_SUPPORTED_BINARY_MIMETYPES.includes(mimeType)) { - const copiedBlob = await convertImage(assetPublicUrl, 'image/png', asset); - - const clipboardOptions = {}; - clipboardOptions['image/png'] = copiedBlob; - data = [ - new ClipboardItem(clipboardOptions), - ]; + const clipboardTargetMimetype = 'image/png'; + const copiedBlob = await convertImage(assetPublicUrl, clipboardTargetMimetype, asset); + clipboardOptions[clipboardTargetMimetype] = copiedBlob; } else { const binary = await fetch(assetPublicUrl); @@ -298,14 +294,13 @@ async function copyToClipboardWithBinary(assetPublicUrl, mimeType, asset) { if (!blob) { throw new Error('No blob provided in asset response'); } - - const clipboardOptions = {}; clipboardOptions[mimeType] = blob; - data = [ - new ClipboardItem(clipboardOptions), - ]; } + const data = [ + new ClipboardItem(clipboardOptions), + ]; + return navigator.clipboard.write(data); } From a720a96486af222ca87c25491cde45a381e733f1 Mon Sep 17 00:00:00 2001 From: Tania Mathern Date: Thu, 24 Aug 2023 09:05:42 -0700 Subject: [PATCH 5/5] Make linter happy --- blocks/aem-asset-selector/aem-asset-selector-util.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/blocks/aem-asset-selector/aem-asset-selector-util.js b/blocks/aem-asset-selector/aem-asset-selector-util.js index 896b6224b..2fa658909 100644 --- a/blocks/aem-asset-selector/aem-asset-selector-util.js +++ b/blocks/aem-asset-selector/aem-asset-selector-util.js @@ -254,10 +254,10 @@ async function loadImageIntoHtmlElement(url) { * @param {*} targetMimeType Target mimetype (target format) * @returns A conversion promise resolving to a blob of the target mimetype */ -async function convertImage(assetPublicUrl, targetMimeType='image/png', asset) { +async function convertImage(assetPublicUrl, targetMimeType, asset) { const imageElement = await loadImageIntoHtmlElement(assetPublicUrl); - return new Promise(resolve => { + return new Promise((resolve) => { const canvas = document.createElement('canvas'); canvas.width = asset['tiff:imageWidth']; canvas.height = asset['tiff:imageLength']; @@ -277,7 +277,7 @@ async function convertImage(assetPublicUrl, targetMimeType='image/png', asset) { * @returns {Promise} Resolves when the item has been written to the clipboard. */ async function copyToClipboardWithBinary(assetPublicUrl, mimeType, asset) { - let clipboardOptions = {}; + const clipboardOptions = {}; if (!CLIPBOARD_SUPPORTED_BINARY_MIMETYPES.includes(mimeType)) { const clipboardTargetMimetype = 'image/png';