Gallery-VTON / app.py
LezRProxy's picture
version 22 stable version
085c879
import mysql.connector, os, datetime, base64, time, requests, gradio as gr, pandas as pd
from io import BytesIO
from PIL import Image
from dotenv import load_dotenv
from typing import List, Tuple, Any
from prxm_uploader import PrxmGcsUploaderClient
from settings_config import settings
from mysql.connector import errorcode
# Load env variables
load_dotenv()
class GarmentDataManager:
def __init__(self):
self.user = os.getenv("user")
self.password = os.getenv("password")
self.host = os.getenv("host")
self.database = os.getenv("database")
self.df = pd.DataFrame()
self.error_message = None
self.cache_version = int(time.time())
self.refresh_data()
def get_connection(self):
"""Creates and returns a MySQL connection."""
try:
return mysql.connector.connect(
user = self.user,
password = self.password,
host = self.host,
database = self.database
)
except mysql.connector.Error as err:
print(f"❌ Connection Failed: {err}")
raise err
def refresh_data(self) -> str:
"""Fetches data from MySQL and updates the internal DataFrame."""
try:
cnx = self.get_connection()
cursor = cnx.cursor()
query = """
SELECT id, sku, clothes_type, collection, season, brand, description,
url_clothes_img, created_at, collection_id, clothing_type_id, validated
FROM clothes
"""
cursor.execute(query)
data = cursor.fetchall()
columns = [i[0] for i in cursor.description]
cursor.close()
cnx.close()
# Create DataFrame
temp_df = pd.DataFrame(data, columns = columns)
# Normalize Columns
cols_needed = ["id", "sku", "clothes_type", "collection", "season", "brand", "description", "url_clothes_img", "created_at", "collection_id", "clothing_type_id", "validated"]
for col in cols_needed:
if col not in temp_df.columns:
temp_df[col] = "" # Ensure all columns exist
# Type Conversions
if "id" in temp_df.columns:
temp_df["id"] = temp_df["id"].astype("Int64")
self.df = temp_df
self.error_message = None
self.cache_version = int(time.time()) # Update cache version on refresh
return "✅ Datos actualizados correctamente de MySQL."
except Exception as e:
self.error_message = f"❌ Error al conectar con la Base de Datos: {str(e)}"
print(self.error_message)
# Initialize empty DF to prevent crashes
if self.df.empty:
self.df = pd.DataFrame(columns = ["id", "sku", "clothes_type", "collection", "season", "brand", "description", "url_clothes_img", "created_at", "validated"])
return self.error_message
def get_dropdown_options(self):
"""Returns sorted unique values for filters."""
if self.df.empty:
return [], [], []
types = sorted(self.df["clothes_type"].dropna().astype(str).unique().tolist())
collections = sorted(self.df["collection"].dropna().astype(str).unique().tolist())
brands = sorted(self.df["brand"].dropna().astype(str).unique().tolist())
return types, collections, brands
def get_gallery_items(self, filter_col = None, order_type = "A -> Z") -> Tuple[List[Tuple[str, str]], pd.DataFrame]:
"""Filters and returns gallery items + the new view DataFrame for state."""
if self.df.empty:
return [], pd.DataFrame()
df_temp = self.df.copy()
if filter_col == 'Prendas invalidadas':
# Logic form original: df_temp['validated'].astype(str).isin(['0', '0.0'])
df_temp = df_temp[df_temp['validated'].astype(str).isin(['0', '0.0'])]
mapping = {
'Marca (Default)': 'brand',
'Tipo de ropa': 'clothes_type',
'Colección': 'collection',
'Temporada': 'season',
'Fecha de creación': 'created_at',
'Prendas invalidadas': 'brand'
}
col_sort = mapping.get(filter_col, 'brand')
ascending = (order_type == "A -> Z")
if col_sort == 'created_at':
df_temp[col_sort] = pd.to_datetime(df_temp[col_sort], errors = 'coerce')
view_df = df_temp.sort_values(by = col_sort, ascending = ascending, na_position = 'last').reset_index(drop = True)
# Use persistent cache version to prevent reload on every interaction
items = [(f"{row['url_clothes_img']}?v={self.cache_version}&ignoreCache=1", f"{row['brand']}, {row['clothes_type']}, {row['season']}")
for _, row in view_df.iterrows()]
return items, view_df
def get_garment_details(self, idx, view_df):
"""Returns details for a specific garment index (from the provided view_df)."""
if view_df is None or view_df.empty or idx >= len(view_df):
return None
row = view_df.iloc[idx]
is_validated = (str(row.get('validated')) == "1")
return {
"validated": is_validated,
"sku": row.get('sku', ''),
"created_at": row.get('created_at', ''),
"clothes_type": row.get('clothes_type', ''),
"brand": row.get('brand', ''),
"season": row.get('season', ''),
"collection": row.get('collection', ''),
"description": row.get('description', ''),
"idx": idx
}
def generate_sku(self, brand, clothes_type, season, year_code):
"""Generates a new SKU based on business logic."""
# Paso 1: Marca, brand[0] V = VC o C = CC
brand = str(brand).upper().strip() or "XX"
vocales = "AEIOU"
lista_v = [c for c in brand if c in vocales]
lista_c = [c for c in brand if c not in vocales and c.isalpha()]
prefix = "XX"
if brand and brand[0] in vocales and lista_v and lista_c:
prefix = lista_v[0] + lista_c[0]
elif len(lista_c) >= 2:
prefix = lista_c[0] + lista_c[1]
# Paso 2: Tipo de ropa
type_code = "00"
if not self.df.empty:
existing_type = self.df[self.df["clothes_type"].astype(str).str.upper() == clothes_type.upper()]
if not existing_type.empty:
first_sku = str(existing_type.iloc[0]["sku"])
if len(first_sku) >= 4:
type_code = first_sku[2:4]
else:
try:
codigos = self.df["sku"].astype(str).str[2:4].dropna()
codigos = codigos[codigos.str.isdigit()].astype(int)
if not codigos.empty:
type_code = str(codigos.max() + 1).zfill(2)
except:
pass
# Paso 3: Temporada
s_map = {"PRIMAVERA": "1", "VERANO": "2", "OTOÑO": "3", "INVIERNO": "4"}
season_code = s_map.get(season.upper(), "0")
# Paso 4: Año
chain = f"{prefix}{type_code}{season_code}{year_code}"
# Paso 5: Si existe prenda similar + 1
correlative = "000"
if not self.df.empty:
matches = self.df[self.df["sku"].astype(str).str.startswith(chain)]
if not matches.empty:
nums = matches["sku"].astype(str).apply(lambda x: int(x[-3:]) if x[-3:].isdigit() else 0)
if not nums.empty:
correlative = f"{nums.max() + 1:03d}"
return chain + correlative
def generate_img_url_and_load(self, file, sku):
if isinstance(file, str):
if not file.lower().endswith('.png'):
# gr.Info("⚠️ Error: El archivo debe ser una imagen png.")
return False, "⚠️ Error: El archivo debe ser una imagen png.", None
try:
with open(file, "rb") as f:
file_bytes = f.read()
except Exception as e:
# gr.Info("⚠️ Error: No se pudo leer el archivo.")
return False, f"⚠️ Error leyendo archivo: {str(e)}", None
else:
if isinstance(file, bytes):
file_bytes = file
elif hasattr(file, "read"):
file_bytes = file.read()
else:
# gr.Info("⚠️ Error: Formato de archivo no válido.")
return False, "⚠️ Error: Formato de archivo no válido.", None
try:
bucket = settings.gcs.bucketName
subfolder = settings.gcs.subfolderName
img_base64 = base64.b64encode(file_bytes).decode('utf-8')
formatted_image = f"data:image/png;base64,{img_base64}"
img_url = PrxmGcsUploaderClient.prxm_upload_using_asset_id(bucket, subfolder, sku, formatted_image)
# gr.Info("✅ Img url generado correctamente.")
return True, None , img_url
except Exception as e:
return False, f"❌ Error al intentar actualizar la imagen: {str(e)}", None
def update_garment(self, idx, new_data: dict, view_df):
"""Updates garment in MySQL and refreshes local Master DF."""
conn = None
try:
if view_df is None or view_df.empty:
return False, "⚠️ Error: No hay datos en la vista actual."
sku_target = view_df.iloc[int(idx)]['sku']
brand = self.df.loc[self.df['sku'] == sku_target, 'brand'].values[0]
duplicates = self.df[
(self.df['clothes_type'] == new_data['clothes_type']) &
(self.df['collection'] == new_data['collection']) &
(self.df['season'] == new_data['season']) &
(self.df['brand'] == brand) &
(self.df['description'] == new_data['description'])
]
if not duplicates.empty and not new_data['url_clothes_img']:
existing_sku = duplicates.iloc[0]['sku']
return False, f"⚠️ Error: Ya existe una prenda con estos datos exactos. (SKU: {existing_sku})"
sku = self.df.loc[self.df['sku'] == sku_target, 'sku'].values[0]
new_data['sku'] = sku
current_clothes_type = self.df.loc[self.df['sku'] == sku_target, 'clothes_type'].values[0]
current_season = self.df.loc[self.df['sku'] == sku_target, 'season'].values[0]
if (new_data.get("clothes_type").upper() != current_clothes_type) or (new_data.get("season").upper() != current_season): #Actualizar SKU & URL
actual_brand = self.df.loc[self.df['sku'] == sku_target, 'brand'].values[0]
actual_time = self.df.loc[self.df['sku'] == sku_target, 'created_at'].values[0]
actual_year_code = str(actual_time)[2:4]
new_clothes_type = new_data.get("clothes_type").upper()
new_season = new_data.get("season").upper()
new_sku = self.generate_sku(actual_brand, new_clothes_type, new_season, actual_year_code)
new_data['sku'] = new_sku
if new_data.get("url_clothes_img"): #Cambiar la imagen
# gr.Info("Actualizando imagen & sku & datos...")
img = new_data["url_clothes_img"]
success_img, err_msg, new_url = self.generate_img_url_and_load(img, new_sku)
if not success_img:
return False, err_msg
new_data['url_clothes_img'] = new_url
else: #Mantener imagen pero dif sku
# gr.Info("Actualizando sku & datos...")
current_url = self.df.loc[self.df['sku'] == sku_target, 'url_clothes_img'].values[0]
respuesta = requests.get(current_url)
respuesta.raise_for_status()
img_bytes = respuesta.content
success_img, err_msg, new_url = self.generate_img_url_and_load(img_bytes, new_sku)
if not success_img:
return False, err_msg
new_data['url_clothes_img'] = new_url
else: #No se modifica SKU por lo que no se modifica la URL
if new_data.get("url_clothes_img"): #Cambia solo la imagen
# gr.Info("Actualizando imagen || datos...")
img = new_data["url_clothes_img"]
success_img, err_msg, new_url = self.generate_img_url_and_load(img, sku)
if not success_img:
return False, err_msg
new_data["url_clothes_img"] = new_url
else: #Mantener imagen
# gr.Info("Actualizando solo datos...")
current_url = self.df.loc[self.df['sku'] == sku_target, 'url_clothes_img'].values[0]
new_data["url_clothes_img"] = current_url
sql = """
UPDATE clothes
SET sku = %s,
clothes_type = %s,
collection = %s,
season = %s,
description = %s,
url_clothes_img = %s,
validated = %s
WHERE sku = %s
"""
val = (
new_data['sku'],
new_data['clothes_type'].upper(),
new_data['collection'].upper(),
new_data['season'].upper(),
new_data['description'],
new_data['url_clothes_img'],
0,
sku_target
)
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute(sql, val)
conn.commit()
cursor.close()
conn.close()
master_idx = self.df[self.df['sku'] == sku_target].index[0]
self.df.at[master_idx, 'sku'] = new_data['sku']
self.df.at[master_idx, 'clothes_type'] = new_data['clothes_type'].upper()
self.df.at[master_idx, 'collection'] = new_data['collection'].upper()
self.df.at[master_idx, 'season'] = new_data['season'].upper()
self.df.at[master_idx, 'description'] = new_data['description']
self.df.at[master_idx, 'url_clothes_img'] = new_data['url_clothes_img']
self.df.at[master_idx, 'validated'] = 0
self.df.at[master_idx, 'url_clothes_img'] = new_data['url_clothes_img']
self.df.at[master_idx, 'validated'] = 0
self.cache_version = int(time.time())
return True, "✅ Cambios guardados correctamente."
except Exception as e:
gr.Info("⚠️ Entre a la excepción de update garment")
if conn: conn.rollback()
return False, f"❌ Error al actualizar: {str(e)}"
def create_garment(self, img, new_data: dict):
"""Inserts new garment into MySQL."""
conn = None
try:
clothes_type = str(new_data['clothes_type']).upper()
collection = str(new_data['collection']).upper()
season = str(new_data['season']).upper()
brand = str(new_data['brand']).upper()
description = new_data['description']
if not self.df.empty:
duplicates = self.df[
(self.df['clothes_type'] == clothes_type) &
(self.df['collection'] == collection) &
(self.df['season'] == season) &
(self.df['brand'] == brand) &
(self.df['description'] == description)
]
if not duplicates.empty:
existing_sku = duplicates.iloc[0]['sku']
return False, f"⚠️ Error: Ya existe una prenda con estos datos exactos. (SKU: {existing_sku})", None
created_time = datetime.datetime.now()
year_code = str(created_time.year)[-2:]
new_sku = self.generate_sku(brand, clothes_type, season, year_code)
created_time_str = created_time.strftime('%Y-%m-%d %H:%M:%S')
success_img, err_msg, img_url = self.generate_img_url_and_load(img, new_sku)
if not success_img:
return False, err_msg, None
sql = """
INSERT INTO clothes (sku, clothes_type, collection, season, brand, description, url_clothes_img, created_at, validated)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
val = (new_sku, clothes_type, collection, season, brand, description, img_url, created_time_str, 0)
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute(sql, val)
conn.commit()
new_row = {
"sku": new_sku,
"clothes_type": clothes_type,
"collection": collection,
"season": season,
"brand": brand,
"description": description,
"url_clothes_img": img_url,
"created_at": created_time_str,
"validated": 0
}
self.df = pd.concat([self.df, pd.DataFrame([new_row])], ignore_index = True).fillna("")
self.cache_version = int(time.time())
return True, f"✅ Nueva prenda guardada (SKU: {new_sku})", new_sku
except Exception as e:
if conn: conn.rollback()
return False, f"❌ Error al crear prenda: {str(e)}", None
def invalidate_garment(self, idx, view_df):
"""Sets validated = 0 in DB."""
conn = None
try:
sku_target = view_df.iloc[int(idx)]['sku']
sql = "UPDATE clothes SET validated = 0 WHERE sku = %s"
val = (sku_target,)
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute(sql, val)
conn.commit()
cursor.close()
conn.close()
master_idx = self.df[self.df['sku'] == sku_target].index[0]
self.df.at[master_idx, 'validated'] = 0
self.cache_version = int(time.time()) # Update cache version
return True, "✅ Prenda invalidada correctamente."
except Exception as e:
return False, f"❌ Error al invalidar: {str(e)}"
dm = GarmentDataManager()
custom_css = """
.gradio-container h3 {
font-weight: 900 !important;
font-style: italic !important;
font-size: 2em !important;
color: #eece25 !important; text-decoration: underline; letter-spacing: 2px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.gradio-container label span, .gradio-container .block-label, .gradio-container span[data-testid="block-info"] {
font-weight: bold !important; font-style: italic !important; font-size: 1.1em !important;
color: #ffffff !important; margin-bottom: 4px !important; display: block !important;
}
.gradio-container input, .gradio-container .single-select, .gradio-container .select-wrap span, .gradio-container .secondary-label {
color: #ffffff !important; font-weight: 600 !important;
}
.gradio-container .tab-nav button { color: #000000 !important; font-weight: bold !important; opacity: 1 !important; }
.gradio-container .tab-nav button.selected { color: #eece25 !important; border-bottom: 2px solid #eece25 !important; }
.check-centrado { display: flex !important; justify-content: center !important; align-items: center !important; border: none !important; }
.check-centrado label { justify-content: center !important; width: auto !important; gap: 10px !important; }
#btn_save { background-color: #12a822 !important; color: white !important; }
#btn_edit { background-color: #7000c8 !important; color: white !important; }
#btn_new { background-color: #0067fd !important; color: white !important; }
#btn_cancel { background-color: #eba428 !important; color: white !important; }
"""
# --- INTERFACE FUNCTIONS ---
def refresh_all_data():
"""Manual refresh button handler."""
msg = dm.refresh_data()
types, colls, brands = dm.get_dropdown_options()
gallery_items, new_view_df = dm.get_gallery_items()
if "Error" in msg:
gr.Warning(msg)
else:
gr.Info(msg)
return (
gr.update(choices = types), # New Type
gr.update(choices = colls), # New Collection
gr.update(choices = brands), # New Brand
gallery_items, # Gallery
gr.update(visible = ("Error" in msg), value = f"### {msg}"), # Error Banner
new_view_df # Update State
)
def ui_load_gallery(filter_col, order_type):
items, new_view_df = dm.get_gallery_items(filter_col, order_type)
new_filters = {"col": filter_col, "order": order_type}
return items, new_view_df, new_filters
def ui_get_details(evt: gr.SelectData, is_editing, view_df):
if is_editing:
gr.Warning("⚠️ Debes Guardar o Cancelar la edición actual antes de seleccionar otra prenda.")
return [gr.update()] * 14
idx = evt.index
data = dm.get_garment_details(idx, view_df)
if not data:
return [gr.update()] * 14
return (
data['validated'], # out_val
data['sku'], # out_sku
data['created_at'], # out_date
data['clothes_type'], # out_type
data['brand'], # out_brand
data['season'], # out_season
data['collection'], # out_coll
data['description'], # out_desc
None, # edit_img (empty)
data['clothes_type'], # edit_type
data['season'], # edit_season
data['collection'], # edit_coll
data['description'], # edit_desc
data['idx'] # selected_idx_holder
)
def ui_save_edit(img, clothes_type, season, collection, description, idx, view_df, current_filters):
if idx == -1: return gr.update(), gr.update() # Return gallery update, state update
success, msg = dm.update_garment(idx, {
"clothes_type": clothes_type,
"collection": collection,
"season": season,
"description": description,
"url_clothes_img": img
}, view_df)
if success:
gr.Info(msg)
else:
gr.Error(msg)
# Refresh gallery restoring filters
f_col = current_filters.get("col", "Marca (Default)")
f_ord = current_filters.get("order", "A -> Z")
items, new_view_df = dm.get_gallery_items(f_col, f_ord)
return items, new_view_df
def ui_save_new(img, type, coll, season, brand, desc, current_filters):
if not all([img, type, coll, season, brand]):
gr.Warning("❌ Error: Faltan campos obligatorios.")
f_col = current_filters.get("col", "Marca (Default)")
f_ord = current_filters.get("order", "A -> Z")
items, v_df = dm.get_gallery_items(f_col, f_ord)
return items, None, v_df
success, msg, new_sku = dm.create_garment(img, {
"clothes_type": type,
"collection": coll,
"season": season,
"brand": brand,
"description": desc
})
if success:
gr.Info(msg)
else:
gr.Error(msg)
f_col = current_filters.get("col", "Marca (Default)")
f_ord = current_filters.get("order", "A -> Z")
items, new_view_df = dm.get_gallery_items(f_col, f_ord)
return items, new_sku, new_view_df
def ui_delete_confirm(idx, view_df, current_filters):
if idx == -1: return gr.update(), gr.update()
success, msg = dm.invalidate_garment(idx, view_df)
if success: gr.Info(msg)
else: gr.Error(msg)
f_col = current_filters.get("col", "Marca (Default)")
f_ord = current_filters.get("order", "A -> Z")
items, new_view_df = dm.get_gallery_items(f_col, f_ord)
return items, new_view_df
# --- BUILD BLOCK ---
with gr.Blocks(title = "Garment DB", css = custom_css) as demo:
# State
is_editing = gr.State(value = False)
selected_idx_holder = gr.State(value = -1)
new_sku_holder = gr.State()
# State for multi-user support
stored_view_df = gr.State()
current_filters = gr.State(value = {"col": "Marca (Default)", "order": "A -> Z"})
error_banner = gr.Markdown(visible = False)
if dm.error_message:
error_banner.value = f"### {dm.error_message}"
error_banner.visible = True
with gr.Row():
with gr.Column(scale = 2):
gallery = gr.Gallery(columns = 3, height = 600, interactive = False)
btn_refresh = gr.Button("🔄 Refrescar Datos", variant = "secondary", size = "sm")
with gr.Column(scale = 1):
with gr.Tabs() as tabs_interface:
# TAB 0: DETALLES
with gr.Tab("Detalles", id = 0):
with gr.Group():
gr.Markdown("<h3 style = 'text-align: center;'>🔎 Filtros</h3>")
with gr.Row():
filter_type = gr.Dropdown(label = "Buscar por:", choices = ['Marca (Default)', 'Tipo de ropa', 'Colección','Temporada', 'Fecha de creación', 'Prendas invalidadas'], value = 'Marca (Default)', allow_custom_value = False)
filter_order = gr.Dropdown(label = "Organizar por:", choices = ['A -> Z', 'Z -> A'], value = 'A -> Z', allow_custom_value = False)
with gr.Group():
gr.Markdown("<h3 style='text-align: center;'>📄 Detalles</h3>")
out_val = gr.Checkbox(label = "Prenda Validada", interactive = False, elem_classes = "check-centrado")
with gr.Row():
out_sku = gr.Textbox(label = "SKU:", interactive = False, text_align = "center")
out_date = gr.Textbox(label = "Fecha Creación:", interactive = False, text_align = "center")
with gr.Row():
out_type = gr.Textbox(label = "Tipo:", interactive = False, text_align = "center")
out_brand = gr.Textbox(label = "Marca:", interactive = False, text_align = "center")
with gr.Row():
out_season = gr.Textbox(label = "Temporada:", interactive = False, text_align = "center")
out_coll = gr.Textbox(label = "Colección:", interactive = False, text_align = "center")
out_desc = gr.Textbox(label = "Descripción:", interactive = False, lines = 2, max_lines = 10, autoscroll = False)
with gr.Row():
btn_goto_edit = gr.Button("📝 Editar")
btn_goto_new = gr.Button("➕ Nueva", variant = "primary")
# TAB 1: EDITAR
with gr.Tab("Editar", id = 1):
with gr.Group():
gr.Markdown("<h3 style = 'text-align: center;'>📝 Editar Prenda</h3>")
edit_img = gr.File(label = "Remplazar Imagen", interactive = True, height = 120)
edit_type = gr.Textbox(label = "Tipo:")
edit_season = gr.Textbox(label = "Temporada:")
edit_coll = gr.Textbox(label = "Colección:")
edit_desc = gr.Textbox(label = "Descripción:", lines = 2, max_lines = 10)
with gr.Row():
btn_edit_cancel = gr.Button("⬅️ Cancelar", elem_id = "btn_cancel")
btn_edit_save = gr.Button("💾 Guardar Cambios", variant = "primary", elem_id = "btn_save")
btn_edit_delete = gr.Button("🗑️ Borrar Prenda", variant = "stop")
with gr.Group(visible = False) as confirm_group:
gr.Markdown("### ⚠️ ¿Seguro que quieres invalidar la prenda?")
with gr.Row(height = 45):
btn_del_yes = gr.Button("✅ Sí", variant = "stop")
btn_del_no = gr.Button("❌ No")
# TAB 2: NUEVA
with gr.Tab("Nueva", id = 2):
with gr.Group():
gr.Markdown("<h3 style = 'text-align: center;'>➕ Nueva Prenda</h3>")
unique_types, unique_colls, unique_brands = dm.get_dropdown_options()
new_img = gr.File(label = "Subir Imagen", type = "filepath", height = 120)
new_type = gr.Dropdown(label = "Tipo:", choices = unique_types, allow_custom_value = True)
new_coll = gr.Dropdown(label = "Colección:", choices = unique_colls, allow_custom_value = True)
new_brand = gr.Dropdown(label = "Marca:", choices = unique_brands, allow_custom_value = True)
new_season = gr.Dropdown(label = "Temporada:", choices = ['N/A', 'PRIMAVERA', 'VERANO', 'OTOÑO', 'INVIERNO'])
new_desc = gr.Textbox(label = "Descripción:", lines = 2, max_lines = 10)
with gr.Row():
btn_new_cancel = gr.Button("⬅️ Cancelar", elem_id = "btn_cancel")
btn_new_save = gr.Button("💾 Guardar Nueva", variant = "primary", elem_id = "btn_save")
def init_load():
items, v_df = dm.get_gallery_items()
return items, v_df
demo.load(init_load, outputs = [gallery, stored_view_df])
filter_type.change(ui_load_gallery, inputs = [filter_type, filter_order], outputs = [gallery, stored_view_df, current_filters])
filter_order.change(ui_load_gallery, inputs = [filter_type, filter_order], outputs = [gallery, stored_view_df, current_filters])
btn_refresh.click(refresh_all_data, outputs = [new_type, new_coll, new_brand, gallery, error_banner, stored_view_df])
# 2. Navigation
nav_outputs = [tabs_interface, is_editing, new_img, new_type, new_coll, new_brand, new_season, new_desc]
def nav_main(): return gr.update(selected = 0), False, None, "", "", "", "N/A", ""
def nav_edit(): return gr.update(selected = 1), True
def nav_new(): return gr.update(selected = 2), False
def cancel_del(): return gr.update(visible = True), gr.update(visible = False)
btn_goto_edit.click(nav_edit, outputs = [tabs_interface, is_editing])
btn_goto_new.click(nav_new, outputs = [tabs_interface, is_editing])
btn_edit_cancel.click(nav_main, outputs = nav_outputs)
btn_new_cancel.click(nav_main, outputs = nav_outputs)
# 3. Selection
gallery.select(ui_get_details,
inputs = [is_editing, stored_view_df],
outputs = [out_val, out_sku, out_date, out_type, out_brand, out_season, out_coll, out_desc,
edit_img, edit_type, edit_season, edit_coll, edit_desc,
selected_idx_holder])
# 4. Save Edit
btn_edit_save.click(ui_save_edit,
inputs = [edit_img, edit_type, edit_season, edit_coll, edit_desc, selected_idx_holder, stored_view_df, current_filters],
outputs = [gallery, stored_view_df]
).then(nav_main, outputs = nav_outputs)
# 5. Save New
btn_new_save.click(ui_save_new,
inputs = [new_img, new_type, new_coll, new_season, new_brand, new_desc, current_filters],
outputs = [gallery, new_sku_holder, stored_view_df]
).then(refresh_all_data, outputs = [new_type, new_coll, new_brand, gallery, error_banner, stored_view_df]) \
.then(nav_main, outputs = nav_outputs)
# 6. Delete (Invalidate)
btn_edit_delete.click(lambda: (gr.update(visible = False), gr.update(visible = True)),
outputs = [btn_edit_delete, confirm_group])
btn_del_yes.click(ui_delete_confirm, inputs = [selected_idx_holder, stored_view_df, current_filters], outputs = [gallery, stored_view_df]) \
.then(cancel_del, outputs = [btn_edit_delete, confirm_group]) \
.then(nav_main, outputs = nav_outputs)
btn_del_no.click(cancel_del, outputs = [btn_edit_delete, confirm_group])
if __name__ == "__main__":
demo.launch()