Skip to content

Commit

Permalink
[SobiSa-Expense-Hunter#149] [Feature] 마지막 임명장 페이지에서 sns 공유 시, 이미지도 같이…
Browse files Browse the repository at this point in the history
… 공유되도록 수정
  • Loading branch information
shinpanda committed Jun 21, 2023
1 parent b6c4b72 commit b2ee1d5
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 25 deletions.
4 changes: 2 additions & 2 deletions src/components/common/share/NativeShareButton.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';

import { NavigatorShareIcon } from '@/assets/SocialIcons';
import { ImageButton } from '@/components/common/buttons';
Expand All @@ -11,7 +11,7 @@ const NativeShareButton = ({ sharedMessage }: NativeShareButtonProps) => {
const [show, setShow] = useState(false);

useEffect(() => {
if (!(navigator.share === undefined || navigator.canShare(sharedMessage) === false)) {
if (navigator.share !== undefined) {
setShow(true);
}
}, [sharedMessage]);
Expand Down
32 changes: 16 additions & 16 deletions src/components/results/CertificateAndShareModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useRef } from 'react';
import { useRef, useState } from 'react';

import { toSvg } from 'html-to-image';
import { useRouter } from 'next/router';
Expand All @@ -8,7 +8,6 @@ import { DownloadIcon } from '@/assets/Icons';
import Portal from '@/components/Portal';
import { Background, ModalContainer } from '@/components/common/Modal';
import { CloseButton, ShareButton } from '@/components/common/buttons';
import Card from '@/components/event/Card';
import Certificate from '@/components/results/Certificate';
import ShareButtons from '@/components/results/ShareButtons';
import useModalAnimation from '@/hooks/useModalAnimation';
Expand All @@ -20,10 +19,7 @@ interface CertificateAndShareModalProps {
const createImage = async (url: string): Promise<HTMLImageElement> => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () =>
setTimeout(() => {
resolve(img);
}, 200);
img.onload = () => resolve(img);
img.decode = async () => resolve(img);
img.onerror = reject;
img.crossOrigin = 'anonymous';
Expand Down Expand Up @@ -65,20 +61,25 @@ const toPng = async (node: HTMLDivElement) => {

const CertificateAndShareModal = ({ onClose }: CertificateAndShareModalProps) => {
const { show, animationAfterClose } = useModalAnimation(onClose);
const [imgData, setImgData] = useState<string>('');
const ref = useRef<HTMLDivElement>(null);
const router = useRouter();

const certificateDownload = () => {
const getImgData = () => {
if (imgData) return imgData;
if (ref.current === null) {
return;
return '';
}
return toPng(ref.current);
};

toPng(ref.current).then(dataUrl => {
const link = document.createElement('a');
link.download = '임명장.png';
link.href = dataUrl;
link.click();
});
const certificateDownload = async () => {
const dataUrl = await getImgData();
const link = document.createElement('a');
link.download = '임명장.png';
link.href = dataUrl;
link.click();
setImgData(dataUrl);
};

const redirectHome = () => {
Expand All @@ -93,13 +94,12 @@ const CertificateAndShareModal = ({ onClose }: CertificateAndShareModalProps) =>
<CertificateAndShareWrapper>
<CloseButton style={{ alignSelf: 'flex-end' }} onClick={animationAfterClose} />
<Certificate ref={ref} />
<ShareButtons />
<ShareButtons imgData={imgData} />
<ModalButton onClick={certificateDownload}>
이미지 저장하기! <DownloadIcon />
</ModalButton>
<ModalGrayButton onClick={redirectHome}>홈으로 돌아가기</ModalGrayButton>
</CertificateAndShareWrapper>
<Card />
</Wrapper>
</Container>
</Portal>
Expand Down
56 changes: 56 additions & 0 deletions src/components/results/ConvertImage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { toSvg } from 'html-to-image';

export const createImage = async (url: string): Promise<HTMLImageElement> => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.decode = async () => resolve(img);
img.onerror = reject;
img.crossOrigin = 'anonymous';
img.src = url;
});
};

export const toPng = async (node: HTMLDivElement) => {
const { offsetWidth: width, offsetHeight: height } = node;
const multiple = 2;

const svgDataUrl = await toSvg(node);

const canvas = document.createElement('canvas');
const offscreenCanvas = canvas.transferControlToOffscreen();
offscreenCanvas.width = width * multiple;
offscreenCanvas.height = height * multiple;
const context = offscreenCanvas.getContext('2d', { alpha: false });
if (context === null) return '';

const img: HTMLImageElement = await createImage(svgDataUrl);
let done = false;
const onFrame = () => {
context.drawImage(img, 0, 0, width * multiple, height * multiple);
if (canvas.toDataURL('image/png', 1.0).length > 204800) done = true;
if (!done) {
window.requestAnimationFrame(onFrame);
}
};
onFrame();

return new Promise((resolve: (url: string) => void) => {
setTimeout(() => {
const url = canvas.toDataURL('image/png', 1.0);
resolve(url);
}, 500);
});
};

export const convertDataUrlToFile = (url: string) => {
if (url === '') return undefined;
return fetch(url)
.then(response => response.blob())
.then(blob => {
const ext = url.split('.').pop(); // url 구조에 맞게 수정할 것
const filename = url.split('/').pop() || '영수증.png'; // url 구조에 맞게 수정할 것
const metadata = { type: `image/${ext}` };
return new File([blob], filename, metadata);
});
};
39 changes: 32 additions & 7 deletions src/components/results/ShareButtons.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import { useEffect, useState } from 'react';

import styled from 'styled-components';

Expand All @@ -7,17 +7,42 @@ import KakaoButton from '@/components/common/share/KakaoButton';
import LinkButton from '@/components/common/share/LinkButton';
import NativeShareButton from '@/components/common/share/NativeShareButton';
import TwitterButton from '@/components/common/share/TwitterButton';
import { convertDataUrlToFile } from '@/components/results/ConvertImage';
import { sharedMessage } from '@/constant';

const ShareButtons = () => {
interface ShareButtonsProps {
imgData: string;
}

const ShareButtons = ({ imgData }: ShareButtonsProps) => {
const [NativeShareFile, setNativeShareFile] = useState<File | null>(null);
const { title = '', url = '' } = sharedMessage;

useEffect(() => {
try {
(async () => {
const file = await convertDataUrlToFile(imgData);
if (file && navigator.share !== undefined) {
setNativeShareFile(file);
}
})();
} catch (e) {
console.error('convertDataUrlToFile error', e);
}
}, [imgData]);

return (
<ShareButtonsContainer>
<FacebookButton pageUrl={url} />
<TwitterButton sendText={title} pageUrl={url} />
<KakaoButton webUrl={url} />
<LinkButton pageUrl={url} />
<NativeShareButton sharedMessage={sharedMessage} />
{NativeShareFile ? (
<NativeShareButton sharedMessage={{ ...sharedMessage, files: [NativeShareFile] }} />
) : (
<>
<FacebookButton pageUrl={url} />
<TwitterButton sendText={title} pageUrl={url} />
<KakaoButton webUrl={url} />
<LinkButton pageUrl={url} />
</>
)}
</ShareButtonsContainer>
);
};
Expand Down

0 comments on commit b2ee1d5

Please sign in to comment.