diff --git a/.gitignore b/.gitignore
index c97af67db..fea63bc4e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -112,6 +112,7 @@ mkdocs.yml
/taipy
/taipy-fe
/docs/refmans/gui/viselements/index.md
+/docs/refmans/gui/viselements/mui-icons.svg
/docs/refmans/gui/viselements/generic/*.md
!/docs/refmans/gui/viselements/generic/index.md
/docs/refmans/gui/viselements/generic/charts/*.md
diff --git a/docs/refmans/gui/viselements/corelements/data_node.md_template b/docs/refmans/gui/viselements/corelements/data_node.md_template
index b9503e904..aff98913c 100644
--- a/docs/refmans/gui/viselements/corelements/data_node.md_template
+++ b/docs/refmans/gui/viselements/corelements/data_node.md_template
@@ -45,8 +45,8 @@ To edit the data node, the user can click the line where the value is displayed:
Note that a 'Comment' field allows the user to explain why this value is changed. This information
is part of the history of the data node.
-When the new value is entered, the user presses the 'Apply' (✓) or the 'Cancel' (⨉) button to
-apply or cancel the change, respectively.
+When the new value is entered, the user presses the [MUI:CheckCircle] icon or the [MUI:Cancel] icon
+to apply or cancel the change, respectively.
### Tabular data
@@ -59,8 +59,8 @@ top-left corner of the 'Properties' section:
Data representation switch
-In the image above, the switch is set to the 'Table' mode.
-The other option is the 'Chart' mode.
+In the image above, the switch is set to the 'Table' mode ([MUI:TableChartOutlined] icon).
+The other option is the 'Chart' mode ([MUI:BarChartOutlined] icon).
Tabular data can be edited only in the 'Table' mode, as described in
[this section](#editing-tabular-data).
@@ -123,9 +123,9 @@ From this tab, you can access the attributes of the data node:
The Properties section
-The label of the data node can be changed using the 'Label' field: click in the value area, change
-the content, then press the 'Apply' button (with the ✓ icon.)
-To cancel the change, press the 'Cancel' button (with the ⨉ icon).
+The label of the data node can be changed using the 'Label' field: the user clicks in the value
+area, change the content, then presses the [MUI:CheckCircle] icon (Apply).
+The [MUI:Cancel] icon (Cancel) is used to cancel the change.
If the data node has has an owner (a scenario or a cycle) and if the [*show_owner*](#p-show_owner)
property is set to True (which is its default value), the label of the owning entity appears in the
@@ -135,20 +135,21 @@ property is set to True (which is its default value), the label of the owning en
The data node is owned by a scenario
-When the selected data node is owned by a scenario, a button is visible next to the scenario's
-label. This button can be pressed to display the list of the owning scenarios so the user can
-select one from there. As a result, any variable bound to the [*scenario*](#p-scenario) is set to
-the selected scenario entity: the application can use that to update other parts of the page from
+When the selected data node is owned by a scenario, the [MUI:Launch] icon is visible next to the
+scenario's label. This icon can be clicked to display the list of the owning scenarios so the user
+can select one from there. As a result, any variable bound to the [*scenario*](#p-scenario) is set
+to the selected scenario entity: the application can use that to update other parts of the page from
an `on_change` callback.
The section at the bottom lists the custom properties for the selected data node. This is visible
only if the [*show_properties*](#p-show_properties) property is set to True (which is its default
value).
The user can create new properties by clicking the 'New Property Key' line, providing a property
-name and value, and then pressing the 'Apply' button (with the ✓ icon.).
-The user can cancel the creation of a new property by pressing the 'Cancel' button (with the ⨉
-icon).
-The user can delete a property by selecting it and pressing the *trash* button.
+name and value, and then pressing the [MUI:CheckCircle] icon.
+The user can cancel the creation of a new property by pressing the [MUI:Cancel] icon.
+
+The user can delete a property by selecting it and then pressing the [MUI:DeleteOutline] icon
+(Delete).
## The 'History' tab
diff --git a/docs/refmans/gui/viselements/corelements/data_node_selector.md_template b/docs/refmans/gui/viselements/corelements/data_node_selector.md_template
index b50b595f0..37e6f430b 100644
--- a/docs/refmans/gui/viselements/corelements/data_node_selector.md_template
+++ b/docs/refmans/gui/viselements/corelements/data_node_selector.md_template
@@ -17,10 +17,8 @@ the selector might appear as follows:
Data nodes are organized in their owning scenario and cycle, when relevant.
In this example, *cleaned_dataset* and *initial_dataset* are scoped at the
[`Scope.GLOBAL`](../../../reference/pkg_taipy/pkg_common/pkg_config/Scope/index.md) level.
-Expanding the 'My scenario' item (by clicking the
-
-- Expand - icon) reveals data nodes scoped at
-[`Scope.SCENARIO`](../../../reference/pkg_taipy/pkg_common/pkg_config/Scope/index.md).
+Expanding the 'My scenario' item (by clicking the [MUI:ChevronRight] icon) reveals data nodes scoped
+at [`Scope.SCENARIO`](../../../reference/pkg_taipy/pkg_common/pkg_config/Scope/index.md).
When the user selects a data node, the [*on_change*](#p-on_change) callback is triggered allowing
the application to respond to the user's selection. The selected node's value is reflected in the
@@ -31,19 +29,17 @@ icons allow users to perform various actions:
Users can interact with the icons in the toolbar to perform various tasks:
--
- : Opens a filter configuration dialog, allowing users to define filters that apply to the list of
- data nodes.
-- :
- Enables users to specify sorting preferences for organizing the data nodes in the list.
--
- : Toggles the visibility of a text search box, which helps users locate specific data nodes
- quickly.
+- [MUI:FilterList]: Opens a filter configuration dialog, allowing users to define filters that apply
+ to the list of data nodes.
+- [MUI:SortByAlpha]: Enables users to specify sorting preferences for organizing the data nodes in
+ the list.
+- [MUI:SearchOutlined]: Toggles the visibility of a text search box, which helps users locate
+ specific data nodes quickly.
Filtering
@@ -52,16 +48,14 @@ criteria.
Opening the Filter dialog
-When the user presses the
-
-icon (Filter), the following dialog appears:
+When the user presses the [MUI:FilterList] icon (Filter), the following dialog appears:
-All the filters that are currently applied to the list of data nodes is shown. So far, it is
+All the filters that are currently applied to the list of data nodes are shown. So far, the list is
empty.
Creating a new filter
@@ -87,7 +81,7 @@ The available comparisons depend on the type of the selected field:
- Date: Options include "is on," "is not on," "is before," "is on or before," "is on or after" and
"is after." The value field accepts a date value.
-To list all data nodes with labels containing the string "eva", the user would configue a filter as
+To list all data nodes with labels containing the string "eva", the user would configure a filter as
follows:
-To add this filter to the filters list, the user must press the
-
-(Check) icon.
+To add this filter to the filters list, the user must press the [MUI:Check] icon (Check).
Multiple filters can be added as needed.
To close the filter configuration dialog, the user must click outside the dialog area.
@@ -109,15 +101,12 @@ Once filters are added, they are applied immediately. The filtered data node lis
Filters applied
-To remove a filter from the list, the user would open the filters dialog
-(
-icon), locate the criterion that must be removed and delete it pressing the
-
-(Delete) icon.
+To remove a filter from the list, the user would open the filters dialog ([MUI:FilterList] icon),
+locate the criterion that must be removed and delete it pressing the [MUI:Delete] icon (Delete).
Sorting
-The data node selector sorts data nodes by default in alphabetically ascending order based on their
+The Data Node Selector sorts data nodes by default in alphabetically ascending order based on their
labels. Grouping is applied according to their respective Cycle or Scenario, if applicable
Users can define custom sorting criteria to modify how data nodes are ordered. This process is
@@ -136,9 +125,7 @@ Consider the following initial list of data nodes displayed in the selector:
Initial data nodes list
-When the user presses the
-
-icon (Sort), the sorting configuration dialog appears:
+When the user presses the [MUI:SortByAlpha] icon (Sort), the sorting configuration dialog appears:
-After the sorting criterion is validated (pressing the
- icon),
-it is added to the sorting criteria list and the criterion is immediately applied to reorder the
-data nodes:
+After the sorting criterion is validated (pressing the [MUI:Check] icon), it is added to the sorting
+criteria list and the criterion is immediately applied to reorder the data nodes:
The user can then create additional sorting criteria (which are applied in sequence).
-To remove a sorting rule, the user can select the
- button
-next to the criterion.
+To remove a sorting rule, the user can select the [MUI:Delete] button next to the criterion.
To exit the sorting dialog, click anywhere outside the dialog window.
@@ -177,9 +160,8 @@ To exit the sorting dialog, click anywhere outside the dialog window.
The Search feature allows users to quickly locate data nodes by filtering the list based on text
input.
-When the user presses the
-
-icon (Search), a search box appears within the data node selector interface:
+When the user presses the [MUI:SearchOutlined] icon (Search), a search box appears within the Data
+Node Selector interface:
-To close the search box, the user must press the
-
-icon (Search off) in the toolbar.
+To close the search box, the user must press the [MUI:SearchOffOutlined] icon (Search off) in the
+toolbar.
Pinning
@@ -208,17 +189,16 @@ Assuming we are in the following situation:
If the user wants to focus only on the data node called *initial_dataset* and the data nodes from
-the scenario called 'Peter's', she can click the
-
-icon (Pin) next to these two items. Here is what the display would look like:
+the scenario called 'Peter's', she can click the [MUI:PushPinOutlined] icon (Pin) next to these two
+items. Here is what the display would look like:
Here is what the control looks like after the 'Pinned only' switch was set and the scenario item
@@ -239,6 +219,6 @@ Note that the cycle item is not pinned because the other scenarios it contains a
- *Pinning* a scenario item pins all its data nodes.
*Unpinning* a scenario item unpins all its data nodes.
- *Pinning* a cycle item pins all the data nodes across all its scenarios.
- *Unpinning* a cycle item unpins all the data nodes across all its scenarios.
+ *Unpinning* a cycle item unpins all the data nodes across all its scenarios.
Turn off the 'Pinned Only' switch to view the complete list of data nodes again.
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-create-d.png b/docs/refmans/gui/viselements/corelements/scenario_selector-create-d.png
index 4aad8a50b..945ee72cd 100644
Binary files a/docs/refmans/gui/viselements/corelements/scenario_selector-create-d.png and b/docs/refmans/gui/viselements/corelements/scenario_selector-create-d.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-create-l.png b/docs/refmans/gui/viselements/corelements/scenario_selector-create-l.png
index f2cfc2226..e496fc665 100644
Binary files a/docs/refmans/gui/viselements/corelements/scenario_selector-create-l.png and b/docs/refmans/gui/viselements/corelements/scenario_selector-create-l.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-filled-d.png b/docs/refmans/gui/viselements/corelements/scenario_selector-filled-d.png
deleted file mode 100644
index 54c3043ef..000000000
Binary files a/docs/refmans/gui/viselements/corelements/scenario_selector-filled-d.png and /dev/null differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-filled-l.png b/docs/refmans/gui/viselements/corelements/scenario_selector-filled-l.png
deleted file mode 100644
index 91a0ed792..000000000
Binary files a/docs/refmans/gui/viselements/corelements/scenario_selector-filled-l.png and /dev/null differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-filter1-d.png b/docs/refmans/gui/viselements/corelements/scenario_selector-filter1-d.png
new file mode 100644
index 000000000..f099912b3
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-filter1-d.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-filter1-l.png b/docs/refmans/gui/viselements/corelements/scenario_selector-filter1-l.png
new file mode 100644
index 000000000..072130ee3
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-filter1-l.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-filter2-d.png b/docs/refmans/gui/viselements/corelements/scenario_selector-filter2-d.png
new file mode 100644
index 000000000..3058d6bfa
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-filter2-d.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-filter2-l.png b/docs/refmans/gui/viselements/corelements/scenario_selector-filter2-l.png
new file mode 100644
index 000000000..da8f63b5b
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-filter2-l.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-filter3-d.png b/docs/refmans/gui/viselements/corelements/scenario_selector-filter3-d.png
new file mode 100644
index 000000000..7ac463df1
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-filter3-d.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-filter3-l.png b/docs/refmans/gui/viselements/corelements/scenario_selector-filter3-l.png
new file mode 100644
index 000000000..7b6602826
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-filter3-l.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-filter4-d.png b/docs/refmans/gui/viselements/corelements/scenario_selector-filter4-d.png
new file mode 100644
index 000000000..d4f6e77ac
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-filter4-d.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-filter4-l.png b/docs/refmans/gui/viselements/corelements/scenario_selector-filter4-l.png
new file mode 100644
index 000000000..67ce38ce0
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-filter4-l.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-filter5-d.png b/docs/refmans/gui/viselements/corelements/scenario_selector-filter5-d.png
new file mode 100644
index 000000000..cd4cfa9ab
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-filter5-d.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-filter5-l.png b/docs/refmans/gui/viselements/corelements/scenario_selector-filter5-l.png
new file mode 100644
index 000000000..53a749858
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-filter5-l.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-init1-d.png b/docs/refmans/gui/viselements/corelements/scenario_selector-init1-d.png
new file mode 100644
index 000000000..c888d1f4e
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-init1-d.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-init1-l.png b/docs/refmans/gui/viselements/corelements/scenario_selector-init1-l.png
new file mode 100644
index 000000000..002921af1
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-init1-l.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-search1-d.png b/docs/refmans/gui/viselements/corelements/scenario_selector-search1-d.png
new file mode 100644
index 000000000..3cb756962
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-search1-d.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-search1-l.png b/docs/refmans/gui/viselements/corelements/scenario_selector-search1-l.png
new file mode 100644
index 000000000..7bb4e3599
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-search1-l.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-search2-d.png b/docs/refmans/gui/viselements/corelements/scenario_selector-search2-d.png
new file mode 100644
index 000000000..dde619667
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-search2-d.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-search2-l.png b/docs/refmans/gui/viselements/corelements/scenario_selector-search2-l.png
new file mode 100644
index 000000000..3730fdb6d
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-search2-l.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-sort1-d.png b/docs/refmans/gui/viselements/corelements/scenario_selector-sort1-d.png
new file mode 100644
index 000000000..9b0646b5d
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-sort1-d.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-sort1-l.png b/docs/refmans/gui/viselements/corelements/scenario_selector-sort1-l.png
new file mode 100644
index 000000000..834acb4ec
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-sort1-l.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-sort2-d.png b/docs/refmans/gui/viselements/corelements/scenario_selector-sort2-d.png
new file mode 100644
index 000000000..eea6d13aa
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-sort2-d.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-sort2-l.png b/docs/refmans/gui/viselements/corelements/scenario_selector-sort2-l.png
new file mode 100644
index 000000000..d3b838211
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-sort2-l.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-sort3-d.png b/docs/refmans/gui/viselements/corelements/scenario_selector-sort3-d.png
new file mode 100644
index 000000000..2854fcd64
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-sort3-d.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-sort3-l.png b/docs/refmans/gui/viselements/corelements/scenario_selector-sort3-l.png
new file mode 100644
index 000000000..e6df98c09
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-sort3-l.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-sort4-d.png b/docs/refmans/gui/viselements/corelements/scenario_selector-sort4-d.png
new file mode 100644
index 000000000..d7e3ac787
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-sort4-d.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector-sort4-l.png b/docs/refmans/gui/viselements/corelements/scenario_selector-sort4-l.png
new file mode 100644
index 000000000..999de5d97
Binary files /dev/null and b/docs/refmans/gui/viselements/corelements/scenario_selector-sort4-l.png differ
diff --git a/docs/refmans/gui/viselements/corelements/scenario_selector.md_template b/docs/refmans/gui/viselements/corelements/scenario_selector.md_template
index 808ba1ad7..c35e3dc07 100644
--- a/docs/refmans/gui/viselements/corelements/scenario_selector.md_template
+++ b/docs/refmans/gui/viselements/corelements/scenario_selector.md_template
@@ -1,55 +1,80 @@
Select scenarios from the list of all scenario entities.
-The scenario selector shows all the scenario entities handled by Taipy and lets the user
+The Scenario Selector shows all the scenario entities handled by Taipy and lets the user
select a scenario from a list, create new scenarios or edit existing scenarios.
# Details
-The scenario selector displays a tree selector where scenarios are grouped based on their cycle
-(if the property [*display_cycles*](#p-display_cycles) has not been set to False).
-If the [*show_primary_flag*](#p-show_primary_flag) property has not been forced to False, the
-label of the primary scenario are overlaid with a small visual hint that lets users spot them
-immediately.
+The Scenario Selector is a visual control that displays a tree structure where scenarios are grouped
+by their respective cycles (provided the [*display_cycles*](#p-display_cycles) property is not set
+to False).
-If no created scenario has been created yet, the tree selector will appear empty. The default
-behavior, controlled by the [*show_add_button*](#p-show_add_button) property, is to display a
-button letting users create new scenarios:
+Here is an example of a populated Scenario Selector:
-When the user presses that button, a form appears so that the settings of the new scenario can be
-set:
+In the example above, we observe several created scenarios, including some organized within a cycle,
+as indicated by the
+
+icon.
+
+Additionally, because the [*show_primary_flag*](#p-show_primary_flag) property is enabled by default
+(not explicitly set to False), the primary scenario within each cycle is marked with a
+[MUI:FlagOutlined] icon, allowing users to identify it at a glance.
+
+At the top of the control, a toolbar provides a few icons that let users perform various tasks:
+
+- [MUI:FilterList]: Opens a filter configuration dialog, allowing users to define filters that apply
+ to the list of scenarios. This is described in the [Filtering](#filtering) section.
+- [MUI:SortByAlpha]: Enables users to specify sorting preferences for organizing the scenarios in
+ the list. This is described in the [Sorting](#sorting) section.
+- [MUI:SearchOutlined]: Toggles the visibility of a text search box, which helps users locate
+ specific scenarios quickly. This is described in the [Searching](#searching) section.
+
+## Creating new scenarios
+
+If no scenarios have been created yet, the tree selector will appear empty. By default, a button
+for creating new scenarios is displayed, controlled by the [*show_add_button*](#p-show_add_button)
+property:
-In this form, the user must indicate which scenario configuration should be used and specify the
-scenario creation date.
-Custom properties can also be added to the new scenario by pressing the '+' button located on the
-right side of the property key and value fields.
-
-When several new scenarios are created, the scenario selector will list all the scenarios,
-potentially grouped in their relevant cycle:
+When the 'Add Scenario' button is clicked, a dialog appears, allowing users to configure the new
+scenario's settings:
-Notice how the primary scenario for a cycle is immediately flagged as "primary" (you may choose
-not to show that icon by setting the [*show_primary_flag*](#p-show_primary_flag) property to
-False).
+In the dialog, users must specify:
+
+- The scenario configuration to use,
+- The scenario creation date,
+- The label for the new scenario.
+
+Additionally, custom properties can be added to the scenario by clicking the [MUI:Add] button
+(Add) located to the right of the property key and value fields.
+
+After configuring the necessary details:
+
+- Clicking the 'Create' button will create the scenario and add it to the Scenario Selector list.
+- Clicking the 'Cancel' button will close the dialog without saving any changes.
## Editing a scenario
-Users can press the pencil icon located next to the scenario labels. When that happens, a dialog
-box similar to the scenario creation dialog is displayed to let users modify the scenario
-settings.
+To edit an existing scenario, users can click the [MUI:Edit] icon (Edit) located next to the
+scenario item in the tree. This action opens a dialog box similar to the scenario creation dialog,
+allowing users to modify scenario settings.
Here is how this dialog box looks like:
-The user can change the scenario label and custom properties then press the 'Apply' button to
-propagate the changes.
-To add a new custom property, the user has to fill the 'Key' and 'Value' fields in the 'Custom
-Properties' section, then press the '+' button.
-A custom property can be removed by pressing the trash button next to it.
+In the dialog, users can:
-The 'Cancel' button closes the dialog without changing anything.
-The 'Delete' button deletes the edited scenario.
+
+
Modify the Scenario Label,
+
Add custom properties by filling in the 'Key' and 'Value' fields in the 'Custom Properties'
+ section then pressing the [MUI:Add] icon (Add),
+
Remove custom properties by clicking the [MUI:DeleteOutline] icon (Delete) next to an existing
+ custom property.
+
-Note that there is no way to change the scenario configuration or its creation date.
+Note that the scenario configuration and creation date cannot be modified.
+
+The 'Apply' button saves and propagates changes to the scenario.
+The 'Cancel' button closes the dialog without making any changes.
+The 'Delete' button permanently deletes the scenario.
## Selecting a scenario
@@ -77,6 +107,157 @@ application can then use the selected value.
If no scenario is selected, [*value*](#p-value) is set no None.
+## Filtering
+
+The Filter functionality allows users to refine the list of scenarios by applying one or more
+criteria.
+
+Here is the Scenario Selector list that we will work with to demonstrate this functionality.
+
+
+
+
Opening the Filter dialog
+
+When the user presses the [MUI:FilterList] icon (Filter), the following dialog appears:
+
+
+All the filters that are currently applied to the list of scenarios are shown. So far, the list is
+empty.
+
+
Creating a new filter criteria
+
+To create a new filter, the user must select a filtering criterion from the dropdown menu in the
+first field:
+
+
+After selecting a criterion, the 'Action' dropdown updates with comparison options specific to the
+selected criterion.
+The available comparisons depend on the type of the selected field:
+
+- Boolean: Options include "is" and "is not." The value field accepts True or False.
+- String: Options include "is," "is not," and "contains." The value field accepts a text
+ value.
+- Number: Options include "equals," "does not equal," "is less than," "is less than or equal to,"
+ "is greater than or equal to" and "is greater than." The value field accepts a numerical
+ value.
+- Date: Options include "is on," "is not on," "is before," "is on or before," "is on or after" and
+ "is after." The value field accepts a date value.
+
+To list all data nodes with labels containing the string "ina", the user would configure a filter as
+follows:
+
+
+To add this filter to the filters list, the user must press the [MUI:Check] (Check) icon.
+Multiple filters can be added as needed.
+
+To close the filter configuration dialog, the user must click outside the dialog area.
+
+Once filters are added, they are applied immediately. The filtered scenario list appears as shown:
+
+
+You can see that only scenarios with matching label now appear in the list.
+The [MUI:FilterList] icon (Filter) in the control's tool bar has an overlay icon indicating that
+a filter with one criterion is currently applied.
+
+
Removing a filter criteria
+
+To remove a filter from the list, the user would open the filters dialog ([MUI:FilterList] icon),
+locate the criterion that must be removed and delete it pressing the [MUI:Delete] icon (Delete).
+
+## Sorting
+
+By default, the Scenario Selector sorts scenarios in ascending order by their creation date. When
+applicable, scenarios are grouped by their respective Cycle.
+
+Users can customize the sorting order by defining custom sorting criteria, similar to the process of
+filtering. This allows users to create a prioritized sequence of sorting rules.
+
+Each sorting criterion specifies:
+
+- The scenario property to sort by (e.g., label, creation date).
+- The sorting direction, either *ascending* ("asc") or *descending* ("desc").
+
+Consider the following initial list of scenarios displayed in the selector:
+
+
+Users can click the [MUI:SortByAlpha] icon (Sort) to open the sorting configuration dialog:
+
+
+Users can select a property from the dropdown and specify the sorting order: 'asc' (Ascending) or
+'desc' (Descending).
+
+In the following image, the user has selected the 'Label' property with a descending sort:
+
+
+After the sorting criterion is validated (pressing the [MUI:Check] icon), it is added to the sorting
+rules. The scenarios are immediately reordered according to the applied criteria:
+
+
+Note the overlay indicator on top of the [MUI:SortByAlpha] icon, indicating that a sorting criterion
+is currently applied.
+
+Additional sorting rules can be created, applied sequentially in the specified order of
+priority.
+To remove a sorting rule, the user can click the [MUI:Delete] button next to the criterion.
+
+To exit the sorting configuration window, users must click anywhere outside the dialog window.
+
+## Searching
+
+The Search feature allows users to quickly locate scenarios by filtering the list based on text input.
+
+When the user presses the [MUI:SearchOutlined] icon (Search), a search box appears within the Scenario Selector interface:
+
+
+If text is entered in the search box, the list dynamically updates to display only scenarios with labels containing the entered text:
+
+
+To close the search box, the user must press the [MUI:SearchOffOutlined] icon (Search off) in the toolbar.
+
# Usage
## Customizing the creation
@@ -165,7 +346,7 @@ not the case, an error message is returned to the user for correction.
Finally, lines 20-22 take care of creating the scenario with the new settings.
This scenario is returned by the callback function to let Taipy know it was created properly.
-The scenario selector control definition needs to have the [*on_creation*](#p-on_creation) property
+The Scenario Selector control definition needs to have the [*on_creation*](#p-on_creation) property
set to the function:
!!! taipy-element
default={scenario}
diff --git a/docs/refmans/gui/viselements/generic/metric.md_template b/docs/refmans/gui/viselements/generic/metric.md_template
index 6b9f30528..a5ff03a7b 100644
--- a/docs/refmans/gui/viselements/generic/metric.md_template
+++ b/docs/refmans/gui/viselements/generic/metric.md_template
@@ -113,7 +113,7 @@ This definition produces the following output that anyone can interpret on the s
Hiding the value
-## Formating the values {data-source="gui:doc/examples/controls/metric_formats.py"}
+## Formatting the values {data-source="gui:doc/examples/controls/metric_formats.py"}
The numerical value set to the properties [*value*](#p-value) and [*delta*](#p-delta) can be
displayed using a formatting string set to the respective properties [*format*](#p-format) and
@@ -137,7 +137,12 @@ Here is the control definition:
delta_format=%d %%
The syntax for the [*format*](#p-format) and [*delta_format*](#p-delta_format) properties is the one
-used by the native `sprintf()` function. In this case, values are both integers.
+used by the native `sprintf()` function. In this case, values are both integers.
+Note that in this example, we want the *delta* value to appear with a percent sign suffix. To get
+this result, you'll need to use the double percent symbol (%%) in the value for the
+[*delta_format*](#p-delta_format) property. In `sprintf()`, a single percent symbol is used for
+specifying format specifiers, so to actually print a literal percent sign, you need to escape it
+with another percent symbol.
Here is how the control displays:
@@ -198,7 +203,7 @@ that provide information. The [*color_map*](#p-color_map) property lets you do j
Here is an example where this is relevant: our `metric` control should display a color depending
on its wavelength.
-Here is the definition of the colormap assigning color names to wavelength ranges:
+Here is the definition of the color map assigning color names to wavelength ranges:
```python
color_wl = 530
color_map = {
@@ -216,7 +221,7 @@ color_map = {
*color_wl* represents the wavelength of the color we want to represent.
-We can use the colormap stored in *color_map* in the control definition:
+We can use the color map stored in *color_map* in the control definition:
!!! taipy-element
default={color_wl}
color_map={color_map}
diff --git a/docs/refmans/gui/viselements/generic/table.md_template b/docs/refmans/gui/viselements/generic/table.md_template
index 6fe2ea5b8..992ffaa74 100644
--- a/docs/refmans/gui/viselements/generic/table.md_template
+++ b/docs/refmans/gui/viselements/generic/table.md_template
@@ -21,7 +21,7 @@ The supported types for the [*data*](#p-data) property are:
- A list of lists of values:
All the lists must be the same length. The table control creates one row for each list in the
collection.
-- A Numpy series:
+- A NumPy series:
Taipy internally builds a Pandas DataFrame with the provided *data*.
!!! note "Polars data types support"
@@ -272,16 +272,16 @@ These elements let users:
The presence of these UI elements depends on the control configuration:
-- 'Add' icon ('🞣'):
+- [MUI:Add] icon (Add):
- Appears in the top-left corner of the table when [*editable*](#p-editable) is set to True and
[*on_add*](#p-on_add) is not set to False.
- Clicking this icon can add new rows to the table, as described in
[this section](#adding-a-row).
-- 'Delete' icon ('🗑'):
+- [MUI:Delete] icon (Delete):
- Appears to the left of each row when [*editable*](#p-editable) is set to True and
[*on_delete*](#p-on_delete) is not set to False.
- Clicking this icon can remove rows, as explained in the [this section](#deleting-a-row).
-- 'Edit' icon ('✎'):
+- [MUI:Edit] icon (Edit):
- Appears to the right of each cell if [*editable*](#p-editable) is set to True.
- It also appears to the right of each cell in a specific column ("column_name") if
[*editable[column_name]*](#p-editable[column_name]) is set to True, even when
@@ -297,8 +297,8 @@ leverage them to fit the needs of your application.
When a table control is editable (i.e., either the [*editable*](#p-editable) property is set to True
or the [*editable[]*](#p-editable[column_name]) property is True for any of the table's columns),
-**and** the [*on_add*](#p-on_add) property is **not** set to False, an 'Add' icon ('🞣') appears in
-the top-left corner of the table:
+**and** the [*on_add*](#p-on_add) property is **not** set to False, an [MUI:Add] icon (Add) appears
+in the top-left corner of the table:
-If the user clicks the *edit* button next to the masked password, the text is revealed, showing the
-actual password in plain text. This allows the user to view or modify the password, but only when
-explicitly editing the field.
+If the user clicks the [MUI:Edit] icon (Edit) next to the masked password, the text is revealed,
+showing the actual password in plain text. This allows the user to view or modify the password, but
+only when explicitly editing the field.
Here is what the user can see in this situation:
@@ -895,8 +896,8 @@ When the user attempts to edit a cell in the "Continent" column, it will look li
Editing an enumerated value
-A button will appear in the cell, which when clicked, displays a drop-down list of predefined values
-that the user can select from:
+A [MUI:ArrowDropDown] icon (Drop down) appears in the cell, which when clicked, displays a drop-down
+list of predefined values that the user can select from:
@@ -912,10 +913,10 @@ changes.
This enables you to execute specific actions based on user modifications.
You can achieve this by setting the [*on_add*](#p-on_add), [*on_delete*](#p-on_delete), or
-[*on_edit*](#p-on_edit) on_edit properties. Below is an example that demonstrates this
+[*on_edit*](#p-on_edit) on_edit properties.
+Below is an example that demonstrates this
capability:
-
Suppose we are creating an application that displays how a user's assets are distributed across
various categories. The table would list each category alongside the allocated amount. The purpose
of the application is to show the percentage of each allocation relative to the user's total assets
@@ -987,8 +988,8 @@ In line 4, the "%" column is updated by recalculating the ratio of all categorie
Finally, line 5 refreshes the *var_name* variable (set to "data") to update the display.
The requirements have now been implemented. Now, let's see how this works.
-Assume the user enters a new value in a cell in the "Amount" column by pressing the 'Edit' icon
-next to the cell:
+Assume the user enters a new value in a cell in the "Amount" column by pressing the [MUI:Edit] icon
+(Edit) next to the cell:
@@ -996,9 +997,9 @@ next to the cell:
Now, suppose the user changes the value from 190,000 to 80,000 and confirms the change by pressing
-the '✓' icon. The [*on_edit*](#p-on_edit) setting triggers the invocation of the *update()*
-function, which in turn calls *compute_ratio()* to recalculate all ratios. Once the new ratios are
-stored in the "%" column, the page is refreshed to display the updated results:
+the [MUI:Check] icon (Apply). The [*on_edit*](#p-on_edit) setting triggers the invocation of the
+*update()* function, which in turn calls *compute_ratio()* to recalculate all ratios. Once the new
+ratios are stored in the "%" column, the page is refreshed to display the updated results:
@@ -1029,10 +1030,10 @@ application are as follows:
The application interface is a table that displays the list of employees, with a column showing the
proposed salary for each.
-- The user can remove a candidate by pressing the "Delete" button in the corresponding row, but the
- list must contain at least three employees.
-- The user can add a new candidate to the list by pressing the "Add" button, but no more than six
- employees can be added.
+- The user can remove a candidate by pressing the [MUI:Delete] icon (Delete) in the corresponding
+ row, but the list must contain at least three employees.
+- The user can add a new candidate to the list by pressing the [MUI:Add] icon (Add), but no more
+ than six employees can be added.
- The user can edit any employee's salary, and the final amount is rounded to the nearest multiple
of $500.
@@ -1106,9 +1107,9 @@ def check_delete(state: State, var_name: str, payload: dict):
*check_delete()* since there is no need to modify them before forwarding the request.
Starting with the initial state (where four candidates are in the list), a row can be deleted by
-clicking the 'Delete' icon ('🗑') on the corresponding row, and then confirming the action by
-pressing the '✓' icon.
-Here is what the tale look like after removing the third line:
+clicking the [MUI:Delete] icon (Delete) on the corresponding row, and then confirming the action by
+pressing the [MUI:Check] icon (Apply).
+Here is what the table look like after removing the third line:
@@ -1150,7 +1151,7 @@ def check_add(state: State, var_name: str, payload: dict):
the values for the new row, converted to a `list`.
Assuming the application is in its initial state with four candidates, you can add a row by clicking
-the '🞣' icon at the top of the table control.
+the [MUI:Add] icon (Add) at the top of the table control.
The result is displayed like this:
@@ -1174,7 +1175,7 @@ be rounded to the nearest multiple of 500.
Here is the implementation of the *force_salary()* function, which is assigned to the
[*on_edit*](#p-on_edit) property of the table control in this example:
-```python
+```python linenums="1"
def force_salary(state: State, var_name: str, payload: dict):
proposed_salary = payload["value"]
proposed_salary = round(proposed_salary / 500) * 500
@@ -1185,11 +1186,11 @@ def force_salary(state: State, var_name: str, payload: dict):
Let's explain what this function does line by line:
- Line 2: Retrieves the value entered by the user from the *payload* dictionary, sent by the
- `on_edit` callback.
+ `on_edit` callback.
- Line 3: Rounds the value to the nearest multiple of 500, satisfying the constraint, and assign the
- result to *proposed_salary*.
-- Line 4: Updates the "value" entry of the *payload* dictionary with the rounded salary to ensure the
- correct value is stored in the dataset.
+ result to *proposed_salary*.
+- Line 4: Updates the "value" entry of the *payload* dictionary with the rounded salary to ensure
+ the correct value is stored in the dataset.
- Line 5: Delegate the update of the dataset and the control by calling `(Gui.)table_on_edit()^`.
Assume the user enters a random salary value for a candidate:
@@ -1201,9 +1202,9 @@ Assume the user enters a random salary value for a candidate:
Obviously, the proposed value (963852) does not meet the constraint.
-After the user validates the input (clicking the '✓' icon), the *force_salary()* function is invoked
-to automatically adjusts the salary to the nearest multiple of 500, and the updated value is
-displayed:
+After the user validates the input (clicking the [MUI:Check] icon (Apply)), the *force_salary()*
+function is invoked to automatically adjusts the salary to the nearest multiple of 500, and the
+updated value is displayed:
@@ -1361,7 +1362,7 @@ We only use the second parameter since, in this straightforward case, we do not
Based on the row index (received in *index*), this function returns the name of the CSS class to
apply to the row: "blue-row" if the index is odd, "red-row" if it is even.
-We need to define what these class define. This is done in a CSS stylesheet, where the following
+We need to define what these class define. This is done in a CSS style sheet, where the following
CSS content would appear:
```css
.blue-row>td {
@@ -1500,7 +1501,7 @@ Here are the CSS classes that this application will use:
}
```
-- The "redish" and "light-reddish" classes (lines 1 to 13) will apply to the "x" column. The
+- The "reddish" and "light-reddish" classes (lines 1 to 13) will apply to the "x" column. The
"reddish" class has a strong red background and uses white text, while the "light-reddish" class
uses a lighter red background with black text and bold typeface.
- The "greenish" and "light-greenish" classes (lines 15 to 28) will apply to the "y" column. The
diff --git a/docs/refmans/gui/viselements/mui-icons.svg b/docs/refmans/gui/viselements/mui-icons.svg
deleted file mode 100644
index f9e39a220..000000000
--- a/docs/refmans/gui/viselements/mui-icons.svg
+++ /dev/null
@@ -1,66 +0,0 @@
-
-
diff --git a/docs/release-notes/index.md b/docs/release-notes/index.md
index 278647b29..1b6b41828 100644
--- a/docs/release-notes/index.md
+++ b/docs/release-notes/index.md
@@ -31,7 +31,7 @@ Published on 2024-10.
!!! warning "Upgrading to Taipy 4.0 from 3.x"
- Due to changes in Taipy’s package structure in version 4.0, the previous version of the
+ Due to changes in Taipy's package structure in version 4.0, the previous version of the
`taipy-config` package may not be automatically removed during the upgrade process. This could
lead to runtime issues as the system may attempt to reference outdated dependencies.
@@ -49,9 +49,18 @@ following new functionalities:
- Multiple selection is now available.
See the [*multiple*](../refmans/gui/viselements/corelements/scenario_selector.md#p-multiple)
property for more details.
- - A filter capability has been added to the scenario selector: TODO add details
- - A sort capability has been added to the scenario selector: TODO add details
- - A search capability has been added to the scenario selector: TODO add details
+ - Users can now filter scenarios in the list.
+ See the
+ [section on Filtering](../refmans/gui/viselements/corelements/scenario_selector.md#filtering)
+ for more details.
+ - Users can now sort scenarios in the list.
+ See the
+ [section on Sorting](../refmans/gui/viselements/corelements/scenario_selector.md#sorting) for
+ more details.
+ - Users can now search scenarios in the list
+ See the
+ [section on Searching](../refmans/gui/viselements/corelements/scenario_selector.md#searching)
+ for more details.
- [*Data Node Selector*](../refmans/gui/viselements/corelements/data_node_selector.md):
- Multiple selection is now available.
See the [*multiple*](../refmans/gui/viselements/corelements/data_node_selector.md#p-multiple)
diff --git a/docs/userman/gui/extension/dynamic_element/tabular_data_props.md b/docs/userman/gui/extension/dynamic_element/tabular_data_props.md
new file mode 100644
index 000000000..043b6e681
--- /dev/null
+++ b/docs/userman/gui/extension/dynamic_element/tabular_data_props.md
@@ -0,0 +1,228 @@
+The previous section on [Scalar properties](scalar_props.md) shows how to create a dynamic element
+that holds a scalar value. However, when dealing with collections of data, such as tabular data, we
+need a more complex approach to support arrays or tables that can be dynamically updated.
+
+In Taipy GUI, tabular data can also be bound to Python variables or expressions, allowing the user
+interface to instantly reflect any changes in the underlying data. Custom elements that manage
+tabular data must declare their properties to specify the type of data they support, similar to
+scalar properties, but now using a structure suitable for collections.
+This is handled by the PropertyType class, where you can define the type as an array or table
+format, enabling the binding of multidimensional data.
+
+For example, the *data* property of a `table` control can be bound to a two-dimensional array or a
+list of objects. When the bound data changes, the table control automatically updates to reflect the
+new content in the user interface.
+This functionality is implemented using the React library, which dynamically generates HTML for the
+tabular display. By leveraging Taipy GUI's variable binding capabilities, developers can efficiently
+update and manage tabular data in the user interface.
+
+Even if a custom element does not need to update its tabular data dynamically, it can still be
+implemented as a dynamic element to take advantage of the expressivity and flexibility offered by
+this approach.
+
+# Using tabular data
+
+In this section, we will expand the dynamic element library, initially created in the
+[Scalar properties](scalar_props.md) section, by adding a new dynamic custom element.
+
+This dynamic element will accept a property containing tabular data and display it within a table.
+When a Python variable is bound to this property, updates to the variable will immediately reflect
+in the table content shown on the page, ensuring real-time synchronization.
+
+## Declaring a dynamic element {data-source="gui/extension/example_library/example_library.py#L36"}
+Starting from the code mentioned above, here is how you would declare this new element:
+```py title="example_library.py"
+from taipy.gui.extension import Element, ElementLibrary, ElementProperty, PropertyType
+
+class ExampleLibrary(ElementLibrary):
+ def __init__(self) -> None:
+ # Initialize the set of visual elements for this extension library
+ self.elements = {
+ "game_table": Element(
+ "data",
+ {
+ "data": ElementProperty(PropertyType.data),
+ },
+ # The name of the React component (GameTable) that implements this custom
+ # element, exported as GameTable in front-end/src/index.ts
+ # react_component="GameTable",
+ ),
+ }
+```
+The declaration of this dynamic element is very similar to what we created in the
+[Scalar properties](scalar_props.md).
+
+The detailed explanation of the code is as follows:
+
+- The *game_table* element includes a single property: *data*.
+- The *data* property has the type *PropertyType.data*, meaning it holds a data value and is
+ dynamic.
+- The *get_name()* method in the *ExampleLibrary* class returns the name of the library as a string.
+ This name is used to identify the library within the Taipy GUI framework.
+- The *get_elements()* method in the *ExampleLibrary* class returns a dictionary of all elements
+ that are part of this library. Each element is defined with its properties and associated React
+ component.
+
+## Creating the React component {data-source="gui/extension/example_library/front-end/src/GameTable.tsx"}
+
+The React component for the *game_table* element is defined as follows:
+
+```jsx title="GameTable.tsx" linenums="1"
+import React, { useEffect, useMemo, useState } from "react";
+import {
+ createRequestDataUpdateAction,
+ useDispatch,
+ useDispatchRequestUpdateOnFirstRender,
+ useModule,
+ TaipyDynamicProps,
+ TableValueType,
+ RowType,
+ RowValue,
+} from "taipy-gui";
+
+interface GameTableProps extends TaipyDynamicProps {
+ data: TableValueType;
+}
+
+const pageKey = "no-page-key";
+
+const GameTable = (props: GameTableProps) => {
+ const { data, updateVarName = "", updateVars = "", id } = props;
+ const [value, setValue] = useState>>({});
+ const dispatch = useDispatch();
+ const module = useModule();
+ const refresh = data?.__taipy_refresh !== undefined;
+ useDispatchRequestUpdateOnFirstRender(dispatch, id, module, updateVars);
+
+ const colsOrder = useMemo(() => {
+ return Object.keys(value);
+ }, [value]);
+
+ const rows = useMemo(() => {
+ const rows: RowType[] = [];
+ if (value) {
+ Object.entries(value).forEach(([col, colValues]) => {
+ colValues.forEach((val, idx) => {
+ rows[idx] = rows[idx] || {};
+ rows[idx][col] = val;
+ });
+ });
+ }
+ return rows;
+ }, [value]);
+
+ useEffect(() => {
+ if (refresh || !data || data[pageKey] === undefined) {
+ dispatch(
+ createRequestDataUpdateAction(
+ updateVarName,
+ id,
+ module,
+ colsOrder,
+ pageKey,
+ {},
+ true,
+ "ExampleLibrary",
+ ),
+ );
+ } else {
+ setValue(data[pageKey]);
+ }
+ }, [refresh, data, colsOrder, updateVarName, id, dispatch, module]);
+
+ return (
+
+
+
+ {rows.map((row, index) => (
+
+ {colsOrder.map((col, cidx) => (
+
{row[col]}
+ ))}
+
+ ))}
+
+
+
+ );
+};
+
+export default GameTable;
+```
+
+The detailed explanation of the code is as follows:
+
+- We use the [`useDispatch()`](../../../../refmans/reference_guiext/functions/useDispatch.md) hook
+ to dispatch actions to the store and initiate backend communications.
+- Additionally, the [`useModule()`](../../../../refmans/reference_guiext/functions/useModule.md)
+ hook retrieves the page module, enabling correct execution of backend functions.
+- To request an update for every dynamic property of an element on initial render, we use the
+ [`useDispatchRequestUpdateOnFirstRender()`](../../../../refmans/reference_guiext/functions/useDispatchRequestUpdateOnFirstRender.md)
+ hook provided by the Taipy GUI Extension API. This hook takes five parameters:
+ - *dispatch*: The React dispatcher associated with the context.
+ - *id*: The identifier of the element.
+ - *context*: The execution context.
+ - *updateVars*: The content of the *updateVars* property.
+- We also dispatch the
+ [`createRequestDataUpdateAction()`](../../../../refmans/reference_guiext/functions/createRequestDataUpdateAction.md)
+ hook to create a request data update action, which updates the context by invoking the
+ `(ElementLibrary.)get_data()^` method of the backend library. This invocation triggers an update
+ of front-end elements holding the data.
+
+The [`createRequestDataUpdateAction()`](../../../../refmans/reference_guiext/functions/createRequestUpdateAction.md)
+hook accepts eight parameters:
+
+- *name*: The name of the variable containing the requested data, as received in the property.
+- *id*: The identifier of the visual element.
+- *context*: The execution context.
+- *columns*: The list of column names required by the element emitting this action.
+- *pageKey*: The unique identifier for the data received from this action.
+- *payload*: The payload, specific to the component type (e.g., table, chart).
+- *allData*: A flag indicating if all the data is requested.
+- *library*: The name of the extension library.
+
+## Exporting the React component {data-source="gui/extension/example_library/front-end/src/index.ts"}
+
+When the component is entirely defined, it must be exported by the JavaScript library.
+This is done by adding the export directive in the file */front-end/src/index.ts*.
+
+```js title="index.ts"
+import GameTable from "./GameTable";
+
+export { GameTable };
+```
+
+## Using the element {data-source="gui/extension/table_chess_game.py"}
+
+In the example below, we use the *game_table* element to display a chess game board.
+The data is represented as a two-dimensional list of strings, where each string represents a chess
+piece.
+The board is displayed in a table format using the *game_table* element.
+We can see how the data property of the control is bound to the Python variable *data*, using the
+default property syntax.
+
+```py
+data = [
+ ["♜", "♞", "♝", "♛", "♚", "♝", "♞", "♜"]
+ ["♟", "♟", "♟", "♟", "♟", "♟", "♟", "♟"],
+ ["", "", "", "", "", "", "", ""],
+ ["", "", "", "", "", "", "", ""],
+ ["", "", "", "", "", "", "", ""],
+ ["", "", "", "", "", "", "", ""],
+ ["♙", "♙", "♙", "♙", "♙", "♙", "♙", "♙"],
+ ["♖", "♘", "♗", "♕", "♔", "♗", "♘", "♖"],
+]
+
+page = """
+## Chess Game
+<|{data}|example.game_table|>
+"""
+```
+
+When you run this application, the page displays the element like this:
+
+
+
+
+ Chessboard
+
diff --git a/docs/userman/gui/extension/extension_data.md b/docs/userman/gui/extension/extension_data.md
index d4cba5df1..adb7694bd 100644
--- a/docs/userman/gui/extension/extension_data.md
+++ b/docs/userman/gui/extension/extension_data.md
@@ -1,10 +1,211 @@
# Using tabular data
-
-!!! warning "Work in Progress"
- This section still requires significant work, which is in progress.
- At this time, Taipy GUI provides a custom element library example
- with lengthy explanations on how to build it.
- Please look into the `doc/extension` directory where Taipy GUI is
- installed for more information.
- You can also look at this example directly on
- [GitHub](https://github.com/Avaiga/taipy/tree/[BRANCH]/doc/gui/extension).
+
+In this section, we will expand the custom element library, initially created in the Static Elements section, by
+adding a dynamic custom element.
+
+This dynamic element will accept a property containing tabular data and display it within a table. When a Python
+variable is bound to this property, updates to the variable will immediately reflect in the table content shown on
+the front end, ensuring real-time synchronization.
+
+## Declaring a dynamic element {data-source="gui:doc/extension/example_library/example_library.py#L36"}
+
+```py
+from taipy.gui.extension import Element, ElementLibrary, ElementProperty, PropertyType
+
+class ExampleLibrary(ElementLibrary):
+ def __init__(self) -> None:
+ # Initialize the set of visual elements for this extension library
+ self.elements = {
+ "game_table": Element(
+ "data",
+ {
+ "data": ElementProperty(PropertyType.data),
+ },
+ # The name of the React component (GameTable) that implements this custom
+ # element, exported as GameTable in front-end/src/index.ts
+ # react_component="GameTable",
+ ),
+ }
+ def get_name(self) -> str:
+ return "example"
+
+ def get_elements(self) -> dict:
+ return self.elements
+
+ def get_scripts(self) -> list[str]:
+ # Only one JavaScript bundle for this library.
+ return ["front-end/dist/exampleLibrary.js"]
+```
+
+The detailed explanation of the code is as follows:
+
+- The `game_table` element includes a single property: `data`.
+- The `data` property has the type `PropertyType.data`, meaning it holds a data value and is dynamic.
+- The `get_name` method in the `ExampleLibrary` class returns the name of the library as a string. This name is used
+ to identify the library within the Taipy GUI framework.
+- The `get_elements` method in the `ExampleLibrary` class returns a dictionary of elements that are part of this
+ library. Each element is defined with its properties and associated React component.
+
+## Creating the React component {data-source="gui:doc/extension/example_library/front-end/src/GameTable.tsx"}
+
+The React component for the `game_table` element is defined as follows:
+
+```jsx
+import React, { useEffect, useMemo, useState } from "react";
+import {
+ createRequestDataUpdateAction,
+ useDispatch,
+ useDispatchRequestUpdateOnFirstRender,
+ useModule,
+ TaipyDynamicProps,
+ TableValueType,
+ RowType,
+ RowValue,
+} from "taipy-gui";
+
+interface GameTableProps extends TaipyDynamicProps {
+ data: TableValueType;
+}
+
+const pageKey = "no-page-key";
+
+const GameTable = (props: GameTableProps) => {
+ const { data, updateVarName = "", updateVars = "", id } = props;
+ const [value, setValue] = useState>>({});
+ const dispatch = useDispatch();
+ const module = useModule();
+ const refresh = data?.__taipy_refresh !== undefined;
+ useDispatchRequestUpdateOnFirstRender(dispatch, id, module, updateVars);
+
+ const colsOrder = useMemo(() => {
+ return Object.keys(value);
+ }, [value]);
+
+ const rows = useMemo(() => {
+ const rows: RowType[] = [];
+ if (value) {
+ Object.entries(value).forEach(([col, colValues]) => {
+ colValues.forEach((val, idx) => {
+ rows[idx] = rows[idx] || {};
+ rows[idx][col] = val;
+ });
+ });
+ }
+ return rows;
+ }, [value]);
+
+ useEffect(() => {
+ if (refresh || !data || data[pageKey] === undefined) {
+ dispatch(
+ createRequestDataUpdateAction(
+ updateVarName,
+ id,
+ module,
+ colsOrder,
+ pageKey,
+ {},
+ true,
+ "ExampleLibrary",
+ ),
+ );
+ } else {
+ setValue(data[pageKey]);
+ }
+ }, [refresh, data, colsOrder, updateVarName, id, dispatch, module]);
+
+ return (
+
+
+
+ {rows.map((row, index) => (
+
+ {colsOrder.map((col, cidx) => (
+
{row[col]}
+ ))}
+
+ ))}
+
+
+
+ );
+};
+
+export default GameTable;
+```
+
+The detailed explanation of the code is as follows:
+
+- We use the [useDispatch](https://docs.taipy.io/en/latest/refmans/reference_guiext/functions/useDispatch/) hook to
+ dispatch actions to the store and initiate backend communications.
+- Additionally, the [useModule](https://docs.taipy.io/en/latest/refmans/reference_guiext/functions/useModule/) hook
+ retrieves the page module, enabling correct execution of backend functions.
+- To request an update for every dynamic property of an element on initial render, we use the
+ [useDispatchRequestUpdateOnFirstRender](https://docs.taipy.io/en/latest/refmans/reference_guiext/functions/useDispatchRequestUpdateOnFirstRender/#function-usedispatchrequestupdateonfirstrender)
+ hook provided by the Taipy GUI Extension API. This hook takes five parameters:
+ - `dispatch`: The React dispatcher associated with the context.
+ - `id`: The identifier of the element.
+ - `context`: The execution context.
+ - `updateVars`: The content of the `updateVars` property.
+- We also dispatch the
+ [createRequestDataUpdateAction](https://docs.taipy.io/en/latest/refmans/reference_guiext/functions/createRequestUpdateAction/)
+ hook to create a request data update action, which updates the context by invoking the
+ [get_data](https://docs.taipy.io/en/latest/refmans/reference/pkg_taipy/pkg_gui/pkg_extension/ElementLibrary/#taipy.gui.extension.ElementLibrary.get_data)
+ method of the backend library. This invocation triggers an update of front-end elements holding the data.
+
+The [createRequestDataUpdateAction](https://docs.taipy.io/en/latest/refmans/reference_guiext/functions/createRequestUpdateAction/)
+hook accepts eight parameters:
+
+- `name`: The name of the variable containing the requested data, as received in the property.
+- `id`: The identifier of the visual element.
+- `context`: The execution context.
+- `columns`: The list of columns required by the element emitting this action.
+- `pageKey`: The unique identifier for the data received from this action.
+- `payload`: The payload, specific to the component type (e.g., table, chart).
+- `allData`: A flag indicating if all data is requested.
+- `library`: The name of the extension library.
+
+## Exporting the React component {data-source="gui:doc/extension/example_library/front-end/src/index.ts"}
+
+When the component is entirely defined, it must be exported by the JavaScript library.
+This is done by adding the export directive in the file `//front-end/src/index.ts`.
+
+```js
+import GameTable from "./GameTable";
+
+export { GameTable };
+```
+
+## Using the element in the application {data-source="gui:doc/extension/table_chess_game.py"}
+
+In the example below, we use the `game_table` element to display a chess game board. The board is represented as a
+two-dimensional list of strings, where each string represents a chess piece. The board is displayed in a table format
+using the `game_table` element.
+
+```py
+from example_library import ExampleLibrary
+
+from taipy.gui import Gui
+
+data = [
+ ["♜", "♞", "♝", "♛", "♚", "♝", "♞", "♜"]
+ ["♟", "♟", "♟", "♟", "♟", "♟", "♟", "♟"],
+ ["", "", "", "", "", "", "", ""],
+ ["", "", "", "", "", "", "", ""],
+ ["", "", "", "", "", "", "", ""],
+ ["", "", "", "", "", "", "", ""],
+ ["♙", "♙", "♙", "♙", "♙", "♙", "♙", "♙"],
+ ["♖", "♘", "♗", "♕", "♔", "♗", "♘", "♖"],
+]
+
+page = """
+## Chess Game
+<|{data}|example.game_table|>
+"""
+
+if __name__ == "__main__":
+ Gui(page, libraries=[ExampleLibrary()]).run(title="Chess Game")
+```
+
+When you run this application, the page displays the element like this:
+
+
diff --git a/tools/_setup_generation/setup.py b/tools/_setup_generation/setup.py
index 004f5e273..e24d91dcc 100644
--- a/tools/_setup_generation/setup.py
+++ b/tools/_setup_generation/setup.py
@@ -130,6 +130,7 @@ def run_setup(root_dir: str, steps: List[SetupStep] = None):
from .step_refman import RefManStep
from .step_rest_refman import RestRefManStep
from .step_gui_ext_refman import GuiExtRefManStep
+ from .step_mui_icons import MuiIconsStep
from .step_contributors import ContributorsStep
from .step_designer import DesignerStep
@@ -140,6 +141,7 @@ def run_setup(root_dir: str, steps: List[SetupStep] = None):
RefManStep(),
RestRefManStep(),
GuiExtRefManStep(),
+ MuiIconsStep(),
ContributorsStep(),
DesignerStep(),
]
diff --git a/tools/_setup_generation/step_mui_icons.py b/tools/_setup_generation/step_mui_icons.py
new file mode 100644
index 000000000..5bed65167
--- /dev/null
+++ b/tools/_setup_generation/step_mui_icons.py
@@ -0,0 +1,112 @@
+# ################################################################################
+# Generates MUI icons used in Taipy
+#
+# All Material UI icons that are used in the entire Taipy product are extracted
+# in a list of SVG symbols in
+# setup.ref_manuals_dir + "/gui/viselements/mui-icons.svg"
+# In the documentation Markdown body, references to these icons can be referenced
+# using the [MUI:] syntax, which is transformed in the post-process step
+# into a SVG element referencing the symbol itself.
+# ################################################################################
+import os
+import re
+
+from .setup import Setup, SetupStep
+
+
+class MuiIconsStep(SetupStep):
+ def __init__(self):
+ self.mui_icons = None
+
+ def get_id(self) -> str:
+ return "mui-icons"
+
+ def get_description(self) -> str:
+ return "Extracts and groups all MUI icons used in the front-end code"
+
+ def enter(self, setup: Setup):
+ self.FE_DIR_PATH = setup.root_dir + "/taipy-fe"
+ if not os.path.isdir(self.FE_DIR_PATH):
+ raise FileNotFoundError(
+ f"FATAL - Could not find front-end code directory in {self.FE_DIR_PATH}"
+ )
+ self.VISELEMENTS_DIR_PATH = setup.ref_manuals_dir + "/gui/viselements"
+ # Location of the front-end node_modules directory
+ self.MODULES_DIR_PATH = None # Initialized in setup()
+
+ def setup(self, setup: Setup) -> None:
+ # This directory may exist only after GuiExtRefManStep was executed
+ MODULES_DIR_PATH = self.FE_DIR_PATH + "/node_modules"
+ if not os.path.isdir(MODULES_DIR_PATH):
+ raise FileNotFoundError(
+ f"FATAL - Could not find node_modules directory in {MODULES_DIR_PATH}"
+ )
+ mui_icons_path = self.VISELEMENTS_DIR_PATH + "/mui-icons.svg"
+ current_icons = []
+ # Read all known icon symbols in *current_icons* if they were generated
+ if os.path.isfile(mui_icons_path):
+ with open(mui_icons_path, "r") as file:
+ current_icons = [
+ m[1] for m in re.finditer(r" list[str]:
+ icon_path = ICON_PATH_PATTERN.format(icon=icon)
+ try:
+ with open(icon_path, "r") as icon_file:
+ icon_def = icon_file.read()
+ return [m[1] for m in SVG_PATH_RE.finditer(icon_def)]
+ except Exception:
+ print(f"ERROR - Couldn't read source for icon '{icon}'")
+
+ with open(mui_icons_path, "w") as file:
+ print(
+ '", file=file)
diff --git a/tools/_setup_generation/step_viselements.py b/tools/_setup_generation/step_viselements.py
index 3b35a5d33..538ee4f90 100644
--- a/tools/_setup_generation/step_viselements.py
+++ b/tools/_setup_generation/step_viselements.py
@@ -39,13 +39,9 @@ def get_description(self) -> str:
def enter(self, setup: Setup):
self.VISELEMENTS_DIR_PATH = setup.ref_manuals_dir + "/gui/viselements"
- self.GENERICELEMENTS_DIR_PATH = (
- setup.ref_manuals_dir + "/gui/viselements/generic"
- )
- self.CORELEMENTS_DIR_PATH = (
- setup.ref_manuals_dir + "/gui/viselements/corelements"
- )
self.TOC_PATH = self.VISELEMENTS_DIR_PATH + "/index.md"
+ self.GENERICELEMENTS_DIR_PATH = self.VISELEMENTS_DIR_PATH + "/generic"
+ self.CORELEMENTS_DIR_PATH = self.VISELEMENTS_DIR_PATH + "/corelements"
self.CHARTS_HOME_HTML_PATH = (
self.GENERICELEMENTS_DIR_PATH + "/charts/home.html_fragment"
)
@@ -75,13 +71,26 @@ def enter(self, setup: Setup):
if k in loader.categories
}
self.elements = loader.elements
+ self.mui_icons = None
def setup(self, setup: Setup) -> None:
+ self.__read_mui_icons()
tocs = self.__generate_element_pages()
self.__build_navigation()
self.__generate_toc_file(tocs)
self.__generate_builder_api()
+ # Read MUI icons symbol names from mui-icons.svg if it exists, so we can check references
+ # from Markdown body text.
+ def __read_mui_icons(self):
+ SVG_ICON_RE = re.compile(r"{element_type}\nsearch:\n boost: 2\n---\n\n"
@@ -455,16 +473,17 @@ def __chart_page_hook(
raise ValueError(
"Couldn't locate first header1 in documentation for element 'chart'"
)
- styling_match = re.search(
- r"\n# Styling\n", after, re.MULTILINE | re.DOTALL
- )
+ styling_match = re.search(r"\n# Styling\n", after, re.MULTILINE | re.DOTALL)
if not styling_match:
raise ValueError(
"Couldn't locate \"Styling\" header1 in documentation for element 'chart'"
)
return (
match[1] + chart_gallery + before[match.end() :],
- after[: styling_match.start()] + chart_sections + "\n\n" + after[styling_match.start() :]
+ after[: styling_match.start()]
+ + chart_sections
+ + "\n\n"
+ + after[styling_match.start() :],
)
def __process_element_md_file(self, type: str, documentation: str) -> str:
diff --git a/tools/fetch_source_files.py b/tools/fetch_source_files.py
index 953186f27..f62d4004b 100644
--- a/tools/fetch_source_files.py
+++ b/tools/fetch_source_files.py
@@ -32,8 +32,7 @@
# Gather version information for each repository
repo_defs = {
- repo if repo == "taipy" else f"taipy-{repo}": {"version": "local", "tag": None}
- for repo in REPOS + PRIVATE_REPOS
+ repo if repo == "taipy" else f"taipy-{repo}": {"version": "local", "tag": None} for repo in REPOS + PRIVATE_REPOS
}
CATCH_VERSION_RE = re.compile(r"(^\d+\.\d+?)(?:(\.\d+)(\..*)?)?|develop|local$")
for version in args.version:
@@ -76,9 +75,7 @@
if repo_desc is None:
repo = f"taipy-{repo}"
repo_desc = repo_defs.get(repo, None)
- if repo_desc and (
- remapped_version := version_remap_desc.get(repo_desc["version"], None)
- ):
+ if repo_desc and (remapped_version := version_remap_desc.get(repo_desc["version"], None)):
repo_desc["version"] = remapped_version
# Test git, if needed
@@ -87,11 +84,7 @@
git_command = None
else:
git_path = shutil.which(git_command)
- if (
- git_path is None
- or subprocess.run(f'"{git_path}" --version', shell=True, capture_output=True)
- is None
- ):
+ if git_path is None or subprocess.run(f'"{git_path}" --version', shell=True, capture_output=True) is None:
raise IOError(f'Couldn\'t find command "{git_command}"')
git_command = git_path
@@ -124,9 +117,7 @@
repo_defs[repo]["skip"] = True
continue
else:
- raise SystemError(
- f"Problem with {repo}:\nOutput: {cmd.stdout}\nError: {cmd.stderr}"
- )
+ raise SystemError(f"Problem with {repo}:\nOutput: {cmd.stdout}\nError: {cmd.stderr}")
else:
with GitContext(repo, PRIVATE_REPOS):
cmd = subprocess.run(
@@ -140,13 +131,9 @@
repo_defs[repo]["skip"] = True
continue
else:
- raise SystemError(
- f"Couldn't query branches from {loggable_github_root}{repo}."
- )
+ raise SystemError(f"Couldn't query branches from {loggable_github_root}{repo}.")
if f"release/{version}\n" not in cmd.stdout:
- raise ValueError(
- f"No branch 'release/{version}' in repository '{repo}'."
- )
+ raise ValueError(f"No branch 'release/{version}' in repository '{repo}'.")
tag = repo_defs[repo]["tag"]
if tag:
cmd = subprocess.run(
@@ -191,8 +178,6 @@ def safe_rmtree(dir: str):
pipfile_packages = {}
PIPFILE_PACKAGE_RE = re.compile(r"(..*?)\s?=\s?(.*)")
-frontend_dir = os.path.join(ROOT_DIR, "taipy-fe")
-
# Fetch files
def move_files(repo: str, src_path: str):
@@ -204,9 +189,7 @@ def move_files(repo: str, src_path: str):
with open(pipfile_path, "r") as pipfile:
while True:
line = pipfile.readline()
- if str(line) == "" or (
- reading_packages and (not line.strip() or line[0] == "[")
- ):
+ if str(line) == "" or (reading_packages and (not line.strip() or line[0] == "[")):
break
line = line.strip()
if line == "[packages]":
@@ -216,10 +199,7 @@ def move_files(repo: str, src_path: str):
if match and not match.group(1).startswith("taipy"):
package = match.group(1).lower()
version = match.group(2)
- if (
- repo_optional_packages is None
- or package not in repo_optional_packages
- ):
+ if repo_optional_packages is None or package not in repo_optional_packages:
if package in pipfile_packages:
versions = pipfile_packages[package]
if version in versions:
@@ -235,24 +215,18 @@ def move_files(repo: str, src_path: str):
for step_dir in [
step_dir
for step_dir in os.listdir(gs_dir)
- if step_dir.startswith("step_")
- and os.path.isdir(os.path.join(gs_dir, step_dir))
+ if step_dir.startswith("step_") and os.path.isdir(os.path.join(gs_dir, step_dir))
]:
safe_rmtree(os.path.join(gs_dir, step_dir))
for step_dir in [
step_dir
for step_dir in os.listdir(src_path)
- if step_dir.startswith("step_")
- and os.path.isdir(os.path.join(src_path, step_dir))
+ if step_dir.startswith("step_") and os.path.isdir(os.path.join(src_path, step_dir))
]:
- shutil.copytree(
- os.path.join(src_path, step_dir), os.path.join(gs_dir, step_dir)
- )
+ shutil.copytree(os.path.join(src_path, step_dir), os.path.join(gs_dir, step_dir))
safe_rmtree(os.path.join(gs_dir, "src"))
shutil.copytree(os.path.join(src_path, "src"), os.path.join(gs_dir, "src"))
- shutil.copy(
- os.path.join(src_path, "index.md"), os.path.join(gs_dir, "index.md")
- )
+ shutil.copy(os.path.join(src_path, "index.md"), os.path.join(gs_dir, "index.md"))
saved_dir = os.getcwd()
os.chdir(os.path.join(ROOT_DIR, "docs", "getting_started", repo[6:]))
subprocess.run(
@@ -263,9 +237,7 @@ def move_files(repo: str, src_path: str):
)
os.chdir(saved_dir)
elif repo == "taipy-designer":
- designer_doc_dir = os.path.join(
- ROOT_DIR, "docs", "userman", "ecosystem", "designer"
- )
+ designer_doc_dir = os.path.join(ROOT_DIR, "docs", "userman", "ecosystem", "designer")
safe_rmtree(designer_doc_dir)
src_documentation_dir = os.path.join(src_path, "documentation")
saved_dir = os.getcwd()
@@ -277,9 +249,7 @@ def move_files(repo: str, src_path: str):
text=True,
)
os.chdir(saved_dir)
- shutil.copytree(
- os.path.join(src_documentation_dir, "taipy_docs"), designer_doc_dir
- )
+ shutil.copytree(os.path.join(src_documentation_dir, "taipy_docs"), designer_doc_dir)
shutil.copy(
os.path.join(src_documentation_dir, "mkdocs_taipy.yml"),
os.path.join(designer_doc_dir, "mkdocs.yml_template"),
@@ -299,9 +269,7 @@ def copy(item: str, src: str, dst: str, rel_path: str):
rel_path = f"{rel_path}/{item}"
for sub_item in os.listdir(full_src):
copy(sub_item, full_src, full_dst, rel_path)
- elif any(
- item.endswith(ext) for ext in [".py", ".pyi", ".json", ".ipynb"]
- ):
+ elif any(item.endswith(ext) for ext in [".py", ".pyi", ".json", ".ipynb"]):
if os.path.isfile(full_dst): # File exists - compare
with open(full_src, "r") as f:
src = f.read()
@@ -327,22 +295,22 @@ def copy(item: str, src: str, dst: str, rel_path: str):
copy_source(src_path, repo)
- # Copy Taipy GUI front end code
if repo == "taipy":
+ # Copy Taipy GUI front end code
if not os.path.isdir(frontend_dir):
os.mkdir(frontend_dir)
fe_src_dir = os.path.join(src_path, "frontend", "taipy-gui")
+ shutil.copytree(os.path.join(fe_src_dir, "src"), os.path.join(frontend_dir, "src"))
+ for f in [f for f in os.listdir(fe_src_dir) if f.endswith(".md") or f.endswith(".json")]:
+ shutil.copy(os.path.join(fe_src_dir, f), os.path.join(frontend_dir, f))
+
+ # Copy Taipy (Core) front end code
+ core_fe_src_dir = os.path.join(src_path, "frontend", "taipy")
shutil.copytree(
- os.path.join(fe_src_dir, "src"), os.path.join(frontend_dir, "src")
+ os.path.join(core_fe_src_dir, "src"),
+ os.path.join(frontend_dir, "core_src"),
)
- for f in [
- f
- for f in os.listdir(fe_src_dir)
- if f.endswith(".md") or f.endswith(".json")
- ]:
- shutil.copy(
- os.path.join(fe_src_dir, f), os.path.join(frontend_dir, f)
- )
+
finally:
pass
"""
@@ -369,9 +337,7 @@ def copy(item: str, src: str, dst: str, rel_path: str):
if not args.no_pull:
cwd = os.getcwd()
os.chdir(src_path)
- subprocess.run(
- f'"{git_path}" pull', shell=True, capture_output=True, text=True
- )
+ subprocess.run(f'"{git_path}" pull', shell=True, capture_output=True, text=True)
os.chdir(cwd)
print(f" Copying from {src_path}...", flush=True)
move_files(repo, src_path)
@@ -414,9 +380,7 @@ def handleRemoveReadonly(func, path, exc):
shutil.rmtree(clone_dir, onerror=handleRemoveReadonly)
-if os.path.isdir(os.path.join(ROOT_DIR, "fe_node_modules")) and os.path.isdir(
- os.path.join(frontend_dir)
-):
+if os.path.isdir(os.path.join(ROOT_DIR, "fe_node_modules")) and os.path.isdir(os.path.join(frontend_dir)):
shutil.move(
os.path.join(ROOT_DIR, "fe_node_modules"),
os.path.join(frontend_dir, "node_modules"),
@@ -446,9 +410,7 @@ def run(*services: t.Union[Gui, Rest, Orchestrator], **kwargs) -> t.Optional[t.U
# Generate Pipfile from package dependencies from all repositories
pipfile_path = os.path.join(ROOT_DIR, "Pipfile")
-pipfile_message = (
- "WARNING: Package versions mismatch in Pipfiles - Pipfile not updated."
-)
+pipfile_message = "WARNING: Package versions mismatch in Pipfiles - Pipfile not updated."
for package, versions in pipfile_packages.items():
if len(versions) != 1:
if pipfile_message:
@@ -485,9 +447,7 @@ def run(*services: t.Union[Gui, Rest, Orchestrator], **kwargs) -> t.Optional[t.U
)
new_pipfile.write(f"{package} = {version}\n")
if package not in legacy_pipfile_packages:
- pipfile_changes.append(
- f"Package '{package}' added ({version})"
- )
+ pipfile_changes.append(f"Package '{package}' added ({version})")
elif legacy_pipfile_packages[package] != version:
pipfile_changes.append(
f"Package '{package}' version changed from "
diff --git a/tools/postprocess.py b/tools/postprocess.py
index e146d353d..9ded8bab5 100644
--- a/tools/postprocess.py
+++ b/tools/postprocess.py
@@ -53,13 +53,15 @@ def define_env(env):
XREF_RE = re.compile(
r"(?:\(([\w*\.]*)\))?([\.\w]*)(\(.*?\))?\^<\/code>(?:\(([^\)]*)\))?"
)
+# Process MUI icons blocks
+MUI_ICON_RE = re.compile(r"\[MUI\s*:s*(.*?)s*\]")
def find_dummy_h3_entries(content: str) -> Dict[str, str]:
"""
Find 'dummy
' entries.
- These are
tags that are just redirections to another page.
+ These
tags are just redirect to another page.
These need to be removed, and redirection must be used in TOC.
"""
@@ -110,9 +112,9 @@ def remove_dummy_h3(content: str, ids: Dict[str, str]) -> str:
def create_navigation_buttons() -> str:
def create_button(label: str, path: str, class_name: str, group: str = "") -> str:
- gclass = " .tp-nav-button-group_element" if group else ""
+ group_class = " .tp-nav-button-group_element" if group else ""
html = f"""
-
+