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