This commit is contained in:
2026-03-26 23:00:25 -07:00
parent 562acd809c
commit 37bb4ca685
8 changed files with 368 additions and 123 deletions

View File

@@ -47,6 +47,60 @@ import logging
logger = logging.getLogger(__name__)
def _normalize_detail_tags(tags: Any) -> List[str]:
if not tags:
return []
if isinstance(tags, str):
source = [part.strip() for part in tags.split(",")]
elif isinstance(tags, (list, tuple, set)):
source = [str(part or "").strip() for part in tags]
else:
source = [str(tags).strip()]
seen: set[str] = set()
normalized: List[str] = []
for tag in source:
text = str(tag or "").strip()
if not text:
continue
key = text.casefold()
if key in seen:
continue
seen.add(key)
normalized.append(text)
return normalized
def _partition_detail_tags(tags: Any) -> tuple[List[str], List[str]]:
normalized = _normalize_detail_tags(tags)
namespace_tags: List[str] = []
freeform_tags: List[str] = []
for tag in normalized:
namespace, sep, value = str(tag).partition(":")
if sep and namespace.strip() and value.strip():
namespace_tags.append(tag)
else:
freeform_tags.append(tag)
namespace_tags.sort(
key=lambda value: (
str(value).partition(":")[0].casefold(),
str(value).partition(":")[2].casefold(),
str(value).casefold(),
)
)
freeform_tags.sort(key=lambda value: str(value).casefold())
return namespace_tags, freeform_tags
def _chunk_detail_tags(tags: List[str], columns: int) -> List[List[str]]:
column_count = max(1, int(columns or 1))
rows: List[List[str]] = []
for index in range(0, len(tags), column_count):
rows.append(tags[index:index + column_count])
return rows
_RESULT_TABLE_ROW_STYLE_LOOP: List[tuple[str, str]] = [
("#ff0000", "#8f00ff"),
("#ffa500", "#800080"),
@@ -2213,7 +2267,6 @@ class ItemDetailView(Table):
from rich.table import Table as RichTable
from rich.panel import Panel
from rich.console import Group
from rich.columns import Columns
from rich.text import Text
# 1. Create Detail Grid (matching rich_display.py style)
@@ -2236,6 +2289,28 @@ class ItemDetailView(Table):
tag_text.append(raw, style="green")
return tag_text
def _build_tag_renderable(tags: Any) -> Optional[Any]:
namespace_tags, freeform_tags = _partition_detail_tags(tags)
if not namespace_tags and not freeform_tags:
return None
renderables: List[Any] = []
for tag in namespace_tags:
renderables.append(_render_tag_text(tag))
if freeform_tags:
freeform_grid = RichTable.grid(expand=True, padding=(0, 2))
for _ in range(3):
freeform_grid.add_column(ratio=1)
for row_values in _chunk_detail_tags(freeform_tags, 3):
cells = [_render_tag_text(tag) for tag in row_values]
while len(cells) < 3:
cells.append(Text(""))
freeform_grid.add_row(*cells)
renderables.append(freeform_grid)
return Group(*renderables)
# Canonical display order for metadata
order = ["Title", "Hash", "Store", "Path", "Ext", "Size", "Duration", "Url", "Relations"]
@@ -2280,13 +2355,11 @@ class ItemDetailView(Table):
# Tags Summary
tags = self.item_metadata.get("Tags") or self.item_metadata.get("tags") or self.item_metadata.get("tag")
if not self.exclude_tags and tags and isinstance(tags, (list, str)):
if isinstance(tags, str):
tags = [t.strip() for t in tags.split(",") if t.strip()]
tags_sorted = sorted(map(str, tags))
tag_cols = Columns([_render_tag_text(t) for t in tags_sorted], equal=True, expand=True)
details_table.add_row("", "") # Spacer
details_table.add_row("Tags:", tag_cols)
has_details = True
tag_renderable = _build_tag_renderable(tags)
if tag_renderable is not None:
details_table.add_row("", "")
details_table.add_row("Tags:", tag_renderable)
has_details = True
# 2. Get the standard table render (if there are rows or a specific title)
original_title = self.title