Skip to content

Commit db670ae

Browse files
briemlaclaude
andcommitted
Fix class filter dialog selection in WebUI
The dialog never rendered its checkboxes, bound every checkbox to the last class created in the loop and always started from all classes, so "Apply" also passed classes that were not selected. Move the dialog into FilterClassificationForm, which derives its state from the current class filter selection (all classes if there is none, as in the customtkinter GUI), binds each checkbox to its own class and evaluates the checkbox value instead of toggling. Buttons are built in a dedicated method so further buttons can be added easily. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent ca6a7d2 commit db670ae

1 file changed

Lines changed: 66 additions & 28 deletions

File tree

  • OTAnalytics/plugin_ui/nicegui_gui/pages/visualization_filters_form

‎OTAnalytics/plugin_ui/nicegui_gui/pages/visualization_filters_form/container.py‎

Lines changed: 66 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Callable
2+
13
from nicegui import ui
24
from nicegui.elements.button import Button
35
from nicegui.events import ValueChangeEventArguments
@@ -140,6 +142,68 @@ def _on_reset_button_clicked(self) -> None:
140142
self._dialog.close()
141143

142144

145+
class FilterClassificationForm:
146+
"""Dialog to select the classes whose tracks are shown.
147+
148+
The checkboxes show the current class filter selection. Without a selection,
149+
every class counts as selected, as in the customtkinter GUI.
150+
"""
151+
152+
def __init__(self, viewmodel: ViewModel) -> None:
153+
self._viewmodel = viewmodel
154+
self._classes: list[str] = []
155+
self._selected_classes: set[str] = set()
156+
157+
def open(self) -> None:
158+
self._classes = self._viewmodel.get_classes()
159+
self._selected_classes = set(self._current_selection())
160+
with ui.dialog() as self._dialog, ui.card():
161+
for classification in self._classes:
162+
self._build_checkbox(classification)
163+
with ui.row():
164+
self._build_buttons()
165+
self._dialog.open()
166+
167+
def _current_selection(self) -> list[str]:
168+
selection = self._viewmodel.get_class_filter_selection()
169+
return self._classes if selection is None else selection
170+
171+
def _build_checkbox(self, classification: str) -> None:
172+
FormFieldCheckbox(
173+
label_text=classification,
174+
initial_value=classification in self._selected_classes,
175+
on_value_change=self._create_class_toggle(classification),
176+
).build()
177+
178+
def _create_class_toggle(
179+
self, classification: str
180+
) -> Callable[[ValueChangeEventArguments], None]:
181+
def toggle(event: ValueChangeEventArguments) -> None:
182+
self._select_class(classification, event.value)
183+
184+
return toggle
185+
186+
def _build_buttons(self) -> None:
187+
ui.button("Apply", on_click=self._on_apply_button_clicked)
188+
189+
def _select_class(self, classification: str, selected: bool) -> None:
190+
if selected:
191+
self._selected_classes.add(classification)
192+
else:
193+
self._selected_classes.discard(classification)
194+
195+
def _on_apply_button_clicked(self) -> None:
196+
self._viewmodel.apply_filter_tracks_by_class(
197+
[
198+
classification
199+
for classification in self._classes
200+
if classification in self._selected_classes
201+
]
202+
)
203+
logger().info("Filter tracks by classification applied")
204+
self._dialog.close()
205+
206+
143207
class VisualizationFiltersForm(AbstractFrameFilter, ButtonForm):
144208

145209
def __init__(
@@ -151,7 +215,6 @@ def __init__(
151215
self._viewmodel = view_model
152216
self._filter_by_date: ui.checkbox
153217
self._filter_by_class: ui.checkbox
154-
self._filter_by_class_list: list = []
155218
self._button_previous_second: ui.button | None = None
156219
self._button_previous_frame: ui.button | None = None
157220
self._button_previous_event: ui.button | None = None
@@ -180,6 +243,7 @@ def __init__(
180243
self._filter_by_date_popup = FilterDateRangeForm(
181244
self._resource_manager, self._viewmodel
182245
)
246+
self._filter_by_class_popup = FilterClassificationForm(self._viewmodel)
183247
self._input_seconds = FormFieldInteger(
184248
label_text=self._resource_manager.get(
185249
VisualizationFiltersKeys.LABEL_SECONDS
@@ -320,33 +384,7 @@ def on_input_change(self, _: ValueChangeEventArguments) -> None:
320384
)
321385

322386
def open_classification_dialog(self) -> None:
323-
self._filter_by_class_list = self._viewmodel.get_classes()
324-
with ui.dialog() as dialog, ui.card():
325-
for classification_class in self._filter_by_class_list:
326-
FormFieldCheckbox(
327-
label_text=classification_class,
328-
initial_value=classification_class in self._selected_classes(),
329-
on_value_change=lambda e: self.remove_or_add_class(
330-
classification_class
331-
),
332-
)
333-
ui.button("Apply", on_click=lambda e: self.apply_new_classification(dialog))
334-
dialog.open()
335-
336-
def _selected_classes(self) -> list[str]:
337-
selected_classes = self._viewmodel.get_class_filter_selection()
338-
return selected_classes if selected_classes else self._viewmodel.get_classes()
339-
340-
def apply_new_classification(self, dialog: ui.dialog) -> None:
341-
self._viewmodel.apply_filter_tracks_by_class(self._filter_by_class_list)
342-
self._filter_by_class_list = []
343-
dialog.close()
344-
345-
def remove_or_add_class(self, class_name: str) -> None:
346-
if class_name in self._filter_by_class_list:
347-
self._filter_by_class_list.remove(class_name)
348-
else:
349-
self._filter_by_class_list.append(class_name)
387+
self._filter_by_class_popup.open()
350388

351389
def open_range_dialog(self) -> None:
352390
self._filter_by_date_popup.open()

0 commit comments

Comments
 (0)