Properties Details correctly reacts on user interaction

This commit is contained in:
2025-08-04 16:40:48 +02:00
parent badc2e28b0
commit e74639c042
5 changed files with 76 additions and 42 deletions

View File

@@ -16,6 +16,7 @@ using `_id={WORKFLOW_DESIGNER_INSTANCE_ID}{session['user_id']}{get_unique_id()}`
| Properties Properties drag top | `ppt_{self._id}` | | Properties Properties drag top | `ppt_{self._id}` |
| Properties Properties drag left | `ppl_{self._id}` | | Properties Properties drag left | `ppl_{self._id}` |
| Properties Properties drag right | `ppr_{self._id}` | | Properties Properties drag right | `ppr_{self._id}` |
| Properties Properties content | `ppc_{self._id}` |
| Spliter | `s_{self._id}` | | Spliter | `s_{self._id}` |
| Top element | `t_{self._id}` | | Top element | `t_{self._id}` |
| Form for properties | `f_{self._id}_{component_id}` | | Form for properties | `f_{self._id}_{component_id}` |

View File

@@ -205,7 +205,7 @@ function bindWorkflowDesignerToolbox(elementId) {
// Also trigger server-side selection // Also trigger server-side selection
utils.makeRequest('/workflows/select-component', { utils.makeRequest('/workflows/select-component', {
component_id: designer.selectedComponent component_id: designer.selectedComponent
}, `#p_${elementId}`, "outerHTML"); }, `#ppc_${elementId}`, "outerHTML");
}, },
// Deselect all components // Deselect all components

View File

