348 lines
15 KiB
JavaScript
348 lines
15 KiB
JavaScript
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;
|
|
});
|
|
} |