diff --git a/apps/web/package.json b/apps/web/package.json index 5071529e2fe..4ac9f36bcfe 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -38,6 +38,7 @@ "@tippyjs/react": "^4.2.6", "@wagmi/connectors": "^3.1.8", "axios": "^1.6.2", + "browser-image-compression": "^2.0.2", "dayjs": "^1.11.10", "dayjs-twitter": "^0.5.0", "dotenv": "^16.3.1", diff --git a/apps/web/src/hooks/useUploadAttachments.tsx b/apps/web/src/hooks/useUploadAttachments.tsx index 7cb7573c4b1..4fdd40aa1af 100644 --- a/apps/web/src/hooks/useUploadAttachments.tsx +++ b/apps/web/src/hooks/useUploadAttachments.tsx @@ -1,104 +1,129 @@ import type { NewAttachment } from '@hey/types/misc'; import uploadToIPFS from '@lib/uploadToIPFS'; +import imageCompression from 'browser-image-compression'; import { useCallback } from 'react'; import { toast } from 'react-hot-toast'; import { usePublicationStore } from 'src/store/non-persisted/usePublicationStore'; import { v4 as uuid } from 'uuid'; const useUploadAttachments = () => { - const addAttachments = usePublicationStore((state) => state.addAttachments); - const updateAttachments = usePublicationStore( - (state) => state.updateAttachments - ); - const removeAttachments = usePublicationStore( - (state) => state.removeAttachments - ); - const setIsUploading = usePublicationStore((state) => state.setIsUploading); - const setUploadedPercentage = usePublicationStore( - (state) => state.setUploadedPercentage - ); + const { + addAttachments, + removeAttachments, + setIsUploading, + setUploadedPercentage, + updateAttachments + } = usePublicationStore((state) => ({ + addAttachments: state.addAttachments, + removeAttachments: state.removeAttachments, + setIsUploading: state.setIsUploading, + setUploadedPercentage: state.setUploadedPercentage, + updateAttachments: state.updateAttachments + })); + + const validateFileSize = (file: any) => { + const isImage = file.type.includes('image'); + const isVideo = file.type.includes('video'); + const isAudio = file.type.includes('audio'); + + if (isImage && file.size > 50000000) { + toast.error('Image size should be less than 50MB'); + return false; + } + + if (isVideo && file.size > 500000000) { + toast.error('Video size should be less than 500MB'); + return false; + } + + if (isAudio && file.size > 200000000) { + toast.error('Audio size should be less than 200MB'); + return false; + } + + return true; + }; const handleUploadAttachments = useCallback( async (attachments: any): Promise => { setIsUploading(true); + const files = Array.from(attachments); const attachmentIds: string[] = []; - const previewAttachments: NewAttachment[] = files.map((file: any) => { - const attachmentId = uuid(); - attachmentIds.push(attachmentId); - - return { - file, - id: attachmentId, - mimeType: file.type, - previewUri: URL.createObjectURL(file), - type: file.type.includes('image') - ? 'Image' - : file.type.includes('video') - ? 'Video' - : 'Audio', - uri: URL.createObjectURL(file) - }; - }); - - const hasLargeAttachment = files.map((file: any) => { - const isImage = file.type.includes('image'); - const isVideo = file.type.includes('video'); - const isAudio = file.type.includes('audio'); - - if (isImage && file.size > 50000000) { - toast.error('Image size should be less than 50MB'); - return false; - } + const compressedFiles = await Promise.all( + files.map(async (file: any) => { + if (file.type.includes('image')) { + return await imageCompression(file, { + exifOrientation: 1, + maxSizeMB: 1, + maxWidthOrHeight: 2048, + useWebWorker: true + }); + } + return file; + }) + ); - if (isVideo && file.size > 500000000) { - toast.error('Video size should be less than 500MB'); - return false; - } + const previewAttachments: NewAttachment[] = compressedFiles.map( + (file: any) => { + const attachmentId = uuid(); + attachmentIds.push(attachmentId); - if (isAudio && file.size > 100000000) { - toast.error('Audio size should be less than 100MB'); - return false; + return { + file, + id: attachmentId, + mimeType: file.type, + previewUri: URL.createObjectURL(file), + type: file.type.includes('image') + ? 'Image' + : file.type.includes('video') + ? 'Video' + : 'Audio', + uri: URL.createObjectURL(file) + }; } + ); - return true; - }); + if (compressedFiles.some((file) => validateFileSize(file))) { + addAttachments(previewAttachments); - addAttachments(previewAttachments); - let attachmentsIPFS: NewAttachment[] = []; - try { - if (hasLargeAttachment.includes(false)) { - setIsUploading(false); - removeAttachments(attachmentIds); - return []; - } - - const attachmentsUploaded = await uploadToIPFS( - attachments, - (percentCompleted) => setUploadedPercentage(percentCompleted) - ); - if (attachmentsUploaded) { - attachmentsIPFS = previewAttachments.map( - (attachment: NewAttachment, index: number) => ({ - ...attachment, - mimeType: attachmentsUploaded[index].mimeType, - uri: attachmentsUploaded[index].uri + try { + const attachmentsUploaded = await uploadToIPFS( + compressedFiles, + setUploadedPercentage + ); + const attachmentsIPFS = attachmentsUploaded.map( + (uploaded, index) => ({ + ...previewAttachments[index], + mimeType: uploaded.mimeType, + uri: uploaded.uri }) ); + updateAttachments(attachmentsIPFS); + setIsUploading(false); + return attachmentsIPFS; + } catch (error) { + console.error(error); + toast.error('Something went wrong while uploading!'); + removeAttachments(attachmentIds); } - } catch { + } else { removeAttachments(attachmentIds); - toast.error('Something went wrong while uploading!'); } - setIsUploading(false); - return attachmentsIPFS; + setIsUploading(false); + return []; }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [addAttachments, removeAttachments, updateAttachments, setIsUploading] + [ + addAttachments, + removeAttachments, + updateAttachments, + setIsUploading, + setUploadedPercentage + ] ); return { handleUploadAttachments }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 877d3100e0b..4b7efa27942 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -216,6 +216,9 @@ importers: axios: specifier: ^1.6.2 version: 1.6.2 + browser-image-compression: + specifier: ^2.0.2 + version: 2.0.2 dayjs: specifier: ^1.11.10 version: 1.11.10 @@ -4244,6 +4247,7 @@ packages: dependencies: is-glob: 4.0.3 micromatch: 4.0.5 + napi-wasm: 1.1.0 dev: false bundledDependencies: - napi-wasm @@ -7684,6 +7688,12 @@ packages: resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} dev: false + /browser-image-compression@2.0.2: + resolution: {integrity: sha512-pBLlQyUf6yB8SmmngrcOw3EoS4RpQ1BcylI3T9Yqn7+4nrQTXJD4sJDe5ODnJdrvNMaio5OicFo75rDyJD2Ucw==} + dependencies: + uzip: 0.20201231.0 + dev: false + /browserslist@4.22.2: resolution: {integrity: sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -11613,6 +11623,10 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + /napi-wasm@1.1.0: + resolution: {integrity: sha512-lHwIAJbmLSjF9VDRm9GoVOy9AGp3aIvkjv+Kvz9h16QR3uSVYH78PNQUnT2U4X53mhlnV2M7wrhibQ3GHicDmg==} + dev: false + /natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} dev: true @@ -14545,6 +14559,10 @@ packages: hasBin: true dev: false + /uzip@0.20201231.0: + resolution: {integrity: sha512-OZeJfZP+R0z9D6TmBgLq2LHzSSptGMGDGigGiEe0pr8UBe/7fdflgHlHBNDASTXB5jnFuxHpNaJywSg8YFeGng==} + dev: false + /v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} dev: true