This commit is contained in:
110
src/ui/timeline.py
Normal file
110
src/ui/timeline.py
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user