Skip to content

Commit

Permalink
Merge pull request #745 from woowacourse-teams/feature/#742
Browse files Browse the repository at this point in the history
HEIC 파일 포맷 지원을 위한 convert로직 구현
  • Loading branch information
jaeml06 authored Oct 24, 2024
2 parents 17eab02 + 2140dcc commit 8abe750
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 15 deletions.
6 changes: 6 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
"@sentry/react": "^8.24.0",
"@sentry/webpack-plugin": "^2.22.0",
"firebase": "^10.12.5",
"heic2any": "^0.0.4",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-ga4": "^2.1.0",
Expand Down
Binary file added frontend/src/common/assets/loading.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ export const container = ({
`;

export const image = css`
width: 100%;
max-width: 40rem;
height: 100%;
max-height: 40rem;
`;
2 changes: 1 addition & 1 deletion frontend/src/pages/Login/OAuthLoginPage/OAuthLoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default function OAuthLoginPage() {
const params = useParams<'provider'>();
const provider = params.provider as Provider | undefined;
const { mutate: kakaoMigration } = useMigrationOAuth(
() => navigate(`${GET_ROUTES.default.resultMigration}/sucess`),
() => navigate(`${GET_ROUTES.default.resultMigration}/success`),
() => navigate(`${GET_ROUTES.default.resultMigration}/fail`),
);

Expand Down
2 changes: 2 additions & 0 deletions frontend/src/pages/Moim/MainPage/MainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { useTheme } from '@emotion/react';
export default function MainPage() {
const navigate = useNavigate();
const { mutate } = useServeToken();

const theme = useTheme();
const [currentTab, setCurrentTab] = useState<MainPageTab>('모임목록');
const [isDarakbangMenuOpened, setIsDarakbangMenuOpened] = useState(false);
Expand All @@ -53,6 +54,7 @@ export default function MainPage() {
if (window.Notification && window.Notification.permission === 'default') {
setIsModalOpen(true);
}
requestPermission(mutate);
}, []);

const handleModalClose = () => {
Expand Down
10 changes: 8 additions & 2 deletions frontend/src/pages/Mypage/MyPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default function MyPage() {
isEditing,
isShownRest,
isValidMyInfo,
isImageLoading,
setNickname,
setDescription,
handleEditClick,
Expand Down Expand Up @@ -74,9 +75,14 @@ export default function MyPage() {
{isValidMyInfo && (
<button
css={S.AccountButton({ theme })}
onClick={() => onUpload()}
onClick={() => {
if (!isImageLoading) {
onUpload();
}
}}
disabled={isImageLoading}
>
저장
{isImageLoading ? '저장 중...' : '저장'}
</button>
)}
<button css={S.AccountButton({ theme })} onClick={handleCancel}>
Expand Down
101 changes: 91 additions & 10 deletions frontend/src/pages/Mypage/hook/useMyPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import useMyInfo from '@_hooks/queries/useMyInfo';
import { useState, useRef, useEffect, ChangeEvent } from 'react';
import { validateNickName } from '../validate';
import POLICES from '@_constants/poclies';
import heic2any from 'heic2any';
import loadingSpinner from '@_common/assets/loading.gif';

export default function useMyPage() {
const { myInfo } = useMyInfo();
Expand All @@ -15,6 +17,7 @@ export default function useMyPage() {
const [isEditing, setIsEditing] = useState(false);
const [isReset, setIsReset] = useState('false');
const [isShownRest, setIsShownRest] = useState(false);
const [isImageLoading, setIsImageLoading] = useState(false); // 로딩 상태 추가

useEffect(() => {
if (myInfo) {
Expand All @@ -30,8 +33,58 @@ export default function useMyPage() {
setIsEditing((prev) => !prev);
setSelectedFile('');
};
const HEIC_SIGNATURE = '66747970'; // HEIC 매직 넘버

const onChange = (e: ChangeEvent<HTMLInputElement>) => {
const isHeifFile = (file: File): Promise<boolean> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();

reader.onloadend = () => {
const arr = new Uint8Array(reader.result as ArrayBuffer).subarray(0, 8);
let header = '';
for (let i = 0; i < arr.length; i++) {
header += arr[i].toString(16);
}

// HEIC 파일인지 확인
const isHeif = header.includes(HEIC_SIGNATURE);
resolve(isHeif);
};

reader.onerror = () => {
reject(new Error('파일 읽기 중 오류 발생'));
};

reader.readAsArrayBuffer(file);
});
};

/**
* HEIC 파일을 JPEG로 변환
* @param file
* @returns
*/
const handleFileConversion = async (file: File) => {
try {
const result = await heic2any({ blob: file, toType: 'image/jpeg' });

const jpegBlob = Array.isArray(result)
? new Blob(result, { type: 'image/jpeg' })
: result;

const jpegFile = new File(
[jpegBlob],
file.name.replace(/\.[^/.]+$/, '.jpeg'),
{ type: 'image/jpeg' },
);

return jpegFile;
} catch (error) {
return null;
}
};

const onChange = async (e: ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
const maxSize = POLICES.maxProfileImageSize;
const fileSize = e.target.files[0].size;
Expand All @@ -40,21 +93,48 @@ export default function useMyPage() {
e.target.value = '';
return;
}
setSelectedFile(e.target.files[0]); // 선택한 파일을 상태에 저장

let file = e.target.files[0];
setIsImageLoading(true); // 변환 시작 시 로딩 상태를 true로 설정
setProfile(loadingSpinner);

const isHeif = await isHeifFile(file);

if (isHeif) {
try {
const convertedFile = await handleFileConversion(file);
if (!convertedFile) throw new Error('파일 변환 실패');
setSelectedFile(convertedFile);
file = convertedFile;
} catch (error) {
alert('이미지 파일을 처리하는 중 오류가 발생했습니다.');
setIsImageLoading(false); // 오류 발생 시 로딩 상태를 false로 설정
return;
}
} else {
setSelectedFile(file);
}

setIsShownRest(true);
setIsReset('false');

const reader = new FileReader();
reader.onload = () => {
if (reader.readyState === 2 && typeof reader.result === 'string') {
setProfile(reader.result);
setIsImageLoading(false); // 이미지 로드 완료 시 로딩 상태를 false로 설정
}
};
reader.onerror = () => {
console.error('이미지 파일을 읽는 중 오류가 발생했습니다.');
alert('이미지 파일을 읽는 중 오류가 발생했습니다.');
setIsImageLoading(false);
};
reader.readAsDataURL(file);
} else {
setProfile(myInfo?.profile ?? '');
return;
}

const reader = new FileReader();
reader.onload = () => {
if (reader.readyState === 2 && typeof reader.result === 'string') {
setProfile(reader.result);
}
};
reader.readAsDataURL(e.target.files[0]);
};

const onUpload = async () => {
Expand Down Expand Up @@ -111,6 +191,7 @@ export default function useMyPage() {
isReset,
isShownRest,
isValidMyInfo,
isImageLoading,
setNickname,
setDescription,
setIsEditing,
Expand Down

0 comments on commit 8abe750

Please sign in to comment.