ソースを参照

scenario selector list of scenarios (#1245)

* Allow user to specify a list of scenario/cycle to the scenario selector
resolves #1040
PS: fix bug in core selector component that won't expand!

* Allow user to specify a list of scenario/cycle to the scenario selector
resolves #1040
PS: fix bug in core selector component that won't expand!

* doc

* format

* todo doc for Fab

---------

Co-authored-by: Fred Lefévère-Laoide <Fred.Lefevere-Laoide@Taipy.io>
Fred Lefévère-Laoide 1 年間 前
コミット
eb6baf19c6

+ 10 - 0
frontend/taipy/src/CoreSelector.tsx

@@ -266,6 +266,15 @@ const CoreSelector = (props: CoreSelectorProps) => {
 
     useDispatchRequestUpdateOnFirstRender(dispatch, id, module, updateVars, undefined, true);
 
+    const onItemExpand = useCallback((e: SyntheticEvent, itemId: string, expanded: boolean) => {
+        setExpandedItems((old) => {
+            if (!expanded) {
+                return old.filter((id) => id != itemId);
+            }
+            return [...old, itemId];
+        });
+    }, []);
+
     const onNodeSelect = useCallback(
         (e: SyntheticEvent, nodeId: string, isSelected: boolean) => {
             if (!isSelected) {
@@ -420,6 +429,7 @@ const CoreSelector = (props: CoreSelectorProps) => {
                 selectedItems={selected}
                 multiSelect={multiple && !multiple}
                 expandedItems={expandedItems}
+                onItemExpansionToggle={onItemExpand}
             >
                 {entities
                     ? entities.map((item) => (

+ 17 - 6
frontend/taipy/src/ScenarioSelector.tsx

@@ -67,7 +67,7 @@ interface ScenarioSelectorProps {
     showPrimaryFlag?: boolean;
     updateVarName?: string;
     updateVars: string;
-    scenarios?: Cycles | Scenarios;
+    innerScenarios?: Cycles | Scenarios;
     onScenarioCrud: string;
     onChange?: string;
     onCreation?: string;
@@ -278,7 +278,9 @@ const ScenarioEditDialog = ({ scenario, submit, open, actionEdit, configs, close
                                         <DatePicker
                                             label="Date"
                                             value={new Date(form.values.date)}
-                                            onChange={(date?:Date|null) => form.setFieldValue("date", date?.toISOString())}
+                                            onChange={(date?: Date | null) =>
+                                                form.setFieldValue("date", date?.toISOString())
+                                            }
                                             disabled={actionEdit}
                                         />
                                     </LocalizationProvider>
@@ -422,10 +424,19 @@ const ScenarioSelector = (props: ScenarioSelectorProps) => {
 
     const onSubmit = useCallback(
         (...values: unknown[]) => {
-            dispatch(createSendActionNameAction(props.id, module, props.onScenarioCrud, props.onCreation, props.updateVarName, ...values));
+            dispatch(
+                createSendActionNameAction(
+                    props.id,
+                    module,
+                    props.onScenarioCrud,
+                    props.onCreation,
+                    props.updateVarName,
+                    ...values
+                )
+            );
             if (values.length > 1 && values[1]) {
                 // delete requested => unselect current node
-                const lovVar = getUpdateVar(props.updateVars, "scenarios");
+                const lovVar = getUpdateVar(props.updateVars, "innerScenarios");
                 dispatch(
                     createSendUpdateAction(props.updateVarName, undefined, module, props.onChange, propagate, lovVar)
                 );
@@ -487,9 +498,9 @@ const ScenarioSelector = (props: ScenarioSelectorProps) => {
             <Box sx={MainTreeBoxSx} id={props.id} className={className}>
                 <CoreSelector
                     {...props}
-                    entities={props.scenarios}
+                    entities={props.innerScenarios}
                     leafType={NodeType.SCENARIO}
-                    lovPropertyName="scenarios"
+                    lovPropertyName="innerScenarios"
                     editComponent={EditScenario}
                     showPins={showPins}
                 />

+ 3 - 2
taipy/gui/_renderers/builder.py

@@ -452,8 +452,9 @@ class _Builder:
                 else lov_name
             )
             hash_name = self.__get_typed_hash_name(typed_lov_hash, PropertyType.lov)
-            self.__update_vars.append(f"{property_name}={hash_name}")
-            self.__set_react_attribute(property_name, hash_name)
+            camel_prop = _to_camel_case(property_name)
+            self.__update_vars.append(f"{camel_prop}={hash_name}")
+            self.__set_react_attribute(camel_prop, hash_name)
 
         return self
 

+ 3 - 1
taipy/gui/gui.py

@@ -991,7 +991,9 @@ class Gui:
         for k, v in values.items():
             if isinstance(v, (_TaipyData, _TaipyContentHtml)) and v.get_name() in modified_vars:
                 modified_vars.remove(v.get_name())
-            elif isinstance(v, _DoNotUpdate):
+            elif isinstance(v, _DoNotUpdate) or (
+                isinstance(v, (list, tuple)) and next(isinstance(i, _DoNotUpdate) for i in v)
+            ):
                 modified_vars.remove(k)
         for _var in modified_vars:
             newvalue = values.get(_var)

+ 7 - 3
taipy/gui_core/_GuiCoreLib.py

@@ -59,9 +59,12 @@ class _GuiCore(ElementLibrary):
                 "show_pins": ElementProperty(PropertyType.boolean, False),
                 "on_creation": ElementProperty(PropertyType.function),
                 "show_dialog": ElementProperty(PropertyType.boolean, True),
+                "scenarios": ElementProperty(PropertyType.dynamic_list),
             },
             inner_properties={
-                "scenarios": ElementProperty(PropertyType.lov, f"{{{__CTX_VAR_NAME}.get_scenarios()}}"),
+                "inner_scenarios": ElementProperty(
+                    PropertyType.lov, f"{{{__CTX_VAR_NAME}.get_scenarios(<tp:prop:scenarios>)}}"
+                ),
                 "on_scenario_crud": ElementProperty(PropertyType.function, f"{{{__CTX_VAR_NAME}.crud_scenario}}"),
                 "configs": ElementProperty(PropertyType.react, f"{{{__CTX_VAR_NAME}.get_scenario_configs()}}"),
                 "core_changed": ElementProperty(PropertyType.broadcast, _GuiCoreContext._CORE_CHANGED_NAME),
@@ -178,7 +181,8 @@ class _GuiCore(ElementLibrary):
                 ),
                 "history": ElementProperty(
                     PropertyType.react,
-                    f"{{{__CTX_VAR_NAME}.get_data_node_history(" + f"{_GuiCoreContext._DATANODE_VIZ_HISTORY_ID_VAR},"
+                    f"{{{__CTX_VAR_NAME}.get_data_node_history("
+                    + f"{_GuiCoreContext._DATANODE_VIZ_HISTORY_ID_VAR},"
                     + "<tp:uniq:dn>)}",
                 ),
                 "tabular_data": ElementProperty(
@@ -214,7 +218,7 @@ class _GuiCore(ElementLibrary):
                     + f"chart_id={_GuiCoreContext._DATANODE_VIZ_DATA_CHART_ID_VAR};"
                     + f"properties_id={_GuiCoreContext._DATANODE_VIZ_PROPERTIES_ID_VAR}",
                 ),
-            }
+            },
         ),
         "job_selector": Element(
             "value",

+ 14 - 11
taipy/gui_core/_context.py

@@ -240,16 +240,19 @@ class _GuiCoreContext(CoreEventConsumerBase):
             )
         return None
 
-    def get_scenarios(self):
-        cycles_scenarios = []
-        with self.lock:
-            if self.scenario_by_cycle is None:
-                self.scenario_by_cycle = get_cycles_scenarios()
-            for cycle, scenarios in self.scenario_by_cycle.items():
-                if cycle is None:
-                    cycles_scenarios.extend(scenarios)
-                else:
-                    cycles_scenarios.append(cycle)
+    def get_scenarios(self, scenarios: t.Optional[t.List[t.Union[Cycle, Scenario]]]):
+        cycles_scenarios: t.List[t.Union[Cycle, Scenario]] = []
+        if scenarios is None:
+            with self.lock:
+                if self.scenario_by_cycle is None:
+                    self.scenario_by_cycle = get_cycles_scenarios()
+                for cycle, c_scenarios in self.scenario_by_cycle.items():
+                    if cycle is None:
+                        cycles_scenarios.extend(c_scenarios)
+                    else:
+                        cycles_scenarios.append(cycle)
+        else:
+            cycles_scenarios = scenarios
         return sorted(cycles_scenarios, key=_GuiCoreContext.get_entity_creation_date_iso)
 
     def select_scenario(self, state: State, id: str, payload: t.Dict[str, str]):
@@ -506,7 +509,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
             self.__do_datanodes_tree()
         return (
             self.data_nodes_by_owner.get(scenario.id if scenario else None, []) if self.data_nodes_by_owner else []
-        ) + (self.get_scenarios() if not scenario else [])
+        ) + (self.get_scenarios(None) if not scenario else [])
 
     def data_node_adapter(self, data):
         try:

+ 46 - 6
taipy/gui_core/viselements.json

@@ -63,16 +63,34 @@
                         "doc": "If True, a pin is shown on each item of the selector and allows to restrict the number of displayed items."
                     },
                     {
-                      "name": "on_creation",
-                      "type": "Callback",
-                      "doc": "The name of the function that is triggered when a scenario is about to be created.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the scenario selector.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>config: the name of the selected scenario configuration.</li>\n<li>date: the creation date for the new scenario.</li>\n<li>label: the user-specified label.</li>\n<li>properties: a dictionary containing all the user-defined custom properties.</li>\n</ul>\n</li>\n<li>The callback function can return a scenario, a string containing an error message (a scenario will not be created), or None (then a new scenario is created with the user parameters).</li>\n</ul>",
-                      "signature": [["state", "State"], ["id", "str"], ["payload", "dict"]]
+                        "name": "on_creation",
+                        "type": "Callback",
+                        "doc": "The name of the function that is triggered when a scenario is about to be created.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>id (str): the identifier of the scenario selector.</li>\n<li>payload (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>config: the name of the selected scenario configuration.</li>\n<li>date: the creation date for the new scenario.</li>\n<li>label: the user-specified label.</li>\n<li>properties: a dictionary containing all the user-defined custom properties.</li>\n</ul>\n</li>\n<li>The callback function can return a scenario, a string containing an error message (a scenario will not be created), or None (then a new scenario is created with the user parameters).</li>\n</ul>",
+                        "signature": [
+                            [
+                                "state",
+                                "State"
+                            ],
+                            [
+                                "id",
+                                "str"
+                            ],
+                            [
+                                "payload",
+                                "dict"
+                            ]
+                        ]
                     },
                     {
                         "name": "show_dialog",
                         "type": "bool",
                         "default_value": "True",
                         "doc": "If True, a dialog is shown when the user click on the 'Add scenario' button."
+                    },
+                    {
+                        "name": "scenarios",
+                        "type": "dynamic(list[Scenario|Cycle])",
+                        "doc": "TODO: The list of Scenario/Cycle to show. Shows all Cycle/Scenario if value is None."
                     }
                 ]
             }
@@ -166,7 +184,20 @@
                         "name": "on_submission_change",
                         "type": "Callback",
                         "doc": "The name of the function that is triggered when a submission status is changed.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>submittable (Submittable): the entity (usually a Scenario) that was submitted.</li>\n<li>details (dict): the details on this callback's invocation.<br/>\nThis dictionary has the following keys:\n<ul>\n<li>submission_status (str): the new status of the submission (possible values: SUBMITTED, COMPLETED, CANCELED, FAILED, BLOCKED, WAITING, RUNNING).</li>\n<li>job: the Job (if any) that is at the origin of the submission status change.</li>\n</ul>",
-                        "signature": [["state", "State"], ["submittable", "Submittable"], ["details", "dict"]]
+                        "signature": [
+                            [
+                                "state",
+                                "State"
+                            ],
+                            [
+                                "submittable",
+                                "Submittable"
+                            ],
+                            [
+                                "details",
+                                "dict"
+                            ]
+                        ]
                     }
                 ]
             }
@@ -212,7 +243,16 @@
                         "name": "on_action",
                         "type": "Callback",
                         "doc": "The name of the function that is triggered when a a node is selected.<br/><br/>All the parameters of that function are optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>entity (DataNode | Task): the entity (DataNode or Task) that was selected.</li>\n</ul>",
-                        "signature": [["state", "State"], ["entity", "Task | DataNode"]]
+                        "signature": [
+                            [
+                                "state",
+                                "State"
+                            ],
+                            [
+                                "entity",
+                                "Task | DataNode"
+                            ]
+                        ]
                     }
                 ]
             }