Fix: 업로드 및 API 연결 문제 해결
- FastAPI 라우터에서 슬래시 문제로 인한 307 리다이렉트 수정 - Nginx 프록시 설정에서 경로 중복 문제 해결 - 계정 관리 시스템 구현 (로그인, 사용자 관리, 권한 설정) - 노트북 연결 기능 수정 (notebook_id 필드 추가) - 메모 트리 UI 개선 (수평 레이아웃, 드래그 기능 제거) - 헤더 UI 개선 및 고정 위치 설정 - 백업/복원 스크립트 추가 - PDF 미리보기 토큰 인증 지원
This commit is contained in:
@@ -63,9 +63,6 @@ window.memoTreeApp = function() {
|
||||
treePanX: 0,
|
||||
treePanY: 0,
|
||||
nodePositions: new Map(), // 노드 ID -> {x, y} 위치 매핑
|
||||
isDragging: false,
|
||||
dragNode: null,
|
||||
dragOffset: { x: 0, y: 0 },
|
||||
|
||||
// 로그인 관련 함수들
|
||||
openLoginModal() {
|
||||
@@ -290,7 +287,15 @@ window.memoTreeApp = function() {
|
||||
|
||||
const node = await window.api.createMemoNode(nodeData);
|
||||
this.treeNodes.push(node);
|
||||
this.selectNode(node);
|
||||
|
||||
// 노드 위치 재계산 (새 노드 추가 후)
|
||||
this.$nextTick(() => {
|
||||
this.calculateNodePositions();
|
||||
// 위치 계산 완료 후 새 노드 선택
|
||||
setTimeout(() => {
|
||||
this.selectNode(node);
|
||||
}, 50);
|
||||
});
|
||||
|
||||
console.log('✅ 루트 노드 생성 완료');
|
||||
} catch (error) {
|
||||
@@ -301,6 +306,11 @@ window.memoTreeApp = function() {
|
||||
|
||||
// 노드 선택
|
||||
selectNode(node) {
|
||||
// 현재 팬 값 저장 (위치 변경 방지)
|
||||
const currentPanX = this.treePanX;
|
||||
const currentPanY = this.treePanY;
|
||||
const currentZoom = this.treeZoom;
|
||||
|
||||
// 이전 노드 저장
|
||||
if (this.selectedNode && this.isEditorDirty) {
|
||||
this.saveNode();
|
||||
@@ -323,6 +333,11 @@ window.memoTreeApp = function() {
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// 팬 값 복원 (위치 변경 방지)
|
||||
this.treePanX = currentPanX;
|
||||
this.treePanY = currentPanY;
|
||||
this.treeZoom = currentZoom;
|
||||
|
||||
console.log('📝 노드 선택:', node.title);
|
||||
},
|
||||
|
||||
@@ -552,8 +567,14 @@ window.memoTreeApp = function() {
|
||||
// 부모 노드 펼치기
|
||||
this.expandedNodes.add(parentNode.id);
|
||||
|
||||
// 새 노드 선택
|
||||
this.selectNode(node);
|
||||
// 노드 위치 재계산 (새 노드 추가 후)
|
||||
this.$nextTick(() => {
|
||||
this.calculateNodePositions();
|
||||
// 위치 계산 완료 후 새 노드 선택
|
||||
setTimeout(() => {
|
||||
this.selectNode(node);
|
||||
}, 50);
|
||||
});
|
||||
|
||||
console.log('✅ 자식 노드 생성 완료');
|
||||
} catch (error) {
|
||||
@@ -623,123 +644,9 @@ window.memoTreeApp = function() {
|
||||
}
|
||||
},
|
||||
|
||||
// 노드 드래그 시작
|
||||
startDragNode(event, node) {
|
||||
event.stopPropagation();
|
||||
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) {
|
||||
@@ -783,15 +690,12 @@ window.memoTreeApp = function() {
|
||||
|
||||
// 노드 위치 계산 및 반환
|
||||
getNodePosition(node) {
|
||||
if (!this.nodePositions.has(node.id)) {
|
||||
this.calculateNodePositions();
|
||||
}
|
||||
|
||||
// 위치가 없으면 기본 위치 반환 (전체 재계산 방지)
|
||||
const pos = this.nodePositions.get(node.id) || { x: 0, y: 0 };
|
||||
return `left: ${pos.x}px; top: ${pos.y}px;`;
|
||||
},
|
||||
|
||||
// 트리 노드 위치 자동 계산
|
||||
// 트리 노드 위치 자동 계산 (가로 방향: 왼쪽에서 오른쪽)
|
||||
calculateNodePositions() {
|
||||
const canvas = document.getElementById('tree-canvas');
|
||||
if (!canvas) return;
|
||||
@@ -802,11 +706,11 @@ window.memoTreeApp = function() {
|
||||
// 노드 크기 설정
|
||||
const nodeWidth = 200;
|
||||
const nodeHeight = 80;
|
||||
const levelHeight = 150; // 레벨 간 간격
|
||||
const nodeSpacing = 50; // 노드 간 간격
|
||||
const levelWidth = 250; // 레벨 간 가로 간격 (왼쪽에서 오른쪽)
|
||||
const nodeSpacing = 100; // 노드 간 세로 간격
|
||||
const margin = 100; // 여백
|
||||
|
||||
// 레벨별 노드 그룹화
|
||||
// 레벨별 노드 그룹화 (가로 방향)
|
||||
const levels = new Map();
|
||||
|
||||
// 루트 노드들 찾기
|
||||
@@ -814,7 +718,7 @@ window.memoTreeApp = function() {
|
||||
|
||||
if (rootNodes.length === 0) return;
|
||||
|
||||
// BFS로 레벨별 노드 배치
|
||||
// BFS로 레벨별 노드 배치 (가로 방향)
|
||||
const queue = [];
|
||||
rootNodes.forEach(node => {
|
||||
queue.push({ node, level: 0 });
|
||||
@@ -835,25 +739,25 @@ window.memoTreeApp = function() {
|
||||
});
|
||||
}
|
||||
|
||||
// 트리 전체 크기 계산
|
||||
// 트리 전체 크기 계산 (가로 방향)
|
||||
const maxLevel = Math.max(...levels.keys());
|
||||
const maxNodesInLevel = Math.max(...Array.from(levels.values()).map(nodes => nodes.length));
|
||||
|
||||
const treeWidth = maxNodesInLevel * nodeWidth + (maxNodesInLevel - 1) * nodeSpacing;
|
||||
const treeHeight = (maxLevel + 1) * levelHeight;
|
||||
const treeWidth = (maxLevel + 1) * levelWidth; // 가로 방향 전체 너비
|
||||
const treeHeight = maxNodesInLevel * nodeHeight + (maxNodesInLevel - 1) * nodeSpacing; // 세로 방향 전체 높이
|
||||
|
||||
// 캔버스 중앙에 트리 배치하기 위한 오프셋 계산
|
||||
const offsetX = Math.max(margin, (canvasWidth - treeWidth) / 2);
|
||||
const offsetY = Math.max(margin, (canvasHeight - treeHeight) / 2);
|
||||
|
||||
// 각 레벨의 노드들 위치 계산
|
||||
// 각 레벨의 노드들 위치 계산 (가로 방향)
|
||||
levels.forEach((nodes, level) => {
|
||||
const y = offsetY + level * levelHeight;
|
||||
const levelWidth = nodes.length * nodeWidth + (nodes.length - 1) * nodeSpacing;
|
||||
const startX = offsetX + (treeWidth - levelWidth) / 2;
|
||||
const x = offsetX + level * levelWidth; // 가로 위치 (왼쪽에서 오른쪽)
|
||||
const levelHeight = nodes.length * nodeHeight + (nodes.length - 1) * nodeSpacing;
|
||||
const startY = offsetY + (treeHeight - levelHeight) / 2; // 세로 중앙 정렬
|
||||
|
||||
nodes.forEach((node, index) => {
|
||||
const x = startX + index * (nodeWidth + nodeSpacing);
|
||||
const y = startY + index * (nodeHeight + nodeSpacing);
|
||||
this.nodePositions.set(node.id, { x, y });
|
||||
});
|
||||
});
|
||||
@@ -893,17 +797,17 @@ window.memoTreeApp = function() {
|
||||
// 연결선 생성
|
||||
const line = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
|
||||
// 부모 노드 하단 중앙에서 시작
|
||||
const startX = parentPos.x + 100; // 노드 중앙
|
||||
const startY = parentPos.y + 80; // 노드 하단
|
||||
// 부모 노드 오른쪽 중앙에서 시작 (가로 방향)
|
||||
const startX = parentPos.x + 200; // 노드 오른쪽 끝
|
||||
const startY = parentPos.y + 40; // 노드 세로 중앙
|
||||
|
||||
// 자식 노드 상단 중앙으로 연결
|
||||
const endX = childPos.x + 100; // 노드 중앙
|
||||
const endY = childPos.y; // 노드 상단
|
||||
// 자식 노드 왼쪽 중앙으로 연결 (가로 방향)
|
||||
const endX = childPos.x; // 노드 왼쪽 끝
|
||||
const endY = childPos.y + 40; // 노드 세로 중앙
|
||||
|
||||
// 곡선 경로 생성 (베지어 곡선)
|
||||
const midY = startY + (endY - startY) / 2;
|
||||
const path = `M ${startX} ${startY} C ${startX} ${midY} ${endX} ${midY} ${endX} ${endY}`;
|
||||
// 곡선 경로 생성 (베지어 곡선, 가로 방향)
|
||||
const midX = startX + (endX - startX) / 2;
|
||||
const path = `M ${startX} ${startY} C ${midX} ${startY} ${midX} ${endY} ${endX} ${endY}`;
|
||||
|
||||
line.setAttribute('d', path);
|
||||
line.setAttribute('stroke', '#9CA3AF');
|
||||
|
||||
Reference in New Issue
Block a user