diff --git a/apps/web-twig-demo/assets/scripts/file-uploader-meta-data.ts b/apps/web-twig-demo/assets/scripts/file-uploader-meta-data.ts index 223762bd6b..586e006c1e 100644 --- a/apps/web-twig-demo/assets/scripts/file-uploader-meta-data.ts +++ b/apps/web-twig-demo/assets/scripts/file-uploader-meta-data.ts @@ -69,8 +69,8 @@ window.addEventListener('DOMContentLoaded', () => { const customEdit = (event) => { const key = event.target.closest('li').id; const newMeta = toggleMetaData - ? { x: 30, y: 30, width: 150, height: 150 } - : { x: 22, y: 0, width: 110, height: 100 }; + ? { x: 30, y: 30, cropWidth: 150, cropHeight: 150, originalWidth: 560, originalHeight: 330 } + : { x: 22, y: 0, cropWidth: 110, cropHeight: 100, originalWidth: 560, originalHeight: 330 }; toggleMetaData = !toggleMetaData; const file = FileUploaderInstance.getFileFromQueue(key).file; FileUploaderInstance.updateQueue(key, file, newMeta, updateQueueCallback); diff --git a/packages/web-react/src/components/FileUploader/FileUploaderAttachment.tsx b/packages/web-react/src/components/FileUploader/FileUploaderAttachment.tsx index 35e55c6160..1efc5f71ca 100644 --- a/packages/web-react/src/components/FileUploader/FileUploaderAttachment.tsx +++ b/packages/web-react/src/components/FileUploader/FileUploaderAttachment.tsx @@ -6,7 +6,12 @@ import { Icon } from '../Icon'; import AttachmentActionButton from './AttachmentActionButton'; import AttachmentDismissButton from './AttachmentDismissButton'; import AttachmentImagePreview from './AttachmentImagePreview'; -import { DEFAULT_BUTTON_LABEL, DEFAULT_EDIT_BUTTON_LABEL, DEFAULT_ICON_NAME } from './constants'; +import { + DEFAULT_BUTTON_LABEL, + DEFAULT_EDIT_BUTTON_LABEL, + DEFAULT_ICON_NAME, + IMAGE_PREVIEW_BASE64_MAX_WIDTH, +} from './constants'; import { useFileUploaderAttachment } from './useFileUploaderAttachment'; import { useFileUploaderStyleProps } from './useFileUploaderStyleProps'; import { image2Base64Preview } from './utils'; @@ -50,7 +55,9 @@ const FileUploaderAttachment = (props: SpiritFileUploaderAttachmentProps) => { const onEditHandler = (event: MouseEvent) => onEdit && onEdit(event, file); if (isFileImage) { - image2Base64Preview(file, 100, (compressedDataURL) => setImagePreview(compressedDataURL)); + image2Base64Preview(file, IMAGE_PREVIEW_BASE64_MAX_WIDTH, (compressedDataURL) => + setImagePreview(compressedDataURL), + ); } useFileUploaderAttachment({ attachmentRef, file, name, meta, onError }); diff --git a/packages/web-react/src/components/FileUploader/README.md b/packages/web-react/src/components/FileUploader/README.md index 2c2cadcfb1..4e1e69006b 100644 --- a/packages/web-react/src/components/FileUploader/README.md +++ b/packages/web-react/src/components/FileUploader/README.md @@ -329,13 +329,13 @@ const customUpdate = (_event: MouseEvent, file: File) => { #### Updating Image Preview with cropped image When you are using FileUploader with some kind of image cropper you want to also update the image preview on FileUploaderAttachment when image changes. -You can do this by passing a specific object in shape of coordinates (`{ x: number, y: number, width: number, height: number }`) to the `meta` argument. +You can do this by passing a specific object in shape of coordinates (`{ x: number, y: number, cropWidth: number, cropHeight: number, originalWidth: number, originalHeight: number }`) to the `meta` argument. Then the coordinates will be applied to the preview image in the attachment. ```javascript // … const customUpdate = (_event: MouseEvent, file: File) => { - const meta = { x: 30, y: 30, width: 150, height: 150 }; + const meta = { x: 30, y: 30, cropWidth: 150, cropHeight: 150, originalWidth: 560, originalHeight: 330 }; return updateQueue(file.name, file, meta); }; diff --git a/packages/web-react/src/components/FileUploader/__tests__/useFileUploaderStyleProps.test.ts b/packages/web-react/src/components/FileUploader/__tests__/useFileUploaderStyleProps.test.ts index b35eb436a1..1fd5525fd5 100644 --- a/packages/web-react/src/components/FileUploader/__tests__/useFileUploaderStyleProps.test.ts +++ b/packages/web-react/src/components/FileUploader/__tests__/useFileUploaderStyleProps.test.ts @@ -56,16 +56,16 @@ describe('useFileUploaderStyleProps', () => { it('should have CSS for crop image', () => { const { result } = renderHook(() => useFileUploaderStyleProps({ - meta: { x: 1, y: 2, width: 3, height: 4 }, + meta: { x: 0, y: 0, cropWidth: 100, cropHeight: 100, originalWidth: 350, originalHeight: 200 }, }), ); expect(result.current.classProps.imageCropStyles).toBeDefined(); expect(result.current.classProps.imageCropStyles).toStrictEqual({ - '--file-uploader-attachment-image-height': '4px', - '--file-uploader-attachment-image-left': '-1px', - '--file-uploader-attachment-image-top': '-2px', - '--file-uploader-attachment-image-width': '3px', + '--file-uploader-attachment-image-height': '108px', + '--file-uploader-attachment-image-left': '-0px', + '--file-uploader-attachment-image-top': '-0px', + '--file-uploader-attachment-image-width': '189px', }); }); }); diff --git a/packages/web-react/src/components/FileUploader/constants.ts b/packages/web-react/src/components/FileUploader/constants.ts index f2563b7c14..b18053e27a 100644 --- a/packages/web-react/src/components/FileUploader/constants.ts +++ b/packages/web-react/src/components/FileUploader/constants.ts @@ -1,6 +1,7 @@ export const DEFAULT_FILE_SIZE_LIMIT = 10000000; // = 10 MB export const DEFAULT_FILE_QUEUE_LIMIT = 10; -export const IMAGE_DIMENSION = 54; // px +export const IMAGE_DIMENSION = 54; // px; @see: CSS class `.FileUploaderAttachment__image` in _FileUploaderAttachment.scss +export const IMAGE_PREVIEW_BASE64_MAX_WIDTH = 500; // px export const DEFAULT_ERROR_MESSAGE_MAX_FILE_SIZE = 'The file size limit has been exceeded'; export const DEFAULT_ERROR_MESSAGE_QUEUE_DUPLICITY = 'This file already exists in the queue'; diff --git a/packages/web-react/src/components/FileUploader/demo/FileUploaderMetaData.tsx b/packages/web-react/src/components/FileUploader/demo/FileUploaderMetaData.tsx index 906b7ba01d..1cc4532271 100644 --- a/packages/web-react/src/components/FileUploader/demo/FileUploaderMetaData.tsx +++ b/packages/web-react/src/components/FileUploader/demo/FileUploaderMetaData.tsx @@ -1,8 +1,8 @@ -import React, { useState, MouseEvent } from 'react'; +import React, { MouseEvent, useState } from 'react'; +import { FileUploader, FileUploaderAttachment, FileUploaderInput, FileUploaderList, useFileQueue } from '..'; import { SpiritFileUploaderAttachmentProps } from '../../../types'; import { Button } from '../../Button'; -import { Modal, ModalDialog, ModalBody, ModalFooter } from '../../Modal'; -import { FileUploader, FileUploaderAttachment, FileUploaderInput, FileUploaderList, useFileQueue } from '..'; +import { Modal, ModalBody, ModalDialog, ModalFooter } from '../../Modal'; const FileUploaderMetaData = () => { const [isModalOpen, setIsModalOpen] = useState(false); @@ -34,7 +34,9 @@ const FileUploaderMetaData = () => { }; const customUpdate = (_event: MouseEvent, file: File) => { - const newMeta = toggleMeta ? { x: 30, y: 30, width: 150, height: 150 } : { x: 22, y: 0, width: 110, height: 100 }; + const newMeta = toggleMeta + ? { x: 30, y: 30, cropWidth: 150, cropHeight: 150, originalWidth: 560, originalHeight: 330 } + : { x: 22, y: 0, cropWidth: 110, cropHeight: 100, originalWidth: 560, originalHeight: 330 }; setIsModalOpen(false); setToggleMeta(!toggleMeta); diff --git a/packages/web-react/src/components/FileUploader/useFileUploaderStyleProps.ts b/packages/web-react/src/components/FileUploader/useFileUploaderStyleProps.ts index 50eaf50e99..ba711bbbca 100644 --- a/packages/web-react/src/components/FileUploader/useFileUploaderStyleProps.ts +++ b/packages/web-react/src/components/FileUploader/useFileUploaderStyleProps.ts @@ -1,8 +1,9 @@ -import { CSSProperties } from 'react'; import classNames from 'classnames'; +import { CSSProperties } from 'react'; import { FileUploaderCropCSS } from '../../constants/dictionaries'; import { useClassNamePrefix } from '../../hooks'; import { FileMetadata, FileUploaderQueueLimitBehaviorType, Validation } from '../../types'; +import { IMAGE_DIMENSION } from './constants'; export interface FileUploaderStyleProps extends Validation { imageObjectFit?: 'contain' | 'cover'; @@ -28,6 +29,15 @@ type ImageObjectFit = { '--file-uploader-attachment-image-object-fit': string; }; +type ImageCropMeta = { + x: number; + y: number; + cropWidth: number; + cropHeight: number; + originalWidth: number; + originalHeight: number; +}; + export interface FileUploaderStyleReturn { /** className props */ classProps: { @@ -88,16 +98,32 @@ export const useFileUploaderStyleProps = (props?: FileUploaderStyleProps): FileU const { meta, imageObjectFit } = props || {}; let imageCropCSS: ImageCropCSS | undefined; let imageObjectFitCSS: ImageObjectFit | undefined; - const hasCoords = meta && meta.x != null && meta.y != null && meta.width != null && meta.height != null; + const hasCoordsInMeta = + meta != null && + ['x', 'y', 'cropWidth', 'cropHeight', 'originalWidth', 'originalHeight'].every((coord) => meta[coord] != null); + + if (hasCoordsInMeta) { + const { x, y, cropWidth, cropHeight, originalWidth, originalHeight } = meta as ImageCropMeta; + const previewHeight = IMAGE_DIMENSION; + let scale; + if (cropHeight > cropWidth) { + // scale for portrait images + scale = previewHeight / cropWidth; + } else { + // scale for landscape images + scale = previewHeight / cropHeight; + } - if (hasCoords) { - const { x, y, width, height } = meta; + const cropX = Math.round(x * scale); + const cropY = Math.round(y * scale); + const imageWidth = Math.round(originalWidth * scale); + const imageHeight = Math.round(originalHeight * scale); imageCropCSS = { - [FileUploaderCropCSS.TOP]: `-${y}px`, - [FileUploaderCropCSS.LEFT]: `-${x}px`, - [FileUploaderCropCSS.WIDTH]: `${width}px`, - [FileUploaderCropCSS.HEIGHT]: `${height}px`, + [FileUploaderCropCSS.TOP]: `-${cropY}px`, + [FileUploaderCropCSS.LEFT]: `-${cropX}px`, + [FileUploaderCropCSS.WIDTH]: `${imageWidth}px`, + [FileUploaderCropCSS.HEIGHT]: `${imageHeight}px`, }; } @@ -141,7 +167,7 @@ export const useFileUploaderStyleProps = (props?: FileUploaderStyleProps): FileU image: fileUploaderAttachmentImageClass, slot: fileUploaderAttachmentSlotClass, }, - ...(hasCoords && { imageCropStyles: imageCropCSS }), + ...(hasCoordsInMeta && { imageCropStyles: imageCropCSS }), ...(imageObjectFit && { attachmentStyles: imageObjectFitCSS }), }, }; diff --git a/packages/web/src/js/FileUploader.ts b/packages/web/src/js/FileUploader.ts index 7b6b15007e..d7afcb39ed 100644 --- a/packages/web/src/js/FileUploader.ts +++ b/packages/web/src/js/FileUploader.ts @@ -1,6 +1,6 @@ -import { EventHandler, SelectorEngine } from './dom'; import BaseComponent from './BaseComponent'; -import { image2Base64Preview, enableToggleAutoloader } from './utils'; +import { EventHandler, SelectorEngine } from './dom'; +import { enableToggleAutoloader, image2Base64Preview } from './utils'; const NAME = 'fileUploader'; const EVENT_KEY = `.${NAME}`; @@ -31,6 +31,8 @@ const DEFAULT_ERROR_MESSAGES = { errorMaxUploadedFiles: 'You have exceeded the number of files allowed in the queue', errorFileNotSupported: 'is not a supported file. Please ensure you are uploading a supported file format.', }; +const IMAGE_PREVIEW_HEIGHT = 54; // px; @see: CSS class `.FileUploaderAttachment__image` in _FileUploaderAttachment.scss +const IMAGE_PREVIEW_BASE64_MAX_WIDTH = 500; // px export interface FileMetadata { [key: string | number]: unknown; @@ -271,7 +273,7 @@ class FileUploader extends BaseComponent { if (hasImagePreview && isFileImg) { AttachmentSvgIcon?.remove(); AttachmentPreviewImage?.querySelector('img')?.setAttribute('alt', file.name); - image2Base64Preview(file, 100, (compressedDataURL) => + image2Base64Preview(file, IMAGE_PREVIEW_BASE64_MAX_WIDTH, (compressedDataURL) => AttachmentPreviewImage?.querySelector('img')?.setAttribute('src', compressedDataURL), ); } else { @@ -367,7 +369,9 @@ class FileUploader extends BaseComponent { } static isCoordsInMeta = (meta: FileMetadata) => { - return ['x', 'y', 'width', 'height'].every((coord) => meta[coord] != null); + return ['x', 'y', 'cropWidth', 'cropHeight', 'originalWidth', 'originalHeight'].every( + (coord) => meta[coord] != null, + ); }; updateQueue( @@ -384,16 +388,34 @@ class FileUploader extends BaseComponent { this.fileQueue.set(name, newValue); - const itemImgElement = SelectorEngine.findOne(`#${name} .FileUploaderAttachment__image img`); + const itemImgElement = SelectorEngine.findOne(`#${name} .FileUploaderAttachment__image img`) as HTMLImageElement; const itemImageObjectFit = itemImgElement?.dataset?.spiritImageObjectFit; let cropStyles; if (meta && itemImgElement && FileUploader.isCoordsInMeta(meta)) { + const previewHeight = IMAGE_PREVIEW_HEIGHT; + const cropWidth = parseInt(meta.cropWidth as string, 10); + const cropHeight = parseInt(meta.cropHeight as string, 10); + + let scale; + if (cropHeight > cropWidth) { + // scale for portrait images + scale = previewHeight / cropWidth; + } else { + // scale for landscape images + scale = previewHeight / cropHeight; + } + + const cropX = Math.round(parseInt(meta.x as string, 10) * scale); + const cropY = Math.round(parseInt(meta.y as string, 10) * scale); + const imageWidth = Math.round(parseInt(meta.originalWidth as string, 10) * scale); + const imageHeight = Math.round(parseInt(meta.originalHeight as string, 10) * scale); + cropStyles = ` - --file-uploader-attachment-image-top: -${meta.y}px; - --file-uploader-attachment-image-left: -${meta.x}px; - --file-uploader-attachment-image-width: ${meta.width}px; - --file-uploader-attachment-image-height: ${meta.height}px; + --file-uploader-attachment-image-top: -${cropY}px; + --file-uploader-attachment-image-left: -${cropX}px; + --file-uploader-attachment-image-width: ${imageWidth}px; + --file-uploader-attachment-image-height: ${imageHeight}px; `; if (itemImageObjectFit) { diff --git a/packages/web/src/scss/components/FileUploader/README.md b/packages/web/src/scss/components/FileUploader/README.md index f024a55996..b2a5d6aa28 100644 --- a/packages/web/src/scss/components/FileUploader/README.md +++ b/packages/web/src/scss/components/FileUploader/README.md @@ -485,12 +485,12 @@ const customUpdate = (_event: MouseEvent, file: File) => { #### Updating Image Preview with cropped image When you are using FileUploader with some kind of image cropper you want to also update the image preview on FileUploader attachment when image changes. -You can do this by passing a specific object in shape of coordinates (`{ x: number, y: number, width: number, height: number }`) to the `meta` argument. +You can do this by passing a specific object in shape of coordinates (`{ x: number, y: number, cropWidth: number, cropHeight: number, originalWidth: number, originalHeight: number }`) to the `meta` argument. Then the coordinates will be applied to the preview image in the attachment. ```javascript const customUpdate = (event: MouseEvent, file: File) => { - const meta = { x: 30, y: 30, width: 150, height: 150 }; + const meta = { x: 30, y: 30, cropWidth: 150, cropHeight: 150, originalWidth: 560, originalHeight: 330 }; return FileUploader.updateQueue(file.name, file, meta); }; diff --git a/packages/web/src/scss/components/FileUploader/index.html b/packages/web/src/scss/components/FileUploader/index.html index bd615cc17e..614667d65c 100644 --- a/packages/web/src/scss/components/FileUploader/index.html +++ b/packages/web/src/scss/components/FileUploader/index.html @@ -933,7 +933,7 @@ const customEdit = (event) => { const key = event.target.closest('li').id - const newMeta = toggleMetaData ? { x: 30, y: 30, width: 150, height: 150 } : { x: 22, y: 0, width: 110, height: 100 }; + const newMeta = toggleMetaData ? { x: 30, y: 30, cropWidth: 150, cropHeight: 150, originalWidth: 560, originalHeight: 330 } : { x: 22, y: 0, cropWidth: 110, cropHeight: 100, originalWidth: 560, originalHeight: 330 }; toggleMetaData = !toggleMetaData; const file = FileUploaderInstance.getFileFromQueue(key).file; FileUploaderInstance.updateQueue(key, file, newMeta, updateQueueCallback); @@ -954,7 +954,7 @@ } CancelButton.addEventListener('click', removeFromQueue); - + FileUploaderElement.addEventListener('queuedFile.fileUploader', event => { file = event.currentFile; @@ -964,7 +964,7 @@ FileUploaderElement.addEventListener('unqueuedFile.fileUploader', (event) => { callbackOnDismiss(event.currentFile); }); - + FileUploaderElement.addEventListener('editFile.fileUploader', (event) => { customEdit(event.currentFile); });