From 7b9ce99541473d4620ae53dc47e7edb0a000c0fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Sat, 7 Mar 2026 22:28:40 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[rd]=20=EA=B8=B0=ED=9A=8D=EB=94=94?= =?UTF-8?q?=EC=9E=90=EC=9D=B8=20=EC=97=B0=EA=B2=B0=EC=84=A0=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20+=20=EC=8A=A4=ED=8E=98=EC=9D=B4=EC=8A=A4=EB=B0=94?= =?UTF-8?q?=20=ED=8C=A8=EB=8B=9D=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 연결선 클릭 선택 → Delete/Backspace로 삭제 - 우클릭 컨텍스트 메뉴에 '연결선 삭제' 항목 추가 - 스페이스바 누른 채 마우스 드래그로 캔버스 이동 (Figma/FigJam 방식) - 패닝 중 커서 grab/grabbing 변경 --- .../views/rd/planning-design/index.blade.php | 65 ++++++++++++++++--- 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/resources/views/rd/planning-design/index.blade.php b/resources/views/rd/planning-design/index.blade.php index 26584332..e131dace 100644 --- a/resources/views/rd/planning-design/index.blade.php +++ b/resources/views/rd/planning-design/index.blade.php @@ -504,7 +504,7 @@ } -
+
{{-- ===== Top Toolbar ===== --}}
@@ -1054,7 +1054,8 @@
빠른 노드 추가
-
삭제
+
연결선 삭제
+
노드 삭제
@@ -1117,6 +1118,7 @@ function planningCanvas() { panning: false, panStartX: 0, panStartY: 0, + spaceHeld: false, // Connection Drawing drawingConnection: false, @@ -1509,11 +1511,22 @@ function planningCanvas() { // ===== Canvas Events ===== onCanvasMouseDown(e) { - if (e.target.closest('.pc-node') || e.target.closest('.pc-port')) return; - if (this.tool === 'pan' || e.button === 1) { + if (e.target.closest('.pc-node') || e.target.closest('.pc-port')) { + // 스페이스바 누른 채 노드 위에서도 패닝 가능 + if (this.spaceHeld) { + this.panning = true; + this.panStartX = e.clientX - this.panX * this.zoom; + this.panStartY = e.clientY - this.panY * this.zoom; + document.getElementById('canvasWrap')?.style.setProperty('cursor', 'grabbing'); + e.preventDefault(); + } + return; + } + if (this.tool === 'pan' || this.spaceHeld || e.button === 1) { this.panning = true; this.panStartX = e.clientX - this.panX * this.zoom; this.panStartY = e.clientY - this.panY * this.zoom; + document.getElementById('canvasWrap')?.style.setProperty('cursor', 'grabbing'); e.preventDefault(); } else { this.selectedNode = null; @@ -1551,7 +1564,12 @@ function planningCanvas() { this.pushHistory(); this.autoSave(); } - if (this.panning) { this.panning = false; } + if (this.panning) { + this.panning = false; + const wrap = document.getElementById('canvasWrap'); + if (this.spaceHeld) wrap?.style.setProperty('cursor', 'grab'); + else wrap?.style.removeProperty('cursor'); + } if (this.drawingConnection) { // Check if dropped on a node port const target = e.target.closest('.pc-node'); @@ -1699,11 +1717,22 @@ function planningCanvas() { // ===== Keyboard Shortcuts ===== handleKeyDown(e) { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) return; + + // 스페이스바: 패닝 모드 (누르고 있는 동안) + if (e.key === ' ' || e.code === 'Space') { + e.preventDefault(); + if (!this.spaceHeld) { + this.spaceHeld = true; + this._toolBeforeSpace = this.tool; + this.tool = 'pan'; + document.getElementById('canvasWrap')?.style.setProperty('cursor', 'grab'); + } + return; + } + if (e.key === 'Delete' || e.key === 'Backspace') { if (this.selectedConnection) { - this.connections = this.connections.filter(c => c.id !== this.selectedConnection.id); - this.selectedConnection = null; - this.pushHistory(); this.autoSave(); + this.deleteSelectedConnection(); } else { this.deleteSelectedNode(); } @@ -1726,6 +1755,26 @@ function planningCanvas() { } }, + handleKeyUp(e) { + if (e.key === ' ' || e.code === 'Space') { + if (this.spaceHeld) { + this.spaceHeld = false; + this.panning = false; + this.tool = this._toolBeforeSpace || 'select'; + document.getElementById('canvasWrap')?.style.removeProperty('cursor'); + } + } + }, + + // ===== Connection Delete ===== + deleteSelectedConnection() { + if (!this.selectedConnection) return; + this.connections = this.connections.filter(c => c.id !== this.selectedConnection.id); + this.selectedConnection = null; + this.pushHistory(); + this.autoSave(); + }, + // ===== Context Menu ===== showContextMenu(e) { this.contextMenuPos = { x: e.clientX, y: e.clientY };