feat: implement Phase 5 - undo/redo system with command pattern
Added comprehensive undo/redo functionality: - Command pattern interface and base classes - HistoryManager with 50-operation stack - EditCommand for all edit operations (cut, delete, paste, trim) - Full keyboard shortcuts (Ctrl+Z undo, Ctrl+Y/Ctrl+Shift+Z redo) - HistoryControls UI component with visual feedback - Integrated history system with all edit operations - Toast notifications for undo/redo actions - History state tracking and display New files: - lib/history/command.ts - Command interface and BaseCommand - lib/history/history-manager.ts - HistoryManager class - lib/history/commands/edit-command.ts - EditCommand and factory functions - lib/hooks/useHistory.ts - React hook for history management - components/editor/HistoryControls.tsx - History UI component Modified files: - components/editor/AudioEditor.tsx - Integrated history system - components/editor/EditControls.tsx - Updated keyboard shortcuts display 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
155
lib/history/commands/edit-command.ts
Normal file
155
lib/history/commands/edit-command.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* Edit commands for audio buffer operations
|
||||
*/
|
||||
|
||||
import { BaseCommand } from '../command';
|
||||
import type { Selection } from '@/types/selection';
|
||||
import {
|
||||
extractBufferSegment,
|
||||
deleteBufferSegment,
|
||||
insertBufferSegment,
|
||||
trimBuffer,
|
||||
} from '@/lib/audio/buffer-utils';
|
||||
|
||||
export type EditCommandType = 'cut' | 'delete' | 'paste' | 'trim';
|
||||
|
||||
export interface EditCommandParams {
|
||||
type: EditCommandType;
|
||||
beforeBuffer: AudioBuffer;
|
||||
afterBuffer: AudioBuffer;
|
||||
selection?: Selection;
|
||||
clipboardData?: AudioBuffer;
|
||||
pastePosition?: number;
|
||||
onApply: (buffer: AudioBuffer) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Command for edit operations (cut, delete, paste, trim)
|
||||
*/
|
||||
export class EditCommand extends BaseCommand {
|
||||
private type: EditCommandType;
|
||||
private beforeBuffer: AudioBuffer;
|
||||
private afterBuffer: AudioBuffer;
|
||||
private selection?: Selection;
|
||||
private clipboardData?: AudioBuffer;
|
||||
private pastePosition?: number;
|
||||
private onApply: (buffer: AudioBuffer) => void;
|
||||
|
||||
constructor(params: EditCommandParams) {
|
||||
super();
|
||||
this.type = params.type;
|
||||
this.beforeBuffer = params.beforeBuffer;
|
||||
this.afterBuffer = params.afterBuffer;
|
||||
this.selection = params.selection;
|
||||
this.clipboardData = params.clipboardData;
|
||||
this.pastePosition = params.pastePosition;
|
||||
this.onApply = params.onApply;
|
||||
}
|
||||
|
||||
execute(): void {
|
||||
this.onApply(this.afterBuffer);
|
||||
}
|
||||
|
||||
undo(): void {
|
||||
this.onApply(this.beforeBuffer);
|
||||
}
|
||||
|
||||
getDescription(): string {
|
||||
switch (this.type) {
|
||||
case 'cut':
|
||||
return 'Cut';
|
||||
case 'delete':
|
||||
return 'Delete';
|
||||
case 'paste':
|
||||
return 'Paste';
|
||||
case 'trim':
|
||||
return 'Trim';
|
||||
default:
|
||||
return 'Edit';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of edit operation
|
||||
*/
|
||||
getType(): EditCommandType {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selection that was affected
|
||||
*/
|
||||
getSelection(): Selection | undefined {
|
||||
return this.selection;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory functions to create edit commands
|
||||
*/
|
||||
|
||||
export function createCutCommand(
|
||||
buffer: AudioBuffer,
|
||||
selection: Selection,
|
||||
onApply: (buffer: AudioBuffer) => void
|
||||
): EditCommand {
|
||||
const afterBuffer = deleteBufferSegment(buffer, selection.start, selection.end);
|
||||
|
||||
return new EditCommand({
|
||||
type: 'cut',
|
||||
beforeBuffer: buffer,
|
||||
afterBuffer,
|
||||
selection,
|
||||
onApply,
|
||||
});
|
||||
}
|
||||
|
||||
export function createDeleteCommand(
|
||||
buffer: AudioBuffer,
|
||||
selection: Selection,
|
||||
onApply: (buffer: AudioBuffer) => void
|
||||
): EditCommand {
|
||||
const afterBuffer = deleteBufferSegment(buffer, selection.start, selection.end);
|
||||
|
||||
return new EditCommand({
|
||||
type: 'delete',
|
||||
beforeBuffer: buffer,
|
||||
afterBuffer,
|
||||
selection,
|
||||
onApply,
|
||||
});
|
||||
}
|
||||
|
||||
export function createPasteCommand(
|
||||
buffer: AudioBuffer,
|
||||
clipboardData: AudioBuffer,
|
||||
pastePosition: number,
|
||||
onApply: (buffer: AudioBuffer) => void
|
||||
): EditCommand {
|
||||
const afterBuffer = insertBufferSegment(buffer, clipboardData, pastePosition);
|
||||
|
||||
return new EditCommand({
|
||||
type: 'paste',
|
||||
beforeBuffer: buffer,
|
||||
afterBuffer,
|
||||
clipboardData,
|
||||
pastePosition,
|
||||
onApply,
|
||||
});
|
||||
}
|
||||
|
||||
export function createTrimCommand(
|
||||
buffer: AudioBuffer,
|
||||
selection: Selection,
|
||||
onApply: (buffer: AudioBuffer) => void
|
||||
): EditCommand {
|
||||
const afterBuffer = trimBuffer(buffer, selection.start, selection.end);
|
||||
|
||||
return new EditCommand({
|
||||
type: 'trim',
|
||||
beforeBuffer: buffer,
|
||||
afterBuffer,
|
||||
selection,
|
||||
onApply,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user