"""Preview pane: shows the content of the currently selected file. SaraTag tags live in their own panel now (Tags.py), not here.""" from pathlib import Path from typing import Optional import flet as ft from Core import AppConfig, Palette from Thumbnails import GetThumbnail, IsThumbnailable TextExtensions = { ".txt", ".md", ".py", ".json", ".toml", ".yaml", ".yml", ".js", ".ts", ".css", ".html", ".htm", ".csv", ".log", ".sh", ".fish", ".cfg", ".ini", ".scad", ".xml", ".ipynb", } TextPreviewCharLimit = 4000 def HumanSize(NumBytes: int) -> str: Size = float(NumBytes) for Unit in ("B", "KB", "MB", "GB"): if Size < 1024 or Unit == "GB": return f"{Size:.0f} {Unit}" if Unit == "B" else f"{Size:.1f} {Unit}" Size /= 1024 return f"{Size:.1f} TB" class Preview(ft.Container): def __init__(self, Config: AppConfig, Side: str = "right"): self.Config = Config self.Body = ft.Column( expand=True, alignment=ft.MainAxisAlignment.CENTER, horizontal_alignment=ft.CrossAxisAlignment.CENTER, ) BorderSide = ft.BorderSide(1, Palette.Surface1) Border = ft.Border(right=BorderSide) if Side == "left" else ft.Border(left=BorderSide) self.Body.controls = self._BuildContent(None) super().__init__( content=self.Body, width=Config.PreviewWidth, padding=16, bgcolor=Palette.Mantle, border=Border, ) def ShowPath(self, EntryPath: Optional[Path]) -> None: self.Body.controls = self._BuildContent(EntryPath) self.update() def _BuildContent(self, EntryPath: Optional[Path]) -> list[ft.Control]: if EntryPath is None: return [ft.Text("Nothing selected.", color=Palette.Overlay0)] if not EntryPath.exists(): return [ft.Text("This no longer exists.", color=Palette.Overlay0)] if EntryPath.is_dir(): return [ ft.Icon(ft.Icons.FOLDER, size=48, color=Palette.Mauve), ft.Text(EntryPath.name, weight=ft.FontWeight.BOLD, text_align=ft.TextAlign.CENTER), ft.Text("Directory", color=Palette.Overlay0), ] if IsThumbnailable(EntryPath): ThumbnailPath = GetThumbnail(EntryPath, Size=(512, 512)) if ThumbnailPath is not None: return [ ft.Image(src=str(ThumbnailPath), fit=ft.BoxFit.CONTAIN, expand=True), ft.Text(EntryPath.name, text_align=ft.TextAlign.CENTER), ] if EntryPath.suffix.lower() in TextExtensions: try: Content = EntryPath.read_text(errors="replace")[:TextPreviewCharLimit] except OSError: Content = None if Content is not None: return [ ft.Text(EntryPath.name, weight=ft.FontWeight.BOLD), ft.Container( content=ft.Text(Content, selectable=True, size=12), expand=True, alignment=ft.Alignment.TOP_LEFT, ), ] Stat = EntryPath.stat() return [ ft.Icon(ft.Icons.INSERT_DRIVE_FILE_OUTLINED, size=48, color=Palette.Overlay0), ft.Text(EntryPath.name, weight=ft.FontWeight.BOLD, text_align=ft.TextAlign.CENTER), ft.Text(HumanSize(Stat.st_size), color=Palette.Overlay0), ]