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:
-
Data node selector toolbar
+
Data Node Selector toolbar
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:
Initialize filters
-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:
@@ -95,9 +89,7 @@ follows:
Create a new filter
-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:
@@ -155,10 +142,8 @@ In the following image, the user has selected the 'Label' property, and a descen
Sort criterion configuration
-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:
@@ -166,9 +151,7 @@ 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:
@@ -194,9 +176,8 @@ labels containing the entered text:
Search active
-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:
-
Crowded data node selector
+
Crowded Data Node Selector
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:
-
Data node selector with pinned items
+
Data Node Selector with pinned items
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:
- - -
Empty scenario selector
+ + +
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:
- - -
Dialog to create a new scenario
+ + +
Empty Scenario Selector
-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:
- - -
Showing all the created scenarios
+ + +
Dialog to create a new scenario
-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:
@@ -58,16 +83,21 @@ Here is how this dialog box looks like:
Editing a scenario
-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. + -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. + +
+ + +
Scenario Selector content
+
+ +

Opening the Filter dialog

+ +When the user presses the [MUI:FilterList] icon (Filter), the following dialog appears: +
+ + +
Initialize filters
+
+ +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: +
+ + +
Filter criteria dialog
+
+ +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: +
+ + +
Create a new filter
+
+ +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: +
+ + +
Filters applied
+
+ +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: +
+ + +
Initial scenarios list
+
+ +Users can click the [MUI:SortByAlpha] icon (Sort) to open the sorting configuration dialog: +
+ + +
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: +
+ + +
Sort criterion configuration
+
+ +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: +
+ + +
Scenarios sorted by their label
+
+ +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: +
+ + +
Search box in the selector
+
+ +If text is entered in the search box, the list dynamically updates to display only scenarios with labels containing the entered text: +
+ + +
Search active
+
+ +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:
@@ -365,8 +365,8 @@ An [example below](#guarding-edits) demonstrates this functionality in [this sec 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_delete*](#p-on_delete) property is **not** set to False, a 'Delete' icon ('🗑') -appears to the left of each row: +**and** the [*on_delete*](#p-on_delete) property is **not** set to False, a [MUI:Delete] icon +(Delete) appears to the left of each row:
@@ -380,8 +380,8 @@ Once clicked, the icon changes to the following:
Validating row deletion
-The user must then confirm the deletion by clicking the '✓' icon or cancel the operation by clicking -the '🗙' icon. +The user must then confirm the deletion by clicking the [MUI:Check] icon (Apply) or cancel the +operation by clicking the [MUI:Clear] icon (Cancel). If the deletion is confirmed, Taipy GUI triggers the callback function specified in the [*on_delete*](#p-on_delete) property. @@ -427,8 +427,9 @@ In the [example below](#guarding-edits), there is a demonstration of this functi If the [*on_edit*](#p-on_edit) property is not set to False and the table columns are editable (i.e., either [*editable[column_name]*](#p-editable[column_name]) is set to True regardless of the [*editable*](#p-editable) property value, or [*editable[column_name]*](#p-editable[column_name]) is -**not** set to False and [*editable*](#p-editable) is set to True), an 'Edit' icon ('✎') appears at -the right end of each editable cell. Users can click this icon to modify the values of individual +**not** set to False and [*editable*](#p-editable) is set to True), the [MUI:Edit] icon (Edit) +appears at the right end of each editable cell. Users can click this icon to modify the values of +individual cells.
Here is how this icon looks:
@@ -443,12 +444,12 @@ When the user clicks this icon, the cell becomes editable:
Editing the cell value
-The user can enter a new value for the cell and either click the check mark (✓) icon or press the -Enter key to confirm the change. This action triggers the callback function defined by the +The user can enter a new value for the cell and either click the [MUI:Check] icon (Apply) or press +the Enter key to confirm the change. This action triggers the callback function defined by the [*on_edit*](#p-on_edit) property (or the built-in `Gui.table_on_edit()^` method if [*on_edit*](#p-on_edit) is not set) to process the updated value.
-Alternatively, the user can cancel the operation by clicking the cancel icon ('🗙') or pressing the -Esc key. +Alternatively, the user can cancel the operation by clicking the [MUI:Clear] icon (Cancel) or +pressing the ESC key. !!! note "Enumerated values" @@ -836,9 +837,9 @@ Here is what the table looks like:
Custom format
-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, + ) + for icon in self.mui_icons: + paths = extract_svg_paths(icon) + if paths is None: + continue + print(f' ', file=file) + paths = extract_svg_paths(icon) + if len(paths) > 1: + print(" ", file=file) + for path in paths: + print(f' ', file=file) + print(" ", file=file) + else: + print(f' ', file=file) + print(" ", file=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""" - +

{label}

@@ -156,7 +158,7 @@ def on_post_build(env): site_dir = env.conf["site_dir"] site_dir_unix = site_dir.replace("\\", "/") - site_url = env.conf["site_url"] + site_url = env.conf["site_url"] # noqa: F841 xrefs = {} multi_xrefs = {} xrefs_path = "xrefs" @@ -172,10 +174,10 @@ def on_post_build(env): if xref_desc[2]: x_packages.update(xref_desc[2]) else: - descs = [xrefs[f"{xref}/{i}"] for i in range(0, xref_desc)] # If unspecified, the first xref will be used (the one with the shortest package) - multi_xrefs[xref] = sorted(descs, key=lambda p: len(p[0])) + multi_xrefs[xref] = sorted([xrefs[f"{xref}/{i}"] for i in range(0, xref_desc)], key=lambda p: len(p[0])) ref_files_path = os.path.join(site_dir, "refmans", "reference") + mui_icons_path = os.path.join(site_dir, "refmans", "gui", "viselements", "mui-icons.svg") fixed_cross_refs = {} # Create navigation button once for all pages navigation_buttons = create_navigation_buttons() @@ -184,7 +186,7 @@ def on_post_build(env): # Post-process generated '.html' files if f.endswith(".html"): filename = os.path.join(root, f) - with open(filename) as html_file: + with open(filename, encoding="utf-8") as html_file: try: html_content = html_file.read() except Exception as e: @@ -257,7 +259,19 @@ def on_post_build(env): "

\\1\\3

", html_content ) - # """ + # Replace MUI icons tags: [MUI:] -> + new_content = "" + last_location = 0 + rel_mui_icons_path = None + for m in MUI_ICON_RE.finditer(html_content): + if not rel_mui_icons_path: + rel_mui_icons_path = os.path.relpath(mui_icons_path, filename).replace("\\", "/")[3:] + new_content += (html_content[last_location : m.start()] + + f"") + last_location = m.end() + if last_location: + html_content = new_content + html_content[last_location:] + # Specific processing for Getting Started documentation files if "getting_started" in filename: GS_H1_H2 = re.compile( @@ -840,7 +854,7 @@ def code(s: str) -> str: html_content, ) - with open(filename, "w") as html_file: + with open(filename, "w", encoding="utf-8") as html_file: html_file.write(html_content) # Replace path to doc in '.ipynb' files elif f.endswith(".ipynb"):