feat: [planning-design] 블록 Ctrl+C/V 복사 붙여넣기 및 Delete 삭제

- Ctrl+C: 선택된 블록 클립보드 복사
- Ctrl+V: 클립보드 블록 붙여넣기 (24px 오프셋)
- Delete/Backspace: 선택된 블록 삭제
- 연속 Ctrl+V 시 오프셋 누적으로 겹침 방지
This commit is contained in:
김보곤
2026-03-07 23:43:16 +09:00
parent 049e66e426
commit abebf0e452

View File

@@ -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(''));