<input type="file" />
๋์ ํ๋ ๋ฒํผ ํด๋ฆญmartipark/form-data
ํ์์ผ๋ก ๋ฐฑ์๋๋ก ์ด๋ฏธ์ง ์ ์กurl
์ ๋ฐ์ ( photoUrl
)![์ด๋ฏธ์ง](photoUrl)
ํํ๋ก contents
์ ๋ฃ์martipark/form-data
ํ์์ผ๋ก ๋ฐฑ์๋๋ก ์ด๋ฏธ์ง ์ ์กurl
์ ๋ฐ์ ( photoUrl
)![์ด๋ฏธ์ง](photoUrl)
ํํ๋ก contents
์ ๋ฃ์// 2022/04/28 - ์ด๋ฏธ์ง ์
๋ก๋ ๋ก๋ฉ ๋ณ์ - by 1-blue
const [uploadLoading, setUploadLoading] = useState(false);
// drag & drop์ผ๋ก ์ด๋ฏธ์ง ์ฒ๋ฆฌํ๋ ์ด๋ฒคํธ ํธ๋ค๋ฌ
const onUploadPhotoByDrop = useCallback(
async (e: any) => {
e.preventDefault();
setUploadLoading(true);
try {
const formData = new FormData();
formData.append("photo", e.dataTransfer.files[0]);
const { photoUrl }: PhotoResponse = await fetch("/api/photo", {
method: "POST",
body: formData,
}).then((res) => res.json());
setValue(
"contents",
getValues("contents") + `\n![์ด๋ฏธ์ง](${photoUrl})`
);
toast.success("์ด๋ฏธ์ง๋ฅผ ์
๋ก๋ํ์ต๋๋ค.");
} catch (error) {
toast.error("์ด๋ฏธ์ง ์
๋ก๋์ ์คํจํ์ต๋๋ค.");
}
setUploadLoading(false);
setIsDragging(false);
},
[getValues, setValue, setUploadLoading, setIsDragging]
);
// jsx ์์ญ
return (
<article
onDragOver={() => setIsDragging(true)}
onDragLeave={() => setIsDragging(false)}
>
<section className="flex-1 dark:bg-zinc-800 bg-zinc-200 p-4">
{isDragging ? (
<div
className="flex flex-col h-full justify-center items-center"
onDragOver={(e) => e.preventDefault()}
onDrop={onUploadPhotoByDrop}
>
<span>๐ผ๏ธ์ด๋ฏธ์ง๋ฅผ ์ฌ๊ธฐ์ ๋๋๊ทธ ํด์ฃผ์ธ์!</span>
<Icon icon={ICON.PHOTO} className="w-40 h-40" />
</div>
) : (
<>
// markdown ํ์์ผ๋ก ์
๋ ฅ๋ฐ๋ form... ์๋ต
</>
)}
</section>
</article>
)
<input type="file" />
์ ์ด์ฉํ ์ด๋ฏธ์ง ์ฒ๋ฆฌ ์์// 2022/04/28 - ์ด๋ฏธ์ง input ref - by 1-blue
const photoRef = useRef<HTMLInputElement>(null);
// 2022/04/28 - ์ด๋ฏธ์ง ๋๋๊ทธ์ค์ธ์ง ํ๋จํ ๋ณ์ - by 1-blue
const [isDragging, setIsDragging] = useState(false);
// 2022/04/28 - ์ด๋ฏธ์ง ์
๋ก๋ ๋ก๋ฉ ๋ณ์ - by 1-blue
const [uploadLoading, setUploadLoading] = useState(false);
// 2022/04/28 - ์ด๋ฏธ์ง ์
๋ก๋ ( ํ์ผ ํ์๊ธฐ ์ด์ฉ ) - by 1-blue
const onUploadPhotoByExplorer = useCallback(
async (e: any) => {
setUploadLoading(true);
try {
const formData = new FormData();
formData.append("photo", e.target.files[0]);
const { photoUrl }: PhotoResponse = await fetch("/api/photo", {
method: "POST",
body: formData,
}).then((res) => res.json());
setValue(
"contents",
getValues("contents") + `\n![์ด๋ฏธ์ง](${photoUrl})`
);
toast.success("์ด๋ฏธ์ง๋ฅผ ์
๋ก๋ํ์ต๋๋ค.");
} catch (error) {
toast.error("์ด๋ฏธ์ง ์
๋ก๋์ ์คํจํ์ต๋๋ค.");
}
setUploadLoading(false);
setIsDragging(false);
},
[setUploadLoading, getValues, setValue, setIsDragging]
);
// jsx ์์ญ
return (
<article
onDragOver={() => setIsDragging(true)}
onDragLeave={() => setIsDragging(false)}
>
<section className="flex-1 dark:bg-zinc-800 bg-zinc-200 p-4">
{isDragging ? (
<div
className="flex flex-col h-full justify-center items-center"
onDragOver={(e) => e.preventDefault()}
onDrop={onUploadPhotoByDrop}
>
<span>๐ผ๏ธ์ด๋ฏธ์ง๋ฅผ ์ฌ๊ธฐ์ ๋๋๊ทธ ํด์ฃผ์ธ์!</span>
<Icon icon={ICON.PHOTO} className="w-40 h-40" />
</div>
) : (
<form
className="flex flex-col h-full"
onSubmit={handleSubmit(onCreatePost)}
>
<input
type="file"
accept="image/*"
ref={photoRef}
onChange={onUploadPhotoByExplorer}
hidden
/>
<button
type="button"
onClick={() => photoRef.current?.click()}
className="p-1 rounded-md hover:text-white hover:bg-black dark:hover:text-black dark:hover:bg-white focus:ring-2 focus:ring-indigo-400 focus:ring-offset-2"
>
<Icon icon={ICON.PHOTO} />
</button>
// ๋ค๋ฅธ ํ๊ทธ๋ค์ ์๋ต...
</form>
)}
</section>
</article>
)
drag & drop API
๋ก ๋ฐ์File
์ ๋ฐฐ์ด์ ๋ฃ์FileReader
๋ฅผ ์ด์ฉํด์ ์ด๋ฏธ์ง๋ฅผ base64
ํํ๋ก ์ฒ๋ฆฌํจbase64
๋ฅผ ํ๋ฉด์ ๋๋๋งํจFile
๋ฐฐ์ด์ ๋ฐฑ์๋๋ก ๋๊ฒจ์ค์ ์ด๋ฏธ์ง๋ฅผ ์ ์ฅํจbase64
์ ํฌ๊ธฐ๊ฐ ๋๋ฌด ์ปค์ ธ์ react-markdown
์ด ๊ณ ์ฅ ๋์ ๋ธ๋ผ์ฐ์ ๊ฐ ๋ฉ์ถฐ๋ฒ๋ฆผbase64
์์ฒด๋ฅผ DB์ ๋ฃ์ด์ผ ํ๋ ๊ฒฝ์ฐ ๋ฐ์ ( ๋ฉ๋ชจ๋ฆฌ ๋ญ๋น )์ด๋ฏธ์ง๋ฅผ ๋๋๊ทธํด์ ํ์ด์ง ๋ด๋ถ๋ก ๊ฐ์ ธ์จ ๊ฒฝ์ฐ์ ์ด๋ฏธ์ง ๋๋๊ทธ ์์ญ์ ๋๋๋งํ๋๋ฐ ํด๋น ์์ญ์ด ๊น๋นก๊ฑฐ๋ฆฌ๋ ๋ฌธ์ ๊ฐ ์์ต๋๋ค.
๋๋๊ทธ ์์ญ์์ ๋ด๋ถ ํ๊ทธ์ ์๋ฅผ ์ง๋๊ฐ ๊ฒฝ์ฐ์๋ง isDragging
๊ฐ์ด false
๊ฐ ๋ผ์ ์ฆ, onDragLeave
์ด๋ฒคํธ๊ฐ ์๋ํ๋๋ฐ ์๋ํ๋ ์ด์ ๋ฅผ ํ์
ํ์ง ๋ชปํด์ ํธ๋ ๋ก์ ๊ธฐ๋กํด๋๊ณ ๋ค์์ ์ฒ๋ฆฌํ ์๊ฐ์
๋๋ค.