Josep Pon Farreny
feat: Improve UI
5b1ca13
raw
history blame
5.06 kB
from __future__ import annotations
from typing import TYPE_CHECKING, Any, NamedTuple
import gradio as gr
if TYPE_CHECKING:
from collections.abc import Callable, Sequence
class MutableCheckBoxGroupEntry(NamedTuple):
"""Entry of the mutable checkbox group."""
name: str
value: str
class MutableCheckBoxGroup(gr.Blocks):
"""Check box group with controls to add or remove values."""
def __init__(
self,
values: list[MutableCheckBoxGroupEntry] | None = None,
label: str = "Extendable List",
new_value_label: str = "New Item Value",
new_name_label: str = "New Item Name",
new_value_placeholder: str = "New item value ...",
new_name_placeholder: str = "New item name, if not given value will be used...",
on_change: Callable[[Sequence[MutableCheckBoxGroupEntry]], None] | None = None,
) -> None:
super().__init__()
self.values = values or []
self.label = label
self.new_value_label = new_value_label
self.new_name_label = new_name_label
self.new_value_placeholder = new_value_placeholder
self.new_name_placeholder = new_name_placeholder
self.on_change = on_change
self._build_interface()
def _build_interface(self) -> None:
# Custom CSS for vertical checkbox layout
self.style = """
#vertical-container .wrap {
display: flex;
flex-direction: column;
gap: 8px;
}
#vertical-container .wrap label {
display: flex;
align-items: center;
gap: 8px;
}
"""
with self:
gr.Markdown(f"### {self.label}")
# Store items in state
self.state = gr.State(self.values)
# Input row
with gr.Row():
self.input_value = gr.Textbox(
label=self.new_value_label,
placeholder=self.new_value_placeholder,
scale=3,
)
self.input_name = gr.Textbox(
label=self.new_name_label,
placeholder=self.new_name_placeholder,
scale=2,
)
with gr.Row():
self.add_btn = gr.Button(
"Add",
variant="primary",
scale=1,
)
self.delete_btn = gr.Button(
"Delete Selected",
variant="stop",
scale=1,
)
# Vertical checkbox group
self.items_group = gr.CheckboxGroup(
choices=self.values,
label="Items",
elem_id="vertical-container",
container=True,
)
# Set up event handlers
self.add_btn.click(
self._add_item,
inputs=[self.state, self.input_value, self.input_name],
outputs=[
self.state,
self.items_group,
self.input_value,
self.input_name,
],
)
self.delete_btn.click(
self._delete_selected,
inputs=[self.state, self.items_group],
outputs=[self.state, self.items_group],
)
def get_values(self) -> Sequence[str]:
"""Get check box values."""
return self.state.value
def _add_item(
self,
items: list[MutableCheckBoxGroupEntry],
new_value: str,
new_name: str,
) -> tuple[list[MutableCheckBoxGroupEntry], dict[str, Any], str, str]:
if not new_name:
new_name = new_value
if new_value:
if any(entry.name == new_name for entry in items):
raise gr.Error(
f"Entry with name '{new_name}' already exists",
duration=10,
)
if any(entry.value == new_value for entry in items):
raise gr.Error(
f"Entry with value '{new_value}' already exists",
duration=10,
)
items = [*items, MutableCheckBoxGroupEntry(new_name, new_value)]
if self.on_change:
self.on_change(items)
# State, checkbox, input_value, input_name
return items, gr.update(choices=items), "", ""
# State, checkbox, input_value, input_name
return items, gr.update(), "", ""
def _delete_selected(
self,
items: list[MutableCheckBoxGroupEntry],
selected: list[str],
) -> tuple[list[MutableCheckBoxGroupEntry], dict[str, Any]]:
updated_items = [item for item in items if item.value not in selected]
if self.on_change:
self.on_change(updated_items)
return updated_items, gr.update(choices=updated_items, value=[])