Files
2026-09-24 22:03:58 +02:00

97 lines
3.4 KiB
Python

"""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),
]