From fe5668fbed9a57a5457bd9848aff7317d94a8fca Mon Sep 17 00:00:00 2001 From: Kodjo Sossouvi Date: Tue, 26 Aug 2025 21:58:42 +0200 Subject: [PATCH] Working version of EntrySelector --- .../entryselector/EntrySelectorApp.py | 11 ++++- .../entryselector/assets/EntrySelector.css | 4 ++ src/components/entryselector/commands.py | 4 +- .../entryselector/components/EntrySelector.py | 21 ++++++-- .../workflows/components/WorkflowDesigner.py | 3 +- .../components/WorkflowDesignerProperties.py | 49 ++++++++++--------- .../workflows/components/WorkflowPlayer.py | 2 + src/workflow/engine.py | 8 +-- 8 files changed, 67 insertions(+), 35 deletions(-) diff --git a/src/components/entryselector/EntrySelectorApp.py b/src/components/entryselector/EntrySelectorApp.py index a145b11..6b8f04b 100644 --- a/src/components/entryselector/EntrySelectorApp.py +++ b/src/components/entryselector/EntrySelectorApp.py @@ -14,4 +14,13 @@ repositories_app, rt = fast_app() def get(session, _id: str, entry: str): logger.debug(f"Entering {Routes.Select} with args {debug_session(session)}, {_id=}, {entry=}") instance = InstanceManager.get(session, _id) - return instance.select_entry(entry) + to_update = instance.select_entry(entry) + + res = [instance] + if res is None: + return instance + if isinstance(to_update, (list, tuple)): + res.extend(to_update) + else: + res.append(to_update) + return tuple(res) diff --git a/src/components/entryselector/assets/EntrySelector.css b/src/components/entryselector/assets/EntrySelector.css index e6fa317..f368244 100644 --- a/src/components/entryselector/assets/EntrySelector.css +++ b/src/components/entryselector/assets/EntrySelector.css @@ -11,6 +11,10 @@ display: inline-block; /* Ensure entries align horizontally if needed */ } +.es-entry-selected { + border: 2px solid var(--color-primary); +} + .es-entry:hover { background-color: var(--color-base-300); } \ No newline at end of file diff --git a/src/components/entryselector/commands.py b/src/components/entryselector/commands.py index 2b2f631..aa9b3d7 100644 --- a/src/components/entryselector/commands.py +++ b/src/components/entryselector/commands.py @@ -9,7 +9,7 @@ class EntrySelectorCommandManager(BaseCommandManager): def select_entry(self, entry): return { "hx-get": f"{ROUTE_ROOT}{Routes.Select}", - "hx-target": f"#{self._owner.content_id}", - "hx-swap": "innerHTML", + "hx-target": f"#{self._id}", + "hx-swap": "outerHTML", "hx-vals": f'{{"_id": "{self._id}", "entry": "{entry}"}}', } \ No newline at end of file diff --git a/src/components/entryselector/components/EntrySelector.py b/src/components/entryselector/components/EntrySelector.py index 738bc61..780a7d0 100644 --- a/src/components/entryselector/components/EntrySelector.py +++ b/src/components/entryselector/components/EntrySelector.py @@ -9,12 +9,12 @@ logger = logging.getLogger("EntrySelector") class EntrySelector(BaseComponentMultipleInstance): - def __init__(self, session, _id, owner, content_id, data=None, hooks=None, key=None, boundaries=None): + def __init__(self, session, _id, owner, data=None, hooks=None, key=None, boundaries=None): super().__init__(session, _id) self._key = key self._owner = owner # debugger component self.data = data - self.content_id = content_id + self.selected = None self.hooks = hooks self._boundaries = boundaries if boundaries else {"width": "300"} self._commands = EntrySelectorCommandManager(self) @@ -22,20 +22,31 @@ class EntrySelector(BaseComponentMultipleInstance): def set_data(self, data): self.data = data + def set_selected(self, selected): + if selected is None: + self.selected = None + else: + self.selected = int(selected) + def set_boundaries(self, boundaries): self._boundaries = boundaries def select_entry(self, entry): logger.debug(f"Selecting entry {entry}") - # return self._owner.select_entry(entry) + self.set_selected(entry) + if self.hooks is not None and (on_entry_selected := self.hooks.get("on_entry_selected", None)) is not None: + return on_entry_selected(entry) + else: + return None def _mk_content(self): - if self.data is None: + if not self.data: return [Div("no entry")] return [Div(index, **self._commands.select_entry(index), - cls="es-entry") for index in range(self.data)] + cls=f"es-entry {'es-entry-selected' if index == self.selected else ''}") + for index in range(self.data)] def __ft__(self): return Div( diff --git a/src/components/workflows/components/WorkflowDesigner.py b/src/components/workflows/components/WorkflowDesigner.py index ac6fda3..5ab896a 100644 --- a/src/components/workflows/components/WorkflowDesigner.py +++ b/src/components/workflows/components/WorkflowDesigner.py @@ -228,7 +228,7 @@ class WorkflowDesigner(BaseComponent): self._error_message = self._player.global_error else: - + self.properties.set_entry_selector_data(self._player.nb_items) # change the tab and display the results self._player.set_boundaries(boundaries) self.tabs_manager.add_tab(f"Workflow {self._designer_settings.workflow_name}", self._player, self._player.key) @@ -238,6 +238,7 @@ class WorkflowDesigner(BaseComponent): def stop_workflow(self): self._error_message = None self._player.stop() + self.properties.set_entry_selector_data(0) return self.tabs_manager.refresh() def on_processor_details_event(self, component_id: str, event_name: str, details: dict): diff --git a/src/components/workflows/components/WorkflowDesignerProperties.py b/src/components/workflows/components/WorkflowDesignerProperties.py index aa943d8..54dbca5 100644 --- a/src/components/workflows/components/WorkflowDesignerProperties.py +++ b/src/components/workflows/components/WorkflowDesignerProperties.py @@ -26,15 +26,13 @@ class WorkflowDesignerProperties(BaseComponent): self._component = None self.update_layout() self.update_component(self._owner.get_state().selected_component_id) - self._input_entry_selector = InstanceManager.new(self._session, - EntrySelector, - owner=self, - content_id=f"pic_{self._id}", - data=100) - self._output_entry_selector = InstanceManager.new(self._session, - EntrySelector, - owner=self, - content_id=f"poc_{self._id}") + self.entry_selector = InstanceManager.new(self._session, + EntrySelector, + owner=self, + hooks={"on_entry_selected": self.on_entry_selector_changed}) + + def set_entry_selector_data(self, data): + self.entry_selector.set_data(data) def update_layout(self): if self._owner.get_state().properties_input_width is None: @@ -65,31 +63,38 @@ class WorkflowDesignerProperties(BaseComponent): return self.__ft__(oob=oob) + def on_entry_selector_changed(self, entry): + return (self._mk_input(content="input:" + entry, oob=True), + self._mk_output(content="output:" + entry, oob=True)) + def _mk_layout(self): return Div( - self._mk_input(), - self._mk_properties(), - self._mk_output(), - cls="flex", - style="height: 100%; width: 100%; flex: 1;" + self.entry_selector, + Div( + self._mk_input(), + self._mk_properties(), + self._mk_output(), + cls="flex", + style="height: 100%; width: 100%; flex: 1;" + ) ) - def _mk_input(self): + def _mk_input(self, content=None, oob=False): return Div( - self._input_entry_selector, - Div(id=f"pic_{self._id}"), + content, id=f"pi_{self._id}", style=f"width: {self.layout.input_width}px;", - cls="wkf-properties-input" + cls="wkf-properties-input", + hx_swap_oob=f'true' if oob else None, ) - def _mk_output(self): + def _mk_output(self, content=None, oob=False): return Div( - self._output_entry_selector, - "Output Content", + content, id=f"po_{self._id}", style=f"width: {self.layout.output_width}px;", - cls="wkf-properties-output" + cls="wkf-properties-output", + hx_swap_oob=f'true' if oob else None, ) def _mk_properties(self): diff --git a/src/components/workflows/components/WorkflowPlayer.py b/src/components/workflows/components/WorkflowPlayer.py index 75d2a1c..2bb8fee 100644 --- a/src/components/workflows/components/WorkflowPlayer.py +++ b/src/components/workflows/components/WorkflowPlayer.py @@ -53,6 +53,7 @@ class WorkflowPlayer(BaseComponent): self.runtime_states = {} self.global_error = None self.has_error = False + self.nb_items = 0 def set_boundaries(self, boundaries: dict): self._datagrid.set_boundaries(boundaries) @@ -93,6 +94,7 @@ class WorkflowPlayer(BaseComponent): self.global_error = engine.global_error else: # loop through the components and update the runtime states + self.nb_items = engine.nb_items for component in sorted_components: runtime_state = self.runtime_states.get(component.id) diff --git a/src/workflow/engine.py b/src/workflow/engine.py index ebc687f..4b5a297 100644 --- a/src/workflow/engine.py +++ b/src/workflow/engine.py @@ -156,7 +156,7 @@ class WorkflowEngine: self.global_error = None self.errors = {} self.debug = {} - self.item_count = -1 + self.nb_items = -1 def add_processor(self, processor: DataProcessor) -> 'WorkflowEngine': """Add a data processor to the pipeline.""" @@ -201,10 +201,10 @@ class WorkflowEngine: if not self.processors: self.has_error = False self.global_error = "No processors in the pipeline" - self.item_count = -1 + self.nb_items = -1 raise ValueError(self.global_error) - self.item_count = 0 + self.nb_items = 0 first_processor = self.processors[0] if not isinstance(first_processor, DataProducer): @@ -215,7 +215,7 @@ class WorkflowEngine: self.debug[first_processor.component_id] = {"input": [], "output": []} for item_linkage_id, item in enumerate(first_processor.process(None)): - self.item_count += 1 + self.nb_items += 1 self.debug[first_processor.component_id]["output"].append(WorkflowPayload( processor_name=first_processor.__class__.__name__, component_id=first_processor.component_id,