From 2c450a49e92c2e4cf97bf27d9fd484b68418ae7e Mon Sep 17 00:00:00 2001 From: hwanheejung Date: Wed, 24 Jul 2024 17:26:43 +0900 Subject: [PATCH 1/2] #57 feat: image compression --- package-lock.json | 14 +++++++++ package.json | 1 + .../board/[boardId]/actions/uploadAction.ts | 30 ++++++++++++------- .../components/CreatePolaroidModal/index.tsx | 15 +++++----- src/components/Polaroid/PolaroidMaker.tsx | 25 +++++++++++++--- src/lib/api/file.ts | 16 ++++++++-- 6 files changed, 76 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5e41075..b23b60f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@storybook/preview-api": "^8.1.11", "@svgr/webpack": "^8.1.0", + "browser-image-compression": "^2.0.2", "eslint-import-resolver-typescript": "^3.6.1", "next": "14.2.4", "prettier-plugin-tailwindcss": "^0.6.5", @@ -7911,6 +7912,14 @@ "resolved": "https://registry.npmjs.org/browser-assert/-/browser-assert-1.2.1.tgz", "integrity": "sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==" }, + "node_modules/browser-image-compression": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/browser-image-compression/-/browser-image-compression-2.0.2.tgz", + "integrity": "sha512-pBLlQyUf6yB8SmmngrcOw3EoS4RpQ1BcylI3T9Yqn7+4nrQTXJD4sJDe5ODnJdrvNMaio5OicFo75rDyJD2Ucw==", + "dependencies": { + "uzip": "0.20201231.0" + } + }, "node_modules/browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", @@ -21264,6 +21273,11 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/uzip": { + "version": "0.20201231.0", + "resolved": "https://registry.npmjs.org/uzip/-/uzip-0.20201231.0.tgz", + "integrity": "sha512-OZeJfZP+R0z9D6TmBgLq2LHzSSptGMGDGigGiEe0pr8UBe/7fdflgHlHBNDASTXB5jnFuxHpNaJywSg8YFeGng==" + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", diff --git a/package.json b/package.json index f6feea4..f9b3a4e 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "dependencies": { "@storybook/preview-api": "^8.1.11", "@svgr/webpack": "^8.1.0", + "browser-image-compression": "^2.0.2", "eslint-import-resolver-typescript": "^3.6.1", "next": "14.2.4", "prettier-plugin-tailwindcss": "^0.6.5", diff --git a/src/app/(board)/board/[boardId]/actions/uploadAction.ts b/src/app/(board)/board/[boardId]/actions/uploadAction.ts index db3d80c..af7d61f 100644 --- a/src/app/(board)/board/[boardId]/actions/uploadAction.ts +++ b/src/app/(board)/board/[boardId]/actions/uploadAction.ts @@ -3,17 +3,25 @@ import { getPreSignedUrl, postPolaroid, uploadImage } from '@/lib' export const uploadAction = async (id: string, formData: FormData) => { - const fileInput = formData.get('fileInput') - const oneLineMessage = formData.get('oneLineMessage') + try { + const fileInput = formData.get('fileInput') + const oneLineMessage = formData.get('oneLineMessage') - // upload image to S3 - const { url, imageKey } = await getPreSignedUrl(id) - await uploadImage({ url, file: fileInput as File }) + if (!fileInput || !(fileInput instanceof File)) { + throw new Error('Invalid file input') + } - // upload polaroid - const res = await postPolaroid(id, { - imageKey, - oneLineMessage: oneLineMessage as string, - }) - return res + // upload image to S3 + const { url, imageKey } = await getPreSignedUrl(id) + await uploadImage({ url, file: fileInput }) + // upload polaroid + const res = await postPolaroid(id, { + imageKey, + oneLineMessage: oneLineMessage as string, + }) + return res + } catch (error) { + console.error('Error in uploadAction:', error) + throw new Error(`Failed to upload polaroid`) + } } diff --git a/src/app/(board)/board/[boardId]/components/CreatePolaroidModal/index.tsx b/src/app/(board)/board/[boardId]/components/CreatePolaroidModal/index.tsx index 1876f96..bd96150 100644 --- a/src/app/(board)/board/[boardId]/components/CreatePolaroidModal/index.tsx +++ b/src/app/(board)/board/[boardId]/components/CreatePolaroidModal/index.tsx @@ -2,7 +2,6 @@ import PolaroidMaker from '@/components/Polaroid/PolaroidMaker' import { useRef, useState } from 'react' -import rotateImageIfNeeded from '@/lib/utils/image' import { uploadAction } from '../../actions/uploadAction' import ArrowBack from './ArrowBack' import { useModal } from './ModalContext' @@ -16,18 +15,15 @@ const CreatePolaroid = ({ id }: CreatePolaroidProps) => { const formRef = useRef(null) const [btnDisabled, setBtnDisabled] = useState(true) const { closeModal } = useModal() + const [compressedFile, setCompressedFile] = useState(null) return (
{ - const fileInput = formData.get('fileInput') - if (fileInput && fileInput instanceof File) { - const rotatedFile = await rotateImageIfNeeded(fileInput) - if (rotatedFile !== fileInput) { - formData.set('fileInput', rotatedFile) - } + if (compressedFile) { + formData.set('fileInput', compressedFile) } const res = await uploadAction(id, formData) @@ -38,7 +34,10 @@ const CreatePolaroid = ({ id }: CreatePolaroidProps) => { ref={formRef} >
- +
diff --git a/src/components/Polaroid/PolaroidMaker.tsx b/src/components/Polaroid/PolaroidMaker.tsx index eb69101..66318ca 100644 --- a/src/components/Polaroid/PolaroidMaker.tsx +++ b/src/components/Polaroid/PolaroidMaker.tsx @@ -1,6 +1,7 @@ 'use client' import rotateImageIfNeeded from '@/lib/utils/image' +import imageCompression from 'browser-image-compression' import AddPhotoIcon from 'public/icons/add_photo_alternate.svg' import { ChangeEvent, @@ -13,35 +14,51 @@ import Base, { PolaroidImage } from './Base' interface PolaroidMakerProps { setBtnDisabled: Dispatch> + setCompressedFile: Dispatch> } const MAX_LENGTH = 20 -const PolaroidMaker = ({ setBtnDisabled }: PolaroidMakerProps) => { +const PolaroidMaker = ({ + setBtnDisabled, + setCompressedFile, +}: PolaroidMakerProps) => { const [text, setText] = useState('') const [fileUrl, setFileUrl] = useState(null) const handleFileChange = async ( event: React.ChangeEvent, ) => { + setFileUrl(null) if (event.target.files && event.target.files.length > 0) { const file = event.target.files[0] const rotatedFile = await rotateImageIfNeeded(file) - // image preview const fileReader = new FileReader() + // image compression + console.log(`originalFile size ${rotatedFile.size / 1024 / 1024} MB`) + const options = { + maxSizeMB: 0.2, + maxWidthOrHeight: 1920, + useWebWorker: true, + } + const compressedFile = await imageCompression(rotatedFile, options) + console.log(`compressedFile size ${compressedFile.size / 1024 / 1024} MB`) + setCompressedFile(compressedFile) + + // image preview fileReader.onload = () => { if (typeof fileReader.result === 'string') { setFileUrl(fileReader.result) } } - fileReader.readAsDataURL(rotatedFile) + fileReader.readAsDataURL(compressedFile) } } useEffect(() => { setBtnDisabled(!fileUrl) - }, [fileUrl]) + }, [fileUrl, setBtnDisabled]) return ( diff --git a/src/lib/api/file.ts b/src/lib/api/file.ts index de49589..b35af3e 100644 --- a/src/lib/api/file.ts +++ b/src/lib/api/file.ts @@ -9,14 +9,26 @@ export const getPreSignedUrl = async ( return result.data } -export const uploadImage = ({ url, file }: { url: string; file: File }) => { - return fetch(url, { +export const uploadImage = async ({ + url, + file, +}: { + url: string + file: File +}) => { + const res = await fetch(url, { method: 'PUT', headers: { 'Content-Type': file.type, }, body: file, }) + + if (!res.ok) { + throw new Error('Failed to upload image') + } + + return res } export const getImageUrl = (imageKey: string): Promise => { From 35b2c453bcb83519d6aa85ed5c288cbaaf1cc91c Mon Sep 17 00:00:00 2001 From: hwanheejung Date: Wed, 24 Jul 2024 19:07:21 +0900 Subject: [PATCH 2/2] #57 chore: removed console.log --- src/components/Polaroid/PolaroidMaker.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/Polaroid/PolaroidMaker.tsx b/src/components/Polaroid/PolaroidMaker.tsx index 66318ca..4be6e93 100644 --- a/src/components/Polaroid/PolaroidMaker.tsx +++ b/src/components/Polaroid/PolaroidMaker.tsx @@ -36,14 +36,12 @@ const PolaroidMaker = ({ const fileReader = new FileReader() // image compression - console.log(`originalFile size ${rotatedFile.size / 1024 / 1024} MB`) const options = { maxSizeMB: 0.2, maxWidthOrHeight: 1920, useWebWorker: true, } const compressedFile = await imageCompression(rotatedFile, options) - console.log(`compressedFile size ${compressedFile.size / 1024 / 1024} MB`) setCompressedFile(compressedFile) // image preview