메모 트리 시스템 드래그 앤 드롭 기능 완성

�� 주요 기능 추가:
- 노드 드래그 앤 드롭으로 부모-자식 관계 변경
- 드래그 중 시각적 피드백 (투명도, 크기 변화)
- 드롭 대상 하이라이트 효과
- 실시간 노드 위치 추적 및 이동

🔧 기술적 구현:
- startDragNode, handleNodeDrag, endNodeDrag 함수 구현
- data-node-id 속성으로 노드 식별
- document.elementsFromPoint로 드롭 대상 감지
- moveNodeToParent API 호출로 실제 데이터 업데이트

🎨 UI/UX 개선:
- 드래그 중 노드 스타일 변경 (opacity, scale, z-index)
- 드롭 대상 하이라이트 CSS (.drop-target-highlight)
- 토스트 알림 시스템 추가
- 성공/실패 피드백 제공

📱 사용자 경험:
- 직관적인 드래그 앤 드롭 인터페이스
- 실시간 시각적 피드백
- 자동 트리 구조 업데이트
- 에러 처리 및 사용자 알림
This commit is contained in:
Hyungi Ahn
2025-09-02 16:45:31 +09:00
parent ea9f4dfaa9
commit c5d09ed948
2 changed files with 203 additions and 3 deletions

View File

@@ -23,6 +23,13 @@ window.memoTreeApp = function() {
showLoginModal: false,
showMobileEditModal: false,
// 알림 시스템
notification: {
show: false,
message: '',
type: 'info' // 'success', 'error', 'info'
},
// 로그인 폼 상태
loginForm: {
email: '',
@@ -503,6 +510,20 @@ window.memoTreeApp = function() {
},
// 트리 타입별 아이콘 가져오기
// 알림 표시
showNotification(message, type = 'info') {
this.notification = {
show: true,
message: message,
type: type
};
// 3초 후 자동으로 숨김
setTimeout(() => {
this.notification.show = false;
}, 3000);
},
getTreeIcon(treeType) {
const icons = {
novel: '📚',
@@ -604,10 +625,144 @@ window.memoTreeApp = function() {
// 노드 드래그 시작
startDragNode(event, node) {
// 현재는 드래그 기능 비활성화 (패닝과 충돌 방지)
event.stopPropagation();
console.log('드래그 시작:', node.title);
// TODO: 노드 드래그 앤 드롭 구현
event.preventDefault();
console.log('🎯 노드 드래그 시작:', node.title);
this.isDragging = true;
this.dragNode = node;
// 드래그 시작 위치 계산
const rect = event.target.getBoundingClientRect();
this.dragOffset = {
x: event.clientX - rect.left,
y: event.clientY - rect.top
};
// 드래그 중인 노드 스타일 적용
event.target.style.opacity = '0.7';
event.target.style.transform = 'scale(1.05)';
event.target.style.zIndex = '1000';
// 이벤트 리스너 등록
document.addEventListener('mousemove', this.handleNodeDrag.bind(this));
document.addEventListener('mouseup', this.endNodeDrag.bind(this));
},
// 노드 드래그 처리
handleNodeDrag(event) {
if (!this.isDragging || !this.dragNode) return;
event.preventDefault();
// 드래그 중인 노드 위치 업데이트
const dragElement = document.querySelector(`[data-node-id="${this.dragNode.id}"]`);
if (dragElement) {
const newX = event.clientX - this.dragOffset.x;
const newY = event.clientY - this.dragOffset.y;
dragElement.style.position = 'fixed';
dragElement.style.left = `${newX}px`;
dragElement.style.top = `${newY}px`;
dragElement.style.pointerEvents = 'none';
}
// 드롭 대상 하이라이트
this.highlightDropTarget(event);
},
// 드롭 대상 하이라이트
highlightDropTarget(event) {
// 모든 노드에서 하이라이트 제거
document.querySelectorAll('.tree-diagram-node').forEach(el => {
el.classList.remove('drop-target-highlight');
});
// 현재 마우스 위치의 노드 찾기
const elements = document.elementsFromPoint(event.clientX, event.clientY);
const targetNode = elements.find(el =>
el.classList.contains('tree-diagram-node') &&
el.getAttribute('data-node-id') !== this.dragNode.id
);
if (targetNode) {
targetNode.classList.add('drop-target-highlight');
}
},
// 노드 드래그 종료
endNodeDrag(event) {
if (!this.isDragging || !this.dragNode) return;
console.log('🎯 노드 드래그 종료');
// 드롭 대상 찾기
const elements = document.elementsFromPoint(event.clientX, event.clientY);
const targetElement = elements.find(el =>
el.classList.contains('tree-diagram-node') &&
el.getAttribute('data-node-id') !== this.dragNode.id
);
let targetNodeId = null;
if (targetElement) {
targetNodeId = targetElement.getAttribute('data-node-id');
}
// 드래그 중인 노드 스타일 복원
const dragElement = document.querySelector(`[data-node-id="${this.dragNode.id}"]`);
if (dragElement) {
dragElement.style.opacity = '';
dragElement.style.transform = '';
dragElement.style.zIndex = '';
dragElement.style.position = '';
dragElement.style.left = '';
dragElement.style.top = '';
dragElement.style.pointerEvents = '';
}
// 모든 하이라이트 제거
document.querySelectorAll('.tree-diagram-node').forEach(el => {
el.classList.remove('drop-target-highlight');
});
// 실제 노드 이동 처리
if (targetNodeId && targetNodeId !== this.dragNode.id) {
this.moveNodeToParent(this.dragNode.id, targetNodeId);
}
// 상태 초기화
this.isDragging = false;
this.dragNode = null;
this.dragOffset = { x: 0, y: 0 };
// 이벤트 리스너 제거
document.removeEventListener('mousemove', this.handleNodeDrag);
document.removeEventListener('mouseup', this.endNodeDrag);
},
// 노드를 다른 부모로 이동
async moveNodeToParent(nodeId, newParentId) {
try {
console.log(`📦 노드 이동: ${nodeId} -> 부모: ${newParentId}`);
const moveData = {
parent_id: newParentId,
sort_order: 0 // 새 부모의 첫 번째 자식으로
};
await window.api.moveMemoNode(nodeId, moveData);
// 트리 다시 로드
await this.loadTreeNodes();
console.log('✅ 노드 이동 완료');
this.showNotification('노드가 이동되었습니다.', 'success');
} catch (error) {
console.error('❌ 노드 이동 실패:', error);
this.showNotification('노드 이동에 실패했습니다.', 'error');
}
},
// 노드 인라인 편집