From 5271072e20418389cefdb0b97d2d55a698129981 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 23:43:16 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[planning-design]=20=EB=B8=94=EB=A1=9D?= =?UTF-8?q?=20Ctrl+C/V=20=EB=B3=B5=EC=82=AC=20=EB=B6=99=EC=97=AC=EB=84=A3?= =?UTF-8?q?=EA=B8=B0=20=EB=B0=8F=20Delete=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ctrl+C: 선택된 블록 클립보드 복사 - Ctrl+V: 클립보드 블록 붙여넣기 (24px 오프셋) - Delete/Backspace: 선택된 블록 삭제 - 연속 Ctrl+V 시 오프셋 누적으로 겹침 방지 --- .../views/rd/planning-design/index.blade.php | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/resources/views/rd/planning-design/index.blade.php b/resources/views/rd/planning-design/index.blade.php index e40b6187..87cb4e7f 100644 --- a/resources/views/rd/planning-design/index.blade.php +++ b/resources/views/rd/planning-design/index.blade.php @@ -1961,6 +1961,7 @@ function planningCanvas() { _sbBlockDragIdx: null, _sbDrag: null, // { blk, startX, startY, origX, origY } _sbResize: null, // { blk, dir, startX, startY, origW, origH } + _sbClipboard: null, // copied block data sbTplOpen: false, sbTplTab: 'preset', sbTplSearch: '', @@ -2760,6 +2761,31 @@ function planningCanvas() { if ((e.ctrlKey || e.metaKey) && e.key === 'y') { e.preventDefault(); this.redoAction(); } if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); this.saveProject(); } if ((e.ctrlKey || e.metaKey) && e.key === 'd') { e.preventDefault(); this.duplicateNode(); } + // 스토리보드 블록 Ctrl+C / Ctrl+V / Delete + if (this.viewMode === 'storyboard' && this.sbSelectedBlock) { + if ((e.ctrlKey || e.metaKey) && (e.key === 'c' || e.key === 'C')) { + e.preventDefault(); + this.sbCopyBlock(); + return; + } + if ((e.ctrlKey || e.metaKey) && (e.key === 'v' || e.key === 'V')) { + e.preventDefault(); + this.sbPasteBlock(); + return; + } + if (e.key === 'Delete' || e.key === 'Backspace') { + e.preventDefault(); + this.sbDeleteSelectedBlock(); + return; + } + } + if (this.viewMode === 'storyboard' && !this.sbSelectedBlock) { + if ((e.ctrlKey || e.metaKey) && (e.key === 'v' || e.key === 'V') && this._sbClipboard) { + e.preventDefault(); + this.sbPasteBlock(); + return; + } + } if ((e.ctrlKey || e.metaKey) && e.key === 'f') { if (this.viewMode === 'kanban' || this.viewMode === 'list') { e.preventDefault(); @@ -2990,6 +3016,39 @@ function planningCanvas() { } }, + sbCopyBlock() { + const page = this.sbCurrentPage; + if (!page || !page.blocks) return; + const blk = page.blocks.find(b => b.id === this.sbSelectedBlock); + if (!blk) return; + this._sbClipboard = JSON.parse(JSON.stringify(blk)); + }, + + sbPasteBlock() { + if (!this._sbClipboard) return; + const page = this.sbCurrentPage; + if (!page) return; + if (!page.blocks) page.blocks = []; + const copy = JSON.parse(JSON.stringify(this._sbClipboard)); + copy.id = 'blk_' + Date.now() + '_' + Math.random().toString(36).slice(2, 5); + copy.x = (copy.x || 0) + 24; + copy.y = (copy.y || 0) + 24; + page.blocks.push(copy); + this.sbSelectedBlock = copy.id; + this._sbClipboard = copy; // 연속 붙여넣기 시 오프셋 누적 + this.autoSave(); + }, + + sbDeleteSelectedBlock() { + const page = this.sbCurrentPage; + if (!page || !page.blocks) return; + const idx = page.blocks.findIndex(b => b.id === this.sbSelectedBlock); + if (idx < 0) return; + page.blocks.splice(idx, 1); + this.sbSelectedBlock = null; + this.autoSave(); + }, + sbTableAddRow(blk) { const colCount = blk.cols.length; blk.rows.push(Array(colCount).fill(''));