Update tab_gallery_sorter.py

This commit is contained in:
2026-01-18 22:33:43 +01:00
parent 0d3ff69f8d
commit 293cfd14f8

View File

@@ -3,21 +3,57 @@ import os
import math
from engine import SorterEngine
# --- FRAGMENT: This section updates independently ---
# --- FRAGMENT 1: SIDEBAR (Category Manager) ---
@st.fragment
def render_gallery_grid(current_batch, quality, selected_cat, grid_cols):
def render_sidebar_fragment():
"""
This function isolates the grid updates.
Clicking buttons here will NOT reload the whole page.
Isolates the category selection.
Changing the active tag here updates Session State instantly
but DOES NOT reload the image grid.
"""
# CRITICAL: Re-fetch staged status inside the fragment so the UI updates instantly
with st.sidebar:
st.divider()
st.subheader("🏷️ Category Manager")
# 1. ADD CATEGORY
c_add1, c_add2 = st.columns([3, 1])
new_cat = c_add1.text_input("New Category", label_visibility="collapsed", placeholder="New Category...", key="t5_new_cat_input")
if c_add2.button("", help="Add Category"):
if new_cat:
SorterEngine.add_category(new_cat)
st.rerun() # Refresh only this sidebar fragment
# 2. SELECT CATEGORY
cats = SorterEngine.get_categories()
if not cats:
st.warning("No categories.")
return None
# We use session state to ensure the grid can see the selection
# Default to first category if not set
if "t5_active_cat" not in st.session_state:
st.session_state.t5_active_cat = cats[0]
# The Radio Button
# on_change is not needed because the key automatically syncs with session_state
current = st.radio("Active Tag", cats, key="t5_active_cat")
return current
# --- FRAGMENT 2: GALLERY GRID ---
@st.fragment
def render_gallery_grid(current_batch, quality, grid_cols):
"""
Isolates the image grid.
Reads the 'Active Tag' directly from Session State when a button is clicked.
"""
# 1. Fetch latest data (DB + Session State)
staged = SorterEngine.get_staged_data()
selected_cat = st.session_state.get("t5_active_cat", "Default") # Read latest tag
# Dynamic Grid
cols = st.columns(grid_cols)
for idx, img_path in enumerate(current_batch):
# Unique key ensures buttons don't conflict
unique_key = f"frag_{os.path.basename(img_path)}"
with cols[idx % grid_cols]:
@@ -28,10 +64,10 @@ def render_gallery_grid(current_batch, quality, selected_cat, grid_cols):
c_head1, c_head2 = st.columns([5, 1])
c_head1.caption(os.path.basename(img_path)[:15])
# Delete Button
# Delete X
if c_head2.button("", key=f"del_{unique_key}"):
SorterEngine.delete_to_trash(img_path)
st.rerun() # Reruns ONLY this fragment
st.rerun()
# Status Banner
if is_staged:
@@ -42,31 +78,32 @@ def render_gallery_grid(current_batch, quality, selected_cat, grid_cols):
if img_data:
st.image(img_data, use_container_width=True)
# Action Buttons
# Buttons
if not is_staged:
# Note: Label is static "Tag", but logic uses 'selected_cat'
if st.button("Tag", key=f"tag_{unique_key}", use_container_width=True):
ext = os.path.splitext(img_path)[1]
# Calculate suffix
count = len([v for v in staged.values() if v['cat'] == selected_cat]) + 1
new_name = f"{selected_cat}_{count:03d}{ext}"
SorterEngine.stage_image(img_path, selected_cat, new_name)
st.rerun() # Reruns ONLY this fragment
st.rerun()
else:
if st.button("Untag", key=f"untag_{unique_key}", use_container_width=True):
SorterEngine.clear_staged_item(img_path)
st.rerun() # Reruns ONLY this fragment
st.rerun()
# --- MAIN RENDER ---
# --- MAIN PAGE RENDERER ---
def render(quality, profile_name):
st.subheader("🖼️ Gallery Staging Sorter")
# 1. Setup Session & Profile
# 1. Init Session State
if 't5_page' not in st.session_state: st.session_state.t5_page = 0
# 2. Profile & Paths
profiles = SorterEngine.load_profiles()
p_data = profiles.get(profile_name, {})
# 2. Paths
c1, c2 = st.columns(2)
path_s = c1.text_input("Source Folder", value=p_data.get("tab5_source", "/storage"), key="t5_s")
path_o = c2.text_input("Output Folder", value=p_data.get("tab5_out", "/storage"), key="t5_o")
@@ -76,36 +113,24 @@ def render(quality, profile_name):
SorterEngine.save_tab_paths(profile_name, t5_s=path_s, t5_o=path_o)
st.rerun()
# 3. Sidebar (Categories)
with st.sidebar:
st.divider()
st.subheader("🏷️ Category Manager")
new_cat = st.text_input("New Category", key="t5_new_cat")
if st.button(" Add", key="t5_add_btn"):
if new_cat:
SorterEngine.add_category(new_cat)
st.rerun()
cats = SorterEngine.get_categories()
if not cats:
st.warning("No categories.")
return
selected_cat = st.radio("Active Tag", cats, key="t5_cat_radio")
if not os.path.exists(path_s): return
# 4. View Settings & Pagination
with st.expander("👀 View Settings", expanded=False):
# 3. CALL SIDEBAR FRAGMENT
# This draws the sidebar controls. Interactions here will NOT reload the main page.
render_sidebar_fragment()
# 4. View Settings (Main Body)
with st.expander("👀 View Settings"):
c_v1, c_v2 = st.columns(2)
page_size = c_v1.slider("Images per Page", 12, 100, 24, 4)
grid_cols = c_v2.slider("Grid Columns", 2, 8, 4)
# 5. Pagination Logic
all_images = SorterEngine.get_images(path_s, recursive=True)
if not all_images:
st.info("No images found.")
return
# Pagination Logic
total_items = len(all_images)
total_pages = math.ceil(total_items / page_size)
if st.session_state.t5_page >= total_pages: st.session_state.t5_page = max(0, total_pages - 1)
@@ -128,15 +153,15 @@ def render(quality, profile_name):
nav_controls("top")
st.divider()
# --- CALL THE FRAGMENT ---
# This is the only part that will refresh when you tag/delete!
render_gallery_grid(current_batch, quality, selected_cat, grid_cols)
# 6. CALL GALLERY FRAGMENT
# Interactions here (Tagging) will only reload this grid.
render_gallery_grid(current_batch, quality, grid_cols)
st.divider()
nav_controls("bottom")
st.divider()
# 5. Apply Batch
# 7. Batch Apply
st.write(f"### 🚀 Batch Actions (Page {st.session_state.t5_page + 1})")
c_act1, c_act2 = st.columns([3, 1])
cleanup = c_act1.radio("Untagged Action:", ["Keep", "Move to Unused", "Delete"], horizontal=True)