diff --git a/resources/views/rd/planning-design/index.blade.php b/resources/views/rd/planning-design/index.blade.php index 87cb4e7f..68e1fb7e 100644 --- a/resources/views/rd/planning-design/index.blade.php +++ b/resources/views/rd/planning-design/index.blade.php @@ -917,10 +917,10 @@ - -
@@ -1962,6 +1962,9 @@ function planningCanvas() { _sbDrag: null, // { blk, startX, startY, origX, origY } _sbResize: null, // { blk, dir, startX, startY, origW, origH } _sbClipboard: null, // copied block data + _sbHistory: [], + _sbHistoryIdx: -1, + _sbHistoryPaused: false, sbTplOpen: false, sbTplTab: 'preset', sbTplSearch: '', @@ -2757,8 +2760,8 @@ function planningCanvas() { if (e.key === 'v' || e.key === 'V') this.tool = 'select'; if (e.key === 'h' || e.key === 'H') this.tool = 'pan'; if (e.key === 'c' || e.key === 'C') this.tool = 'connect'; - if ((e.ctrlKey || e.metaKey) && e.key === 'z') { e.preventDefault(); this.undoAction(); } - if ((e.ctrlKey || e.metaKey) && e.key === 'y') { e.preventDefault(); this.redoAction(); } + if ((e.ctrlKey || e.metaKey) && e.key === 'z') { e.preventDefault(); this.viewMode === 'storyboard' ? this.sbUndo() : this.undoAction(); return; } + if ((e.ctrlKey || e.metaKey) && e.key === 'y') { e.preventDefault(); this.viewMode === 'storyboard' ? this.sbRedo() : this.redoAction(); return; } 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 @@ -2914,6 +2917,7 @@ function planningCanvas() { const page = this.sbCurrentPage; if (!page) return; if (!page.blocks) page.blocks = []; + this.sbPushHistory(); const blk = this.sbNewBlock(type); page.blocks.push(blk); this.sbSelectedBlock = blk.id; @@ -2923,6 +2927,7 @@ function planningCanvas() { sbAddBlockAfter(idx, type) { const page = this.sbCurrentPage; if (!page || !page.blocks) return; + this.sbPushHistory(); const blk = this.sbNewBlock(type); page.blocks.splice(idx + 1, 0, blk); this.sbSelectedBlock = blk.id; @@ -2932,6 +2937,7 @@ function planningCanvas() { sbRemoveBlock(idx) { const page = this.sbCurrentPage; if (!page || !page.blocks) return; + this.sbPushHistory(); page.blocks.splice(idx, 1); this.sbSelectedBlock = null; this.autoSave(); @@ -2940,6 +2946,7 @@ function planningCanvas() { sbDuplicateBlock(idx) { const page = this.sbCurrentPage; if (!page || !page.blocks) return; + this.sbPushHistory(); const copy = JSON.parse(JSON.stringify(page.blocks[idx])); copy.id = 'blk_' + Date.now() + '_' + Math.random().toString(36).slice(2, 5); copy.x = (copy.x || 0) + 20; @@ -2954,6 +2961,7 @@ function planningCanvas() { // 더블클릭 편집 모드에서는 드래그 안 함 if (this.sbEditingBlock === blk.id) return; this.sbSelectedBlock = blk.id; + this.sbPushHistory(); this._sbDrag = { blk, startX: e.clientX, @@ -2966,6 +2974,7 @@ function planningCanvas() { sbResizeStart(blk, dir, e) { this.sbSelectedBlock = blk.id; + this.sbPushHistory(); this._sbResize = { blk, dir, @@ -3029,6 +3038,7 @@ function planningCanvas() { const page = this.sbCurrentPage; if (!page) return; if (!page.blocks) page.blocks = []; + this.sbPushHistory(); 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; @@ -3044,11 +3054,62 @@ function planningCanvas() { if (!page || !page.blocks) return; const idx = page.blocks.findIndex(b => b.id === this.sbSelectedBlock); if (idx < 0) return; + this.sbPushHistory(); page.blocks.splice(idx, 1); this.sbSelectedBlock = null; this.autoSave(); }, + // ===== Storyboard Undo/Redo ===== + sbPushHistory() { + if (this._sbHistoryPaused) return; + const page = this.sbCurrentPage; + if (!page || !page.blocks) return; + const snap = JSON.parse(JSON.stringify(page.blocks)); + // 현재 위치 이후의 히스토리 삭제 (새 분기) + if (this._sbHistoryIdx < this._sbHistory.length - 1) { + this._sbHistory = this._sbHistory.slice(0, this._sbHistoryIdx + 1); + } + this._sbHistory.push(snap); + // 최대 50개 + if (this._sbHistory.length > 50) { + this._sbHistory = this._sbHistory.slice(this._sbHistory.length - 50); + } + this._sbHistoryIdx = this._sbHistory.length - 1; + }, + + sbUndo() { + if (this._sbHistoryIdx <= 0) return; + const page = this.sbCurrentPage; + if (!page) return; + // 처음 undo 시 현재 상태 저장 + if (this._sbHistoryIdx === this._sbHistory.length - 1) { + const snap = JSON.parse(JSON.stringify(page.blocks || [])); + if (this._sbHistory.length === 0 || JSON.stringify(this._sbHistory[this._sbHistoryIdx]) !== JSON.stringify(snap)) { + this._sbHistory.push(snap); + this._sbHistoryIdx = this._sbHistory.length - 1; + } + } + this._sbHistoryIdx--; + this._sbHistoryPaused = true; + page.blocks = JSON.parse(JSON.stringify(this._sbHistory[this._sbHistoryIdx])); + this.sbSelectedBlock = null; + this._sbHistoryPaused = false; + this.autoSave(); + }, + + sbRedo() { + if (this._sbHistoryIdx >= this._sbHistory.length - 1) return; + const page = this.sbCurrentPage; + if (!page) return; + this._sbHistoryIdx++; + this._sbHistoryPaused = true; + page.blocks = JSON.parse(JSON.stringify(this._sbHistory[this._sbHistoryIdx])); + this.sbSelectedBlock = null; + this._sbHistoryPaused = false; + this.autoSave(); + }, + sbTableAddRow(blk) { const colCount = blk.cols.length; blk.rows.push(Array(colCount).fill('')); @@ -3080,6 +3141,7 @@ function planningCanvas() { const page = this.sbCurrentPage; if (!page) return; if (!page.blocks) page.blocks = []; + this.sbPushHistory(); const blocks = JSON.parse(JSON.stringify(tpl.blocks)); // 기존 블록 아래에 배치 let curY = 16;