64 lines
2.1 KiB
Python
64 lines
2.1 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import PurePosixPath
|
|
from textual.app import ComposeResult
|
|
from textual.screen import ModalScreen
|
|
from textual.widgets import Tree, Static, Button
|
|
from textual.containers import Vertical, Horizontal
|
|
|
|
|
|
class SnapshotBrowser(ModalScreen[None]):
|
|
"""Modal file browser for remote snapshot contents."""
|
|
|
|
BINDINGS = [("escape", "close", "Close")]
|
|
|
|
def __init__(self, title: str, file_list: list[str]):
|
|
super().__init__()
|
|
self._title = title
|
|
self._file_list = file_list
|
|
|
|
def compose(self) -> ComposeResult:
|
|
with Vertical(id="snapshot-browser"):
|
|
yield Static(self._title, id="sb-title")
|
|
yield Tree("snapshot", id="sb-tree")
|
|
with Horizontal(id="sb-buttons"):
|
|
yield Button("Close", variant="primary", id="sb-close")
|
|
|
|
def on_mount(self) -> None:
|
|
tree = self.query_one("#sb-tree", Tree)
|
|
tree.root.expand()
|
|
self._build_tree(tree)
|
|
|
|
def _build_tree(self, tree: Tree) -> None:
|
|
# Build a nested dict from file paths
|
|
root: dict = {}
|
|
for filepath in self._file_list:
|
|
filepath = filepath.strip()
|
|
if not filepath:
|
|
continue
|
|
parts = PurePosixPath(filepath).parts
|
|
node = root
|
|
for part in parts:
|
|
if part not in node:
|
|
node[part] = {}
|
|
node = node[part]
|
|
|
|
# Add to tree widget recursively
|
|
self._add_nodes(tree.root, root)
|
|
|
|
def _add_nodes(self, parent, structure: dict) -> None:
|
|
# Sort: directories first, then files
|
|
dirs = sorted(k for k, v in structure.items() if v)
|
|
files = sorted(k for k, v in structure.items() if not v)
|
|
for name in dirs:
|
|
branch = parent.add(f"[bold]{name}/[/bold]")
|
|
self._add_nodes(branch, structure[name])
|
|
for name in files:
|
|
parent.add_leaf(name)
|
|
|
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
self.dismiss(None)
|
|
|
|
def action_close(self) -> None:
|
|
self.dismiss(None)
|