이정민

Merge branch 'feat/web' into develop

declare module "*.svg" {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const content: any;
export default content;
}
......@@ -3,6 +3,8 @@ import styled from "styled-components";
import TuiImageEditor from "tui-image-editor";
import "gif-generator/dist/gif-generator";
import { postGif } from "api";
import Close from "public/close.svg";
import Copy from "public/copy.svg";
declare global {
interface Window {
......@@ -23,6 +25,9 @@ const GifEditor = ({ previewURL }) => {
const [isUploadLoading, setIsUploadLoading] = useState(false);
const [viewLink, setViewLink] = useState(null);
const [unableToUpload, setUnableToUpload] = useState(false);
const [isModalOpened, setIsModalOpened] = useState(false);
useEffect(() => {
if (window) {
setImageEditor(
......@@ -58,7 +63,9 @@ const GifEditor = ({ previewURL }) => {
gifGenerator.make().then(
(blob: Blob) => {
setBlob(blob);
if (blob.size > 5000000) setUnableToUpload(true);
setDownload(window.URL.createObjectURL(blob));
setIsModalOpened(true);
},
(error) => {
alert(error);
......@@ -77,37 +84,77 @@ const GifEditor = ({ previewURL }) => {
setViewLink(`https://gif-generator.bu.to/${res.id}`);
};
const handleCloseModal = () => {
setIsModalOpened(false);
setIsMakeStarted(false);
setDownload(null);
setPercent(0);
setViewLink(null);
};
const clipboardCopy = (text: string) => {
if (!document.queryCommandSupported("copy")) {
return alert("클립보드가 지원되지 않는 브라우저입니다.");
}
const textarea = document.createElement("textarea");
textarea.value = text;
document.body.appendChild(textarea);
// 사파리 브라우저 서포팅
textarea.focus();
// 사용자가 입력한 내용을 영역을 설정할 때 필요
textarea.select();
document.execCommand("copy");
document.body.removeChild(textarea);
return null;
};
return (
<>
<Wrapper>
{((isMakeStarted && !download) || isUploadLoading) && (
<>
<div className="background" />
<div className="modal">
<div className="download">
loading... {!isUploadLoading && percent}%
loading... {!isUploadLoading && `${percent}%`}
</div>
</div>
</>
)}
{!isUploadLoading && viewLink && (
<div className="download" style={{ zIndex: 200 }}>
<a href={viewLink}>{viewLink}</a>
</div>
)}
{download && !isUploadLoading && (
<>
<div className="background" />
<div className="modal">
<div className="download">
<div className="download__btn">
<a href={download} download="new_gif.gif">
Download a File
</a>
<div className="modal__close" onClick={handleCloseModal}>
<Close />
</div>
<div className="download__explain">Click to Copy the Link!</div>
<div
className="download__copy"
onClick={() => clipboardCopy(viewLink)}
>
{viewLink}
<div style={{ marginLeft: "0.5rem" }}>
<Copy />
</div>
</div>
<div className="download__btn">
<div onClick={handleUpload}>Upload to Server</div>
</div>
</div>
</>
)}
{download && !isUploadLoading && isModalOpened && !viewLink && (
<NextStepModal
{...{
unableToUpload,
download,
handleUpload,
blob,
handleCloseModal,
}}
/>
)}
<div onClick={makeGif} className="make">
Make a Gif
</div>
......@@ -117,6 +164,44 @@ const GifEditor = ({ previewURL }) => {
);
};
const NextStepModal = ({
unableToUpload,
download,
handleUpload,
blob,
handleCloseModal,
}) => {
const url = window.URL.createObjectURL(blob);
return (
<ModalWrapper {...{ unableToUpload }}>
<div className="background" />
<div className="modal">
<div className="download">
<div className="modal__close" onClick={handleCloseModal}>
<Close />
</div>
<img src={url} width={500} />
<div className="buttons">
<div className="buttons__btn">
<a href={download} download="new_gif.gif">
Download a File
</a>
</div>
<div className="buttons__btn">
<div onClick={!unableToUpload && handleUpload}>
Upload to Server
</div>
</div>
</div>
{unableToUpload && (
<div className="warning">5MB 미만만 업로드 가능합니다!</div>
)}
</div>
</div>
</ModalWrapper>
);
};
const Wrapper = styled.div`
position: fixed;
width: 90%;
......@@ -126,10 +211,71 @@ const Wrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
.background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
background-color: black;
opacity: 0.7;
z-index: 100;
}
.modal {
width: 100%;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
&__close {
cursor: pointer;
position: absolute;
width: 2.5rem;
height: 2.5rem;
background: white;
border-radius: 50%;
box-shadow: ${({ theme }) => theme.boxShadow.normal};
display: flex;
align-items: center;
justify-content: center;
right: -0.7rem;
top: -0.7rem;
z-index: 103;
}
}
.download {
position: absolute;
z-index: 100;
background-color: white;
padding: 1.5rem 2rem;
border-radius: 2rem;
&__explain {
font-size: 1.2rem;
margin-bottom: 0.5rem;
font-weight: 800;
}
&__copy {
cursor: pointer;
display: flex;
align-items: center;
:hover {
text-decoration: underline;
}
}
}
a {
color: black;
text-decoration: none;
}
.unable {
cursor: not-allowed;
opacity: 0.5;
}
.make {
font: 800 11.5px Arial;
position: absolute;
......@@ -137,7 +283,6 @@ const Wrapper = styled.div`
top: 0;
width: 120px;
height: 40px;
background: red;
z-index: 10;
border-radius: 20px;
margin: 8px;
......@@ -150,34 +295,7 @@ const Wrapper = styled.div`
text-decoration: underline;
}
}
.background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
background-color: black;
opacity: 0.7;
z-index: 100;
}
.download {
position: absolute;
top: 15rem;
z-index: 100;
display: flex;
background-color: white;
padding: 1.5rem 2rem;
border-radius: 2rem;
&__btn {
cursor: pointer;
:last-child {
margin-left: 1rem;
}
:hover {
text-decoration: underline;
}
}
}
.tui-image-editor-container {
border-radius: 1.5rem;
}
......@@ -196,4 +314,51 @@ const Wrapper = styled.div`
}
`;
const ModalWrapper = styled.div<{ unableToUpload: boolean }>`
.download {
position: absolute;
z-index: 100;
background-color: white;
padding: 1.5rem 2rem;
border-radius: 2rem;
}
.warning {
box-sizing: border-box;
padding-right: 1rem;
width: 100%;
text-align: end;
margin-top: 1rem;
}
.buttons {
display: flex;
flex: 1;
margin-top: 1rem;
&__btn {
flex: 0.5;
text-align: center;
cursor: pointer;
border-radius: 2rem;
padding: 1.5rem;
background-color: #fdba3b;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
:hover {
text-decoration: underline;
}
:last-child {
margin-left: 1rem;
cursor: ${({ unableToUpload }) =>
unableToUpload ? "not-allowed" : "pointer"};
:hover {
text-decoration: ${({ unableToUpload }) =>
!unableToUpload ? "underline" : "none"};
}
opacity: ${({ unableToUpload }) => unableToUpload && 0.5};
}
}
}
`;
export default GifEditor;
......
......@@ -6,6 +6,7 @@ const Header = () => {
const Container = styled.div`
position: fixed;
box-sizing: border-box;
top: 0;
left: 0;
width: 100%;
......
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M24 20.188l-8.315-8.209 8.2-8.282-3.697-3.697-8.212 8.318-8.31-8.203-3.666 3.666 8.321 8.24-8.206 8.313 3.666 3.666 8.237-8.318 8.285 8.203z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M22 6v16h-16v-16h16zm2-2h-20v20h20v-20zm-24 17v-21h21v2h-19v19h-2z"/></svg>
\ No newline at end of file