import { vars } from './variable.js'; import { targetFocus, syncGroupStyle } from './common.js'; import { resetViewer, } from './pageRenderer.js'; let archiveMain = document.querySelector('.archive-main'); let viewerContainer = archiveMain?.querySelector('.archive-main-right .viewer-container'); addFileDragEvent(document.querySelector('.archive-main-center .list-container .list-body'), 'file-area'); addFileDragEvent(document.querySelector('.recycle-bin-modal .list-container .list-body'), 'recycle-bin'); export function addFileDragEvent(listBody, target) { let draggingStartTarget, draggingEndTarget; let draggingStart = false, isDragging = false; let isCtrl = false, isShift = false; let processedItems = new Set(); let originalSelection = new Set(); let multiSelectBox = document.createElement('div'); multiSelectBox.style.position = 'absolute'; multiSelectBox.style.border = '1px solid rgba(0, 120, 215, 1)'; multiSelectBox.style.background = 'rgba(0, 120, 215, 0.2)'; multiSelectBox.style.zIndex = '9999'; multiSelectBox.style.pointerEvents = 'none'; multiSelectBox.style.display = 'none'; listBody.appendChild(multiSelectBox); vars.preventNextClick = false; let startX = 0; let startY = 0; let prevClientX = 0; let prevClientY = 0; let autoScrollDirection = null; let autoScrollFrameId = null; const scrollSpeed = 8; const scrollMargin = 36; function autoScrollLoop() { if (!autoScrollDirection) return; // 더 작으면 느리고 부드러움 if (autoScrollDirection === 'down') { listBody.scrollTop += scrollSpeed; } else if (autoScrollDirection === 'up') { listBody.scrollTop -= scrollSpeed; } autoScrollFrameId = requestAnimationFrame(autoScrollLoop); } listBody.addEventListener('pointerdown', (e) => { // 마우스 좌클릭이 아닌 경우 리턴 if (e.button !== 0) return; // 갤러리 폴더 지도화면인 경우 리턴 if (target == 'file-area' && listBody.querySelector('.map-container').style.display === 'flex') return; draggingStartTarget = e.target; draggingStart = true; isDragging = false; isCtrl = e.ctrlKey; isShift = e.shiftKey; processedItems.clear(); if (target == 'file-area') originalSelection = new Set(vars.multiSelectListItemArr); if (target == 'recycle-bin') originalSelection = new Set(vars.multiSelectListItemArr_bin); vars.preventNextClick = false; // if (isCtrl) { // vars.lastListItem = e.target.closest('.list-item'); // } else { // listBody.querySelectorAll('.list-item').forEach(listItem => { // listItem.classList.remove('selected'); // listItem.classList.remove('group-style'); // }) // vars.multiSelectListItemArr = []; // } const dragStartListItem = e.target.closest('.list-item'); if (dragStartListItem && !isShift) { if (target == 'file-area') vars.lastListItem = dragStartListItem; if (target == 'recycle-bin') vars.lastListItem_bin = dragStartListItem; } if (!isCtrl) { // Ctrl이 아닐 경우 기존 선택 모두 해제 listBody.querySelectorAll('.list-item').forEach(listItem => { listItem.classList.remove('selected'); listItem.classList.remove('group-style'); }); if (target == 'file-area') vars.multiSelectListItemArr = []; if (target == 'recycle-bin') vars.multiSelectListItemArr_bin = []; } const bodyRect = listBody.getBoundingClientRect(); startX = e.clientX + listBody.scrollLeft - bodyRect.left; startY = e.clientY + listBody.scrollTop - bodyRect.top; prevClientX = e.clientX; prevClientY = e.clientY; Object.assign(multiSelectBox.style, { left: `${startX}px`, top: `${startY}px`, width: `0px`, height: `0px`, display: 'none' }); }); listBody.addEventListener('pointermove', (e) => { // 마우스 좌클릭이 아닌 경우 리턴 if (e.buttons !== 1) return; // list-body에서 마우스 드래그가 시작된게 아닌 경우 리턴 if (!draggingStart) return; if (e.clientX === prevClientX && e.clientY === prevClientY) return; prevClientX = e.clientX; prevClientY = e.clientY; const listBodyRect = listBody.getBoundingClientRect(); const currX = e.clientX + listBody.scrollLeft - listBodyRect.left; const currY = e.clientY + listBody.scrollTop - listBodyRect.top; const dx = currX - startX; const dy = currY - startY; if (Math.abs(dx) > 5 || Math.abs(dy) > 5) { isDragging = true; vars.preventNextClick = true; const left = Math.min(startX, currX); const top = Math.min(startY, currY); const width = Math.abs(dx); const height = Math.abs(dy); Object.assign(multiSelectBox.style, { left: `${left}px`, top: `${top}px`, width: `${width}px`, height: `${height}px`, display: 'block' }); const boxRect = multiSelectBox.getBoundingClientRect(); listBody.querySelectorAll('.list-item .wrap').forEach(elem => { const rect = elem.getBoundingClientRect(); const overlaps = !( rect.right < boxRect.left || rect.left > boxRect.right || rect.bottom < boxRect.top || rect.top > boxRect.bottom ); let listItem = elem.parentElement; if (isCtrl) { if (overlaps && !processedItems.has(listItem)) { processedItems.add(listItem); // ✅ 토글: 원래 선택되어 있었으면 해제, 아니면 선택 if (originalSelection.has(listItem)) { listItem.classList.remove('selected'); listItem.classList.remove('group-style'); if (target == 'file-area') vars.multiSelectListItemArr = vars.multiSelectListItemArr.filter(item => item !== listItem); if (target == 'recycle-bin') vars.multiSelectListItemArr_bin = vars.multiSelectListItemArr_bin.filter(item => item !== listItem); } else { listItem.classList.add('selected'); listItem.classList.add('group-style'); if (target == 'file-area') vars.multiSelectListItemArr.push(listItem); if (target == 'recycle-bin') vars.multiSelectListItemArr_bin.push(listItem); } } // ✅ 박스에서 벗어난 항목 복원 if (!overlaps && processedItems.has(listItem)) { processedItems.delete(listItem); if (originalSelection.has(listItem)) { listItem.classList.add('selected'); listItem.classList.add('group-style'); if (target == 'file-area') { if (!vars.multiSelectListItemArr.includes(listItem)) vars.multiSelectListItemArr.push(listItem); } if (target == 'recycle-bin') { if (!vars.multiSelectListItemArr_bin.includes(listItem)) vars.multiSelectListItemArr_bin.push(listItem); } } else { listItem.classList.remove('selected'); listItem.classList.remove('group-style'); if (target == 'file-area') vars.multiSelectListItemArr = vars.multiSelectListItemArr.filter(item => item !== listItem); if (target == 'recycle-bin') vars.multiSelectListItemArr_bin = vars.multiSelectListItemArr_bin.filter(item => item !== listItem); } } } else { listItem.classList.toggle('selected', overlaps); listItem.classList.toggle('group-style', overlaps); if (target == 'file-area') { if (overlaps && !vars.multiSelectListItemArr.includes(listItem)) vars.multiSelectListItemArr.push(listItem); } if (target == 'recycle-bin') { if (overlaps && !vars.multiSelectListItemArr_bin.includes(listItem)) vars.multiSelectListItemArr_bin.push(listItem); } } }); if (e.clientY > listBodyRect.bottom - scrollMargin) { if (autoScrollDirection !== 'down') { cancelAnimationFrame(autoScrollFrameId); autoScrollDirection = 'down'; autoScrollLoop(); } } else if (e.clientY < listBodyRect.top + scrollMargin) { if (autoScrollDirection !== 'up') { cancelAnimationFrame(autoScrollFrameId); autoScrollDirection = 'up'; autoScrollLoop(); } } else { if (autoScrollDirection !== null) { cancelAnimationFrame(autoScrollFrameId); autoScrollDirection = null; } } } }); document.addEventListener('pointerup', async (e) => { // list-body에서 마우스 드래그가 시작된게 아닌 경우 리턴 if (!draggingStart) return; if (target == 'file-area') { if (vars.lastHeaderBtn) targetFocus(vars.lastHeaderBtn); if (vars.lastMainTreeItem) targetFocus(vars.lastMainTreeItem); } if (isShift && isDragging) { const dragStartListItem = draggingStartTarget.closest('.list-item'); if (dragStartListItem) { if (target == 'file-area') vars.lastListItem = dragStartListItem; if (target == 'recycle-bin') vars.lastListItem_bin = dragStartListItem; } } multiSelectBox.style.display = 'none'; draggingEndTarget = e.target; draggingStart = false; isDragging = false; isCtrl = false; isShift = false; // 드래그가 아이템 바깥 영역에서만 진행되고, 드래그에 포함된 아이템이 하나도 없을 때 뷰어 초기화 if ( ( (draggingStartTarget.matches('.list-body') && draggingEndTarget.matches('.list-body')) || (draggingStartTarget.matches('.list-item-wrap') && draggingEndTarget.matches('.list-item-wrap')) ) && ( (target == 'file-area' && vars.multiSelectListItemArr.length == 0) || (target == 'recycle-bin' && vars.multiSelectListItemArr_bin.length == 0) ) ) { // 모든 아이템에서 non-selected 클래스 삭제 document.querySelectorAll('.grid-item').forEach(gridItem => { gridItem.classList.remove('non-selected') }) // if (viewerContainer.style.display == 'flex') resetViewer(); resetViewer(); vars.lastContextTarget = undefined; if (target == 'file-area') { vars.lastListGroupTarget = undefined; vars.lastListItem = undefined; // vars.lastContextTarget = undefined; } if (target == 'recycle-bin') { vars.lastListGroupTarget_bin = undefined; vars.lastListItem_bin = undefined; // vars.lastContextTarget_bin = undefined; } // userSelected 삭제 let me = JSON.parse(vars.userInfoString); me.selected = undefined; vars.socket.emit('fileSelect',{me : me}); } else { if (target == 'file-area') { // 모든 아이템에 non-selected 클래스 추가 document.querySelectorAll('.grid-item').forEach(gridItem => { gridItem.classList.add('non-selected') }) } } // 드래그 시작한 대상과 종료된 대상이 다르면 뷰어 초기화 if (draggingStartTarget != draggingEndTarget) { if (target == 'file-area') { vars.lastListItem = undefined; vars.lastListGroupTarget = undefined; } if (target == 'recycle-bin') { vars.lastListItem_bin = undefined; vars.lastListGroupTarget_bin = undefined; } if (viewerContainer.style.display == 'flex') resetViewer(); } // 드래그가 끝나고 pointerup 이벤트가 발생하는 시점에 selected 클래스가 있는 listItem만 필터링 if (target == 'file-area') vars.multiSelectListItemArr = vars.multiSelectListItemArr.filter(item => item.classList.contains('selected')); if (target == 'recycle-bin') vars.multiSelectListItemArr_bin = vars.multiSelectListItemArr_bin.filter(item => item.classList.contains('selected')); // vars.multiSelectListItemArr/vars.multiSelectListItemArr_bin에 포함된 아이템에서 non-selected 클래스 삭제 if (target == 'file-area') { vars.multiSelectListItemArr.forEach(item => { item.classList.remove('non-selected'); }) } if (target == 'recycle-bin') { vars.multiSelectListItemArr_bin.forEach(item => { item.classList.remove('non-selected'); }) } await syncGroupStyle(); // 드래그 시작한 대상의 부모요소와 종료된 대상의 부모요소가 다르면 뷰어 초기화 // if (draggingStartTarget.parentElement != draggingEndTarget.parentElement) { // if (viewerContainer.style.display == 'flex') resetViewer(); // } if (target == 'file-area') vars.lastSelectTarget = vars.multiSelectListItemArr[0]; if (target == 'recycle-bin') vars.lastSelectTarget_bin = vars.multiSelectListItemArr_bin[0]; cancelAnimationFrame(autoScrollFrameId); autoScrollDirection = null; }); }