저번에 이미지 끌어서 놓기를 시도했다.
음. 그런데 하나만 놓기는 아쉬우니 여러개를 끌어서 놓을 수 있게 하고, 또 그에 대한 관리를 해주자.
간단하게 용량과 파일 타입, 갯수를 제한 시키자.
React 이미지 끌어서 놓기, 드래그 앤 드랍, 틀 만들기1(이미지 1개 끌어서 놓기)
저번에 한 개를 놓는 것을 성공했고, 이번에는 여러개를 가능하도록 하자.
변수부터가 달라져야한다.
우선 난 파일 하나당 용량, 총 용량, 총 갯수 를 관리하고 싶었기에 변수를 다음 처럼 사용했다.
const [isDragOver, setIsDragOver] = useState(false)
const [files, setFiles] = useState([])
const [maxSize, setMaxSize] = useState(10) //파일 업로드 가능 용량 mb기준
const [maxTotalSize, setMaxTotalSize] = useState(10) //총 업로드 가능 용량 mb 기준
const [maxFileNum, setMaxFileNum] = useState(10) // 최대 업로드 가능 파일 갯수
const [currentTotalSize, setCurrentTotalSize] = useState({"byteSize":0, "size": 0, "unit" : 'B'}) // 현재 업로드한 파일의 총 용량
const allowFileTypes = [
'image/png',
'image/jpg',
'image/jpeg',
'image/gif',
]
여기서 currentTotalSize의 byteSize와 size로 구분한 이유는 byteSize를 이용해서 데이터가 추가 삭제 될 때마다 총 용량을 확인하고 변경하기 때문이다.
style은 틀을 잡기 위한 것이라 inline 방식으로 사용했다.
우선 html코드 먼저 보자.
<div>
<div
onDragOver={(e) => {handleDragOver(e)}}
onDragLeave={() => {handleDragLeave()}}
onDrop={(e) => {handleDrop(e)}}
style={{
border: isDragOver ? '2px dashed blue' : '2px dashed gray',
}}
>
{files.length ? (
<div className='' style={{width: "600px", height: '600px', display:'flex', flexDirection:'column', justifyContent:'center', alignItems:'center'}}>
{files.map((theFile, i1) => (
<div key={i1}>
<span>{theFile[0].name}</span> <span>{theFile[1]} {theFile[2]}</span> <button onClick={() => {cancelFile(i1)}}>X</button>
</div>
))}
</div>
) : (
<div className='' style={{width: "600px", height: '600px', display:'flex', flexDirection:'column', justifyContent:'center', alignItems:'center'}}>
<p>이미지를 끌어서 올려주세요.</p>
</div>
)}
</div>
{files.length
?
<div>
<p>{currentTotalSize.size} {currentTotalSize.unit}</p>
</div>
: <></>
}
<button onClick={() => {handleUpload()}}>Upload</button>
</div>
files 변수를 [] 로 두었기 때문에 files의 유무가 아닌 files.length를 통해 파일 유무를 확인하도록 했다.
또한 files.map()을 이용해서 어떤 파일을 추가했는지, 용량은 얼마인지를 사용자가 확인할 수 있게 했다. 그리고 인덱스를 이용해서 cancel()할 수 있게 설정을 했다.
또한 총 용량을 파악할 수 있도록
<p>{currentTotalSize.size} {currentTotalSize.unit}</p>
를 이용해주기로 했다.
이제 함수 부분을 보자.
const handleDragOver = (event) => {
event.preventDefault()
setIsDragOver(true)
}
const handleDragLeave = () => {
setIsDragOver(false)
}
const handleDrop = (event) => {
event.preventDefault()
setIsDragOver(false)
// for문 동안 작업해줄 변수
let tmpFiles = [...files]
// 최대 사이즈 확인
let tmpTotalSize = currentTotalSize.byteSize
// 1KB 2**10 1MB는 2**20 바이트 1GB는 2**30
for(let i = 0; i < event.dataTransfer.files.length; i++){
const thisFile = event.dataTransfer.files[i]
// 파일 사이즈, 파일 총 용량, 파일 갯수를 확인
if(thisFile.size <= maxSize * 2**20 && (tmpTotalSize + thisFile.size <= maxTotalSize * 2**20) && tmpFiles.length < maxFileNum){
if(allowFileTypes.includes(thisFile.type)){
// 사이즈와 단위
const sizeUNit = handleFileSize(thisFile.size)
const inputV = [thisFile, ...sizeUNit]
tmpFiles = [...tmpFiles, inputV]
tmpTotalSize += thisFile.size
}else{
alert('이미지 파일만 업로드 가능합니다.')
break
}
}else if(tmpFiles.length >= maxFileNum){
alert(`최대 ${maxFileNum}개의 파일을 등록할 수 있습니다.`)
break
}else{
alert(`각 파일 및 전체 파일의 용량은 10MB를 초과해 등록할 수 없습니다.`)
break
}
}
// for문 이후 files 정리
setFiles([...tmpFiles])
const value = handleFileSize(tmpTotalSize)
setCurrentTotalSize({
"byteSize" : tmpTotalSize,
"size" : value[0],
"unit" : value[1]
})
}
const handleUpload = () => {
// 파일 업로드 로직 구현
if(files.length){
console.log('업로드한 파일들', files)
}else{
alert('파일을 넣은 후 진행하세요.')
}
}
const handleFileSize = (theFileSize) => {
const UNITS = ['B', 'KB', 'MB', 'GB', 'TB']
let i = 0
// let theFileSize = theFile.size
while(theFileSize >= 1024 && i < UNITS.length - 1){
theFileSize /= 1024
i ++
}
if(i <= 1){
return [Math.ceil((theFileSize)), UNITS[i]]
}else{
return [Math.ceil((theFileSize) * 100) / 100 , UNITS[i]]
}
}
const cancelFile = (index) => {
if(files.length){
let tmpFiles = [...files]
let tmpSize = tmpFiles[index][0].size
let tmpTotalSize = currentTotalSize.byteSize - tmpSize
const value = handleFileSize(tmpTotalSize)
setCurrentTotalSize({
"byteSize" : tmpTotalSize,
"size" : value[0],
"unit" : value[1]
})
tmpFiles.splice(index, 1)
setFiles([...tmpFiles])
}
}
useEffect(() => {
if(files.length){
console.log(currentTotalSize)
}
}, [files])
handleDragOver()와 handleDragLeave() 는 이전과 동일하다.
handleDrop()을 보게 되면, 우선 tmpFiles라는 변수를 사용했다. for문에서 바로
setFiles(...files, 어쩌고)
로 해도된다고 생각했지만, 동기적으로 작동하는 문제때문인지, 제대로 작동하지 않았다. 그래서 아예 변수를 하나 생성해주고 이를 이용해서 완성 시킨 tmpFiles를
setFiles(...tmpFiles)
의 방식을 이용했다.
또한 for문에서 &&를 이용해서 하나의 파일용량, 전체 파일용량, 전체 파일 갯수에 대해서 확인하고 3개가 모두 통과시에 파일을 추가했으면, 그렇지 못할 경우 break문으로 작동을 멈추게 했다.
파일의 사이즈는 이전과 동일하게 handleFileSize()를 사용했으며, cancel()시에는 총 용량에서 제거할 파일의 용량을 제한 것을 계산 한 후에, 해당 파일을
splice(index, 1)
splice(삭제할인덱스, 연속해서 몇개 지울래?)
을 이용해서 삭제했다.
useEffect()에서는 제대로 작동하는지를 확인하기위해 전체 파일 용량 크기를 출력하도록 했다.
잘 작동한다.
잘 작동하는 모습을 볼 수 있다.
다음에 필요하면 가져다가 사용해야겠다ㅎㅎ