333 Zeilen
8.2 KiB
JavaScript
333 Zeilen
8.2 KiB
JavaScript
/**
|
|
* 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;
|