초기 PM 소스 전체 업로드
This commit is contained in:
348
views/main/jsm/archive/fileDrag.js
Normal file
348
views/main/jsm/archive/fileDrag.js
Normal file
@@ -0,0 +1,348 @@
|
||||
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;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user