diff --git a/src/ui/timeline.py b/src/ui/timeline.py new file mode 100644 index 0000000..61c5836 --- /dev/null +++ b/src/ui/timeline.py @@ -0,0 +1,110 @@ +"""Timeline view for DevTrace events.""" + +from datetime import datetime +from typing import List, Optional, Dict, Any + +from rich.console import Console +from rich.table import Table +from rich.panel import Panel + +from ..storage.models import ( + FileEvent, + CommandEvent, + GitEvent, + TimelineEvent +) + + +class TimelineView: + """Displays development events in a timeline format.""" + + EVENT_ICONS = { + "created": "📄", + "modified": "✏️", + "deleted": "🗑️", + "moved": "📁", + "commit": "💾", + "branch": "🌿", + "merge": "🔀", + "checkout": "🔀", + "push": "📤", + "pull": "📥", + } + + def __init__(self, events: Optional[List] = None): + self.events = events or [] + self.console = Console() + + def _format_timestamp(self, timestamp: Optional[datetime]) -> str: + """Format timestamp for display.""" + if timestamp is None: + return "N/A" + + now = datetime.now() + delta = now - timestamp + + if delta.total_seconds() < 60: + return "Just now" + elif delta.total_seconds() < 3600: + mins = int(delta.total_seconds() / 60) + return f"{mins}m ago" + elif delta.total_seconds() < 86400: + hours = int(delta.total_seconds() / 3600) + return f"{hours}h ago" + else: + return timestamp.strftime("%Y-%m-%d %H:%M") + + def _get_event_color(self, event_type: str) -> str: + """Get color for event type.""" + colors = { + "file": "cyan", + "command": "green", + "git": "magenta", + } + return colors.get(event_type, "white") + + def display(self, limit: int = 50) -> None: + """Display the timeline.""" + if not self.events: + self.console.print("[bold yellow]No events to display[/]") + return + + table = Table(title="Timeline", show_header=True, header_style="bold magenta") + table.add_column("Time", width=12, style="cyan") + table.add_column("Icon", width=4, justify="center") + table.add_column("Type", width=10) + table.add_column("Details", overflow="fold") + + for event in self.events[:limit]: + if isinstance(event, FileEvent): + icon = self.EVENT_ICONS.get(event.event_type, "📄") + details = event.file_path + elif isinstance(event, CommandEvent): + icon = "⚡" + details = event.short_command + elif isinstance(event, GitEvent): + icon = self.EVENT_ICONS.get(event.operation, "🔧") + details = f"{event.operation} {event.branch or event.short_hash}" + else: + icon = "📌" + details = str(event) + + color = self._get_event_color(type(event).__name__.replace("Event", "").lower()) + time_str = self._format_timestamp(getattr(event, 'timestamp', None)) + + table.add_row( + f"[{color}]{time_str}[/]", + icon, + f"[{color}]{type(event).__name__.replace('Event', '')}[/]", + details + ) + + self.console.print(table) + + def get_event_counts(self) -> Dict[str, int]: + """Get counts of each event type.""" + counts = {} + for event in self.events: + event_type = type(event).__name__.replace("Event", "").lower() + counts[event_type] = counts.get(event_type, 0) + 1 + return counts