/** * TASKMATE - Undo/Redo Module * =========================== * Undo and redo functionality */ import store from './store.js'; import api from './api.js'; class UndoManager { constructor() { this.bindEvents(); } bindEvents() { window.addEventListener('undo:perform', () => this.undo()); window.addEventListener('redo:perform', () => this.redo()); } // ===================== // UNDO/REDO // ===================== async undo() { if (!store.canUndo()) { this.showInfo('Nichts zum Rückgängigmachen'); return; } const action = store.popUndo(); if (!action) return; try { await this.executeUndo(action); this.showSuccess('Rückgängig gemacht'); } catch (error) { console.error('Undo failed:', error); this.showError('Rückgängig fehlgeschlagen'); // Re-push to undo stack store.pushUndo(action); } } async redo() { if (!store.canRedo()) { this.showInfo('Nichts zum Wiederholen'); return; } const action = store.popRedo(); if (!action) return; try { await this.executeRedo(action); this.showSuccess('Wiederholt'); } catch (error) { console.error('Redo failed:', error); this.showError('Wiederholen fehlgeschlagen'); } } // ===================== // ACTION EXECUTION // ===================== async executeUndo(action) { const projectId = store.get('currentProjectId'); switch (action.type) { case 'DELETE_TASK': // Restore deleted task const restoredTask = await api.createTask(projectId, { ...action.task, id: undefined // Let server assign new ID }); store.addTask(restoredTask); break; case 'CREATE_TASK': // Delete created task await api.deleteTask(projectId, action.taskId); store.removeTask(action.taskId); break; case 'UPDATE_TASK': // Restore previous values await api.updateTask(projectId, action.taskId, action.previousData); store.updateTask(action.taskId, action.previousData); break; case 'MOVE_TASK': // Move back to original position await api.moveTask(projectId, action.taskId, action.fromColumnId, action.fromPosition); store.moveTask(action.taskId, action.fromColumnId, action.fromPosition); break; case 'DELETE_COLUMN': // Restore deleted column (without tasks - they're gone) const restoredColumn = await api.createColumn(projectId, action.column); store.addColumn(restoredColumn); break; case 'CREATE_COLUMN': // Delete created column await api.deleteColumn(projectId, action.columnId); store.removeColumn(action.columnId); break; case 'UPDATE_COLUMN': // Restore previous values await api.updateColumn(projectId, action.columnId, action.previousData); store.updateColumn(action.columnId, action.previousData); break; case 'BULK_DELETE': // Restore all deleted tasks for (const task of action.tasks) { const restored = await api.createTask(projectId, { ...task, id: undefined }); store.addTask(restored); } break; case 'BULK_MOVE': // Move all tasks back for (const item of action.items) { await api.moveTask(projectId, item.taskId, item.fromColumnId, item.fromPosition); store.moveTask(item.taskId, item.fromColumnId, item.fromPosition); } break; case 'BULK_UPDATE': // Restore all previous values for (const item of action.items) { await api.updateTask(projectId, item.taskId, item.previousData); store.updateTask(item.taskId, item.previousData); } break; default: console.warn('Unknown undo action type:', action.type); } } async executeRedo(action) { const projectId = store.get('currentProjectId'); switch (action.type) { case 'DELETE_TASK': // Re-delete the task await api.deleteTask(projectId, action.task.id); store.removeTask(action.task.id); break; case 'CREATE_TASK': // Re-create the task const recreatedTask = await api.createTask(projectId, action.taskData); store.addTask(recreatedTask); break; case 'UPDATE_TASK': // Re-apply the update await api.updateTask(projectId, action.taskId, action.newData); store.updateTask(action.taskId, action.newData); break; case 'MOVE_TASK': // Re-move to new position await api.moveTask(projectId, action.taskId, action.toColumnId, action.toPosition); store.moveTask(action.taskId, action.toColumnId, action.toPosition); break; case 'DELETE_COLUMN': // Re-delete the column await api.deleteColumn(projectId, action.column.id); store.removeColumn(action.column.id); break; case 'CREATE_COLUMN': // Re-create the column const recreatedColumn = await api.createColumn(projectId, action.columnData); store.addColumn(recreatedColumn); break; case 'UPDATE_COLUMN': // Re-apply the update await api.updateColumn(projectId, action.columnId, action.newData); store.updateColumn(action.columnId, action.newData); break; case 'BULK_DELETE': // Re-delete all tasks for (const task of action.tasks) { await api.deleteTask(projectId, task.id); store.removeTask(task.id); } break; case 'BULK_MOVE': // Re-move all tasks for (const item of action.items) { await api.moveTask(projectId, item.taskId, item.toColumnId, item.toPosition); store.moveTask(item.taskId, item.toColumnId, item.toPosition); } break; case 'BULK_UPDATE': // Re-apply all updates for (const item of action.items) { await api.updateTask(projectId, item.taskId, item.newData); store.updateTask(item.taskId, item.newData); } break; default: console.warn('Unknown redo action type:', action.type); } } // ===================== // ACTION RECORDING // ===================== recordTaskDelete(task) { store.pushUndo({ type: 'DELETE_TASK', task: { ...task } }); } recordTaskCreate(taskId, taskData) { store.pushUndo({ type: 'CREATE_TASK', taskId, taskData }); } recordTaskUpdate(taskId, previousData, newData) { store.pushUndo({ type: 'UPDATE_TASK', taskId, previousData, newData }); } recordTaskMove(taskId, fromColumnId, fromPosition, toColumnId, toPosition) { store.pushUndo({ type: 'MOVE_TASK', taskId, fromColumnId, fromPosition, toColumnId, toPosition }); } recordColumnDelete(column) { store.pushUndo({ type: 'DELETE_COLUMN', column: { ...column } }); } recordColumnCreate(columnId, columnData) { store.pushUndo({ type: 'CREATE_COLUMN', columnId, columnData }); } recordColumnUpdate(columnId, previousData, newData) { store.pushUndo({ type: 'UPDATE_COLUMN', columnId, previousData, newData }); } recordBulkDelete(tasks) { store.pushUndo({ type: 'BULK_DELETE', tasks: tasks.map(t => ({ ...t })) }); } recordBulkMove(items) { store.pushUndo({ type: 'BULK_MOVE', items: [...items] }); } recordBulkUpdate(items) { store.pushUndo({ type: 'BULK_UPDATE', items: [...items] }); } // ===================== // HELPERS // ===================== showSuccess(message) { window.dispatchEvent(new CustomEvent('toast:show', { detail: { message, type: 'success' } })); } showError(message) { window.dispatchEvent(new CustomEvent('toast:show', { detail: { message, type: 'error' } })); } showInfo(message) { window.dispatchEvent(new CustomEvent('toast:show', { detail: { message, type: 'info' } })); } } // Create and export singleton const undoManager = new UndoManager(); export default undoManager;