Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HEIC 파일 포맷 지원을 위한 convert로직 구현 #745

Merged
merged 8 commits into from
Oct 24, 2024
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 @@
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 Expand Up @@ -78,7 +78,7 @@
};

loginOAuth();
}, [navigate, provider]);

Check warning on line 81 in frontend/src/pages/Login/OAuthLoginPage/OAuthLoginPage.tsx

View workflow job for this annotation

GitHub Actions / test

React Hook useEffect has a missing dependency: 'kakaoMigration'. Either include it or remove the dependency array

return null;
}
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,8 @@ import { useTheme } from '@emotion/react';
export default function MainPage() {
const navigate = useNavigate();
const { mutate } = useServeToken();

requestPermission(mutate);
const theme = useTheme();
const [currentTab, setCurrentTab] = useState<MainPageTab>('모임목록');
const [isDarakbangMenuOpened, setIsDarakbangMenuOpened] = useState(false);
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); // 오류 발생 시 로딩 상태를 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
Loading