@@ -43,7 +43,7 @@ class WorkflowDesignerCommandManager(BaseCommandManager):
def select_processor(self, component_id: str): def select_processor(self, component_id: str):
return { return {
"hx_post": f"{ROUTE_ROOT}{Routes.SelectProcessor}", "hx_post": f"{ROUTE_ROOT}{Routes.SelectProcessor}",
"hx-target": f"#p_{self._id}", "hx-target": f"#ppc_{self._id}",
"hx-swap": "outerHTML", "hx-swap": "outerHTML",
"hx-trigger": "change", "hx-trigger": "change",
"hx-vals": f'js:{{"_id": "{self._id}", "component_id": "{component_id}"}}', "hx-vals": f'js:{{"_id": "{self._id}", "component_id": "{component_id}"}}',

View File

@@ -25,7 +25,6 @@ from utils.DbManagementHelper import DbManagementHelper
logger = logging.getLogger("WorkflowDesigner") logger = logging.getLogger("WorkflowDesigner")
class WorkflowDesigner(BaseComponent): class WorkflowDesigner(BaseComponent):
def __init__(self, session, def __init__(self, session,
_id=None, _id=None,
@@ -121,7 +120,7 @@ class WorkflowDesigner(BaseComponent):
undo_redo_attrs = UndoRedoAttrs(f"Move Component '{component.title}'", on_undo=self.refresh_state) undo_redo_attrs = UndoRedoAttrs(f"Move Component '{component.title}'", on_undo=self.refresh_state)
self._db.save_state(self._key, self._state, undo_redo_attrs) # update db self._db.save_state(self._key, self._state, undo_redo_attrs) # update db
return self.refresh_designer(), self.properties.refresh(), self._undo_redo.refresh() return self.refresh_designer(), self.properties.refresh(mode="form", oob=True), self._undo_redo.refresh()
def delete_component(self, component_id): def delete_component(self, component_id):
# Remove component # Remove component
@@ -191,7 +190,7 @@ class WorkflowDesigner(BaseComponent):
undo_redo_attrs = UndoRedoAttrs(f"Select Component {component.title}", on_undo=self.refresh_state) undo_redo_attrs = UndoRedoAttrs(f"Select Component {component.title}", on_undo=self.refresh_state)
self._db.save_state(self._key, self._state, undo_redo_attrs) self._db.save_state(self._key, self._state, undo_redo_attrs)
return self.properties.refresh(), self._undo_redo.refresh() return self.properties.refresh(mode="form"), self._undo_redo.refresh()
def save_properties(self, component_id: str, details: dict): def save_properties(self, component_id: str, details: dict):
if component_id in self._state.components: if component_id in self._state.components:
@@ -217,7 +216,7 @@ class WorkflowDesigner(BaseComponent):
undo_redo_attrs = UndoRedoAttrs(f"Set Processor for {component.title}", on_undo=self.refresh_state) undo_redo_attrs = UndoRedoAttrs(f"Set Processor for {component.title}", on_undo=self.refresh_state)
self._db.save_state(self._key, self._state, undo_redo_attrs) self._db.save_state(self._key, self._state, undo_redo_attrs)
return self.refresh_properties(), self._undo_redo.refresh() return self.properties.refresh(mode="form"), self._undo_redo.refresh()
def play_workflow(self, boundaries: dict): def play_workflow(self, boundaries: dict):
self._error_message = None self._error_message = None

View File

@@ -2,7 +2,7 @@ from fasthtml.common import *
from dataclasses import dataclass from dataclasses import dataclass
from components.BaseComponent import BaseComponent from components.BaseComponent import BaseComponent
from components.workflows.constants import COMPONENT_TYPES from components.workflows.constants import COMPONENT_TYPES, PROCESSOR_TYPES
from components_helpers import mk_dialog_buttons from components_helpers import mk_dialog_buttons
from core.utils import merge_classes from core.utils import merge_classes
@@ -44,12 +44,25 @@ class WorkflowDesignerProperties(BaseComponent):
def update_component(self, component_id): def update_component(self, component_id):
if component_id is None or component_id not in self._owner.get_state().components: if component_id is None or component_id not in self._owner.get_state().components:
self._component = None self._component = None
self._component = self._owner.get_state().components[component_id] else:
self._component = self._owner.get_state().components[component_id]
def refresh(self, oob=True): def refresh(self, mode="all", oob=False):
self.update_component(self._owner.get_state().selected_component_id) self.update_component(self._owner.get_state().selected_component_id)
if mode == "form":
return self._mk_content(oob=oob)
return self.__ft__(oob=oob) return self.__ft__(oob=oob)
def _mk_layout(self):
return 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):
return Div( return Div(
"Input", "Input",
@@ -66,30 +79,6 @@ class WorkflowDesignerProperties(BaseComponent):
cls="wkf-properties-output" cls="wkf-properties-output"
) )
def _mk_header(self, cls=None):
if self._component is None:
return None
icon = COMPONENT_TYPES[self._component.type]["icon"]
color = COMPONENT_TYPES[self._component.type]["color"]
return Div(
Div(
Span(icon),
H4(self._component.title, cls="font-semibold text-xs"),
cls=f"rounded-lg border-2 {color} flex text-center px-2"
),
H1(self._component.id, cls="ml-4"),
cls=merge_classes("flex mb-2", cls)
)
def _mk_form(self, cls=None):
if self._component is None:
return None
return Div(
cls=merge_classes(cls)
)
def _mk_properties(self): def _mk_properties(self):
return Div( return Div(
# Drag handle (20px height) # Drag handle (20px height)
@@ -100,9 +89,9 @@ class WorkflowDesignerProperties(BaseComponent):
), ),
# Properties content # Properties content
self._mk_content(cls="flex-1 overflow-y-auto"),
self._mk_header(cls="flex-none"), # Dialog buttons
self._mk_form(cls="flex-1 overflow-y-auto"),
mk_dialog_buttons(cls="flex-none mt-auto pb-2"), mk_dialog_buttons(cls="flex-none mt-auto pb-2"),
# Left resize handle # Left resize handle
@@ -122,22 +111,67 @@ class WorkflowDesignerProperties(BaseComponent):
cls="wkf-properties-properties flex flex-col", cls="wkf-properties-properties flex flex-col",
) )
def _mk_layout(self): def _mk_content(self, cls=None, oob=False):
return Div( return Div(
self._mk_input(), self._header(),
self._mk_properties(), self._form(),
self._mk_output(), cls=merge_classes(cls),
cls="flex", id=f"ppc_{self._id}",
style="height: 100%; width: 100%; flex: 1;" hx_swap_oob=f'true' if oob else None,
) )
def _header(self):
if self._component is None:
return None
icon = COMPONENT_TYPES[self._component.type]["icon"]
color = COMPONENT_TYPES[self._component.type]["color"]
return Div(
Div(
Span(icon),
H4(self._component.title, cls="font-semibold text-xs"),
cls=f"rounded-lg border-2 {color} flex text-center px-2"
),
cls=merge_classes("flex"),
)
def _form(self):
if self._component is None:
return None
return Form(
self._mk_select_processor(),
self._content_details(),
)
def _mk_select_processor(self):
selected_processor_name = self._component.properties["processor_name"]
return Select(
*[Option(processor_name, selected="selected" if processor_name == selected_processor_name else None)
for processor_name in PROCESSOR_TYPES[self._component.type]],
cls="select select-sm m-2",
id="processor_name",
name="processor_name",
**self._commands.select_processor(self._component.id)
)
def _content_details(self):
component_type = self._component.type
processor_name = self._component.properties.get("processor_name", None)
key = f"_mk_details_{component_type}_{processor_name}"
if hasattr(self, key):
return getattr(self, key)()
else:
return Div(f"Component '{key}' not found")
def __ft__(self, oob=False): def __ft__(self, oob=False):
# return self.render() # return self.render()
return Div( return Div(
self._mk_layout(), self._mk_layout(),
style=f"height: {self._get_height()}px;", style=f"height: {self._get_height()}px;",
id=f"p_{self._id}", id=f"p_{self._id}",
hx_swap_oob='true' if oob else None, hx_swap_oob=f'innerHTML' if oob else None,
cls="wkf-properties" cls="wkf-properties"
) )