Initial commit
Dieser Commit ist enthalten in:
332
frontend/js/undo.js
Normale Datei
332
frontend/js/undo.js
Normale Datei
@ -0,0 +1,332 @@
|
||||
/**
|
||||
* 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;
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren