리펙토링 - 전체 프로젝트 코드 (Codex)
This commit is contained in:
@@ -1,25 +1,27 @@
|
||||
let _grid, _gridContainer, _dragGhostEl, _fileExplorerEl;
|
||||
import { createAutoScroller } from './drag/auto-scroll.js';
|
||||
import { createDragGhost } from './drag/ghost.js';
|
||||
|
||||
let _grid, _gridContainer, _fileExplorerEl;
|
||||
let _state, _cb, _onFolderDrop;
|
||||
let _dropSeparator = null;
|
||||
let _originPlaceholders = [];
|
||||
let _dropTargetIdx = null;
|
||||
let _autoScroller = null;
|
||||
let _ghost = null;
|
||||
|
||||
let dragMode = null;
|
||||
let dragSrcIdx = null;
|
||||
let _hoveredTreeRow = null;
|
||||
|
||||
let autoScrollRAF = null;
|
||||
let autoScrollSpeed = 0;
|
||||
let lastScrollTs = null;
|
||||
|
||||
export function init({ grid, gridContainer, dragGhostEl, state, callbacks, fileExplorerEl, onFolderDrop }) {
|
||||
_grid = grid;
|
||||
_gridContainer = gridContainer;
|
||||
_dragGhostEl = dragGhostEl;
|
||||
_state = state;
|
||||
_cb = callbacks;
|
||||
_fileExplorerEl = fileExplorerEl;
|
||||
_onFolderDrop = onFolderDrop;
|
||||
_autoScroller = createAutoScroller(_gridContainer, () => Boolean(dragMode));
|
||||
_ghost = createDragGhost(dragGhostEl);
|
||||
|
||||
_dropSeparator = document.createElement('div');
|
||||
_dropSeparator.className = 'drop-separator';
|
||||
@@ -49,8 +51,8 @@ export function setupCard(card) {
|
||||
|
||||
function _onWindowDragOver(e) {
|
||||
if (!dragMode) return;
|
||||
_moveGhost(e);
|
||||
_trackScroll(e.clientY);
|
||||
_ghost.move(e);
|
||||
_autoScroller.track(e.clientY);
|
||||
}
|
||||
|
||||
// ── 카드 드래그 핸들러 ─────────────────────────────
|
||||
@@ -68,7 +70,7 @@ function _onDragStart(e) {
|
||||
const img = new Image();
|
||||
img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
||||
e.dataTransfer.setDragImage(img, 0, 0);
|
||||
_showGhost(e, `📦 ${count}개 이동 중`);
|
||||
_ghost.show(e, `📦 ${count}개 이동 중`);
|
||||
|
||||
// 브라우저가 드래그 이미지를 캡처한 뒤 카드 숨기고 원본 위치 표시
|
||||
requestAnimationFrame(() => {
|
||||
@@ -136,8 +138,8 @@ function _performDrop() {
|
||||
}
|
||||
|
||||
function _onDragEnd() {
|
||||
_stopScroll();
|
||||
_hideGhost();
|
||||
_autoScroller.stop();
|
||||
_ghost.hide();
|
||||
_hideDropSeparator();
|
||||
_clearTreeHover();
|
||||
_clearOriginPlaceholders();
|
||||
@@ -163,19 +165,19 @@ function _onExplorerDragOver(e) {
|
||||
if (_hoveredTreeRow) _hoveredTreeRow.classList.add('drop-target');
|
||||
}
|
||||
|
||||
if (dragMode === 'single' && row && _dragGhostEl.style.display === 'none') {
|
||||
_showGhost(e, '📁 1개 폴더로 이동');
|
||||
if (dragMode === 'single' && row && _ghost.isHidden()) {
|
||||
_ghost.show(e, '📁 1개 폴더로 이동');
|
||||
}
|
||||
if (dragMode === 'multi' && row) {
|
||||
_dragGhostEl.textContent = `📁 ${_state.selectedIdxs.size}개 폴더로 이동`;
|
||||
_ghost.setText(`📁 ${_state.selectedIdxs.size}개 폴더로 이동`);
|
||||
}
|
||||
}
|
||||
|
||||
function _onExplorerDragLeave(e) {
|
||||
if (!_fileExplorerEl.contains(e.relatedTarget)) {
|
||||
_clearTreeHover();
|
||||
if (dragMode === 'single') _hideGhost();
|
||||
if (dragMode === 'multi') _dragGhostEl.textContent = `📦 ${_state.selectedIdxs.size}개 이동 중`;
|
||||
if (dragMode === 'single') _ghost.hide();
|
||||
if (dragMode === 'multi') _ghost.setText(`📦 ${_state.selectedIdxs.size}개 이동 중`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,65 +289,6 @@ function _moveMulti(targetIdx) {
|
||||
_cb.pushHistory?.(movedFiles.map(f => f.path));
|
||||
}
|
||||
|
||||
// ── 자동 스크롤 ───────────────────────────────────
|
||||
|
||||
function _trackScroll(clientY) {
|
||||
const vh = window.innerHeight;
|
||||
const topLine = vh * 0.1;
|
||||
const botLine = vh * 0.9;
|
||||
|
||||
if (clientY < topLine) {
|
||||
const ratio = clientY / topLine;
|
||||
_startScroll(-vh * (1 - 0.8 * ratio));
|
||||
} else if (clientY > botLine) {
|
||||
const ratio = (clientY - botLine) / (vh * 0.1);
|
||||
_startScroll(vh * (0.2 + 0.8 * ratio));
|
||||
} else {
|
||||
_stopScroll();
|
||||
}
|
||||
}
|
||||
|
||||
function _startScroll(speed) {
|
||||
autoScrollSpeed = speed;
|
||||
if (!autoScrollRAF) {
|
||||
lastScrollTs = performance.now();
|
||||
autoScrollRAF = requestAnimationFrame(_tickScroll);
|
||||
}
|
||||
}
|
||||
|
||||
function _stopScroll() {
|
||||
autoScrollSpeed = 0;
|
||||
if (autoScrollRAF) {
|
||||
cancelAnimationFrame(autoScrollRAF);
|
||||
autoScrollRAF = null;
|
||||
}
|
||||
}
|
||||
|
||||
function _tickScroll(now) {
|
||||
if (!dragMode) { _stopScroll(); return; }
|
||||
const dt = (now - lastScrollTs) / 1000;
|
||||
lastScrollTs = now;
|
||||
_gridContainer.scrollTop += autoScrollSpeed * dt;
|
||||
autoScrollRAF = requestAnimationFrame(_tickScroll);
|
||||
}
|
||||
|
||||
// ── 드래그 고스트 ─────────────────────────────────
|
||||
|
||||
function _showGhost(e, text) {
|
||||
_dragGhostEl.textContent = text;
|
||||
_dragGhostEl.style.display = 'block';
|
||||
_moveGhost(e);
|
||||
}
|
||||
|
||||
function _moveGhost(e) {
|
||||
_dragGhostEl.style.left = (e.clientX + 16) + 'px';
|
||||
_dragGhostEl.style.top = (e.clientY - 14) + 'px';
|
||||
}
|
||||
|
||||
function _hideGhost() {
|
||||
_dragGhostEl.style.display = 'none';
|
||||
}
|
||||
|
||||
function _getCardEl(idx) {
|
||||
return _grid.querySelector(`.card[data-idx="${idx}"]`);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
export function createAutoScroller(container, isActive) {
|
||||
let frameId = null;
|
||||
let speed = 0;
|
||||
let lastTs = null;
|
||||
|
||||
function track(clientY) {
|
||||
const vh = window.innerHeight;
|
||||
const topLine = vh * 0.1;
|
||||
const botLine = vh * 0.9;
|
||||
|
||||
if (clientY < topLine) {
|
||||
const ratio = clientY / topLine;
|
||||
start(-vh * (1 - 0.8 * ratio));
|
||||
} else if (clientY > botLine) {
|
||||
const ratio = (clientY - botLine) / (vh * 0.1);
|
||||
start(vh * (0.2 + 0.8 * ratio));
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
function start(nextSpeed) {
|
||||
speed = nextSpeed;
|
||||
if (!frameId) {
|
||||
lastTs = performance.now();
|
||||
frameId = requestAnimationFrame(tick);
|
||||
}
|
||||
}
|
||||
|
||||
function stop() {
|
||||
speed = 0;
|
||||
if (frameId) {
|
||||
cancelAnimationFrame(frameId);
|
||||
frameId = null;
|
||||
}
|
||||
}
|
||||
|
||||
function tick(now) {
|
||||
if (!isActive()) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
const dt = (now - lastTs) / 1000;
|
||||
lastTs = now;
|
||||
container.scrollTop += speed * dt;
|
||||
frameId = requestAnimationFrame(tick);
|
||||
}
|
||||
|
||||
return { track, stop };
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
export function createDragGhost(el) {
|
||||
function show(e, text) {
|
||||
el.textContent = text;
|
||||
el.style.display = 'block';
|
||||
move(e);
|
||||
}
|
||||
|
||||
function move(e) {
|
||||
el.style.left = `${e.clientX + 16}px`;
|
||||
el.style.top = `${e.clientY - 14}px`;
|
||||
}
|
||||
|
||||
function hide() {
|
||||
el.style.display = 'none';
|
||||
}
|
||||
|
||||
function setText(text) {
|
||||
el.textContent = text;
|
||||
}
|
||||
|
||||
function isHidden() {
|
||||
return el.style.display === 'none';
|
||||
}
|
||||
|
||||
return { hide, isHidden, move, setText, show };
|
||||
}
|
||||
Reference in New Issue
Block a user