Files
sorting-sorted/tab_gallery_sorter.py
2026-01-18 22:28:53 +01:00

148 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import streamlit as st
import os
import math
from engine import SorterEngine
# --- FRAGMENT: This section updates independently ---
@st.fragment
def render_gallery_grid(current_batch, quality, selected_cat, grid_cols):
"""
This function isolates the grid updates.
Clicking buttons here will NOT reload the whole page.
"""
# CRITICAL: Re-fetch staged status inside the fragment so the UI updates instantly
staged = SorterEngine.get_staged_data()
# 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]:
is_staged = img_path in staged
with st.container(border=True):
# Header
c_head1, c_head2 = st.columns([5, 1])
c_head1.caption(os.path.basename(img_path)[:15])
# Delete Button
if c_head2.button("", key=f"del_{unique_key}"):
SorterEngine.delete_to_trash(img_path)
st.rerun() # Reruns ONLY this fragment
# Status Banner
if is_staged:
st.success(f"🏷️ {staged[img_path]['cat']}")
# Image
img_data = SorterEngine.compress_for_web(img_path, quality)
if img_data:
st.image(img_data, use_container_width=True)
# Action Buttons
if not is_staged:
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
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
# --- MAIN RENDER ---
def render(quality, profile_name):
st.subheader("🖼️ Gallery Staging Sorter")
# 1. Setup Session & Profile
if 't5_page' not in st.session_state: st.session_state.t5_page = 0
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")
if path_s != p_data.get("tab5_source") or path_o != p_data.get("tab5_out"):
if st.button("💾 Save Settings"):
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):
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)
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)
start_idx = st.session_state.t5_page * page_size
end_idx = start_idx + page_size
current_batch = all_images[start_idx:end_idx]
# Navigation Helper
def nav_controls(key):
c1, c2, c3 = st.columns([1, 2, 1])
if c1.button("⬅️ Prev", disabled=(st.session_state.t5_page==0), key=f"p_{key}"):
st.session_state.t5_page -= 1
st.rerun()
c2.markdown(f"<div style='text-align:center'><b>Page {st.session_state.t5_page+1} / {total_pages}</b></div>", unsafe_allow_html=True)
if c3.button("Next ➡️", disabled=(st.session_state.t5_page>=total_pages-1), key=f"n_{key}"):
st.session_state.t5_page += 1
st.rerun()
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)
st.divider()
nav_controls("bottom")
st.divider()
# 5. Apply Batch
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)
if c_act2.button("APPLY PAGE", type="primary", use_container_width=True):
with st.spinner("Processing..."):
SorterEngine.commit_batch(current_batch, path_o, cleanup)
st.success("Done!")
st.rerun()