_element.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. # Copyright 2021-2024 Avaiga Private Limited
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  4. # the License. You may obtain a copy of the License at
  5. #
  6. # http://www.apache.org/licenses/LICENSE-2.0
  7. #
  8. # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  9. # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  10. # specific language governing permissions and limitations under the License.
  11. from __future__ import annotations
  12. import copy
  13. import re
  14. import typing as t
  15. from abc import ABC, abstractmethod
  16. from collections.abc import Iterable
  17. from ._context_manager import _BuilderContextManager
  18. from ._factory import _BuilderFactory
  19. if t.TYPE_CHECKING:
  20. from ..gui import Gui
  21. class _Element(ABC):
  22. """NOT DOCUMENTED"""
  23. _ELEMENT_NAME = ""
  24. _DEFAULT_PROPERTY = ""
  25. __RE_INDEXED_PROPERTY = re.compile(r"^(.*?)__([\w\d]+)$")
  26. def __new__(cls, *args, **kwargs):
  27. obj = super(_Element, cls).__new__(cls)
  28. parent = _BuilderContextManager().peek()
  29. if parent is not None:
  30. parent.add(obj)
  31. return obj
  32. def __init__(self, *args, **kwargs) -> None:
  33. self._properties: t.Dict[str, t.Any] = {}
  34. if args and self._DEFAULT_PROPERTY != "":
  35. self._properties = {self._DEFAULT_PROPERTY: args[0]}
  36. self._properties.update(kwargs)
  37. self.parse_properties()
  38. def update(self, **kwargs):
  39. self._properties.update(kwargs)
  40. self.parse_properties()
  41. # Convert property value to string
  42. def parse_properties(self):
  43. self._properties = {
  44. _Element._parse_property_key(k): _Element._parse_property(v) for k, v in self._properties.items()
  45. }
  46. # Get a deepcopy version of the properties
  47. def _deepcopy_properties(self):
  48. return copy.deepcopy(self._properties)
  49. @staticmethod
  50. def _parse_property_key(key: str) -> str:
  51. if match := _Element.__RE_INDEXED_PROPERTY.match(key):
  52. return f"{match.group(1)}[{match.group(2)}]"
  53. return key
  54. @staticmethod
  55. def _parse_property(value: t.Any) -> t.Any:
  56. if isinstance(value, (str, dict, Iterable)):
  57. return value
  58. if hasattr(value, "__name__"):
  59. return str(getattr(value, "__name__")) # noqa: B009
  60. return str(value)
  61. @abstractmethod
  62. def _render(self, gui: "Gui") -> str:
  63. pass
  64. class _Block(_Element):
  65. """NOT DOCUMENTED"""
  66. def __init__(self, *args, **kwargs) -> None:
  67. super().__init__(*args, **kwargs)
  68. self._children: t.List[_Element] = []
  69. def add(self, *elements: _Element):
  70. for element in elements:
  71. if element not in self._children:
  72. self._children.append(element)
  73. return self
  74. def __enter__(self):
  75. _BuilderContextManager().push(self)
  76. return self
  77. def __exit__(self, exc_type, exc_value, traceback):
  78. _BuilderContextManager().pop()
  79. def _render(self, gui: "Gui") -> str:
  80. el = _BuilderFactory.create_element(gui, self._ELEMENT_NAME, self._deepcopy_properties())
  81. return f"{el[0]}{self._render_children(gui)}</{el[1]}>"
  82. def _render_children(self, gui: "Gui") -> str:
  83. return "\n".join([child._render(gui) for child in self._children])
  84. class _DefaultBlock(_Block):
  85. _ELEMENT_NAME = "part"
  86. def __init__(self, *args, **kwargs):
  87. super().__init__(*args, **kwargs)
  88. class html(_Block):
  89. """A visual element defined as an HTML tag.
  90. Use this class to integrate raw HTML to your page.
  91. This element can be used as a block element.
  92. """
  93. def __init__(self, *args, **kwargs):
  94. """Create a new `html` block.
  95. Arguments:
  96. args (any[]): A list of one or two unnamed arguments:
  97. - *args[0]* is the HTML tag name. If empty or None, this represents an HTML text
  98. node.
  99. - *args[1]* (optional) is the text of this element.<br/>
  100. Note that special HTML characters (such as '&lt;' or '&amp;') do not need to be protected.
  101. kwargs (dict[str, any]): the HTML attributes for this element.<br/>
  102. These should be valid attribute names, with valid attribute values.
  103. Examples:
  104. - To generate `<br/>`, use:
  105. ```
  106. html("br")
  107. ```
  108. - To generate `<h1>My page title</h1>`, use:
  109. ```
  110. html("h1", "My page title")
  111. ```
  112. - To generate `<h1 id="page-title">My page title</h1>`, use:
  113. ```
  114. html("h1", "My page title", id="page-title")
  115. ```
  116. - To generate `<p>This is a <b>Taipy GUI</b> element.</p>`, use:
  117. ```
  118. with html("p"):
  119. html(None, "This is a ")
  120. html("b", "Taipy GUI")
  121. html(None, " element.")
  122. ```
  123. """
  124. super().__init__(*args, **kwargs)
  125. if not args:
  126. raise RuntimeError("Can't render html element. Missing html tag name.")
  127. self._ELEMENT_NAME = args[0] if args[0] else None
  128. self._content = args[1] if len(args) > 1 else ""
  129. def _render(self, gui: "Gui") -> str:
  130. if self._ELEMENT_NAME:
  131. attrs = ""
  132. if self._properties:
  133. attrs = " " + " ".join([f'{k}="{str(v)}"' for k, v in self._properties.items()])
  134. return f"<{self._ELEMENT_NAME}{attrs}>{self._content}{self._render_children(gui)}</{self._ELEMENT_NAME}>"
  135. else:
  136. return self._content
  137. class _Control(_Element):
  138. """NOT DOCUMENTED"""
  139. def __init__(self, *args, **kwargs):
  140. super().__init__(*args, **kwargs)
  141. def _render(self, gui: "Gui") -> str:
  142. el = _BuilderFactory.create_element(gui, self._ELEMENT_NAME, self._deepcopy_properties())
  143. return (
  144. f"<div>{el[0]}</{el[1]}></div>"
  145. if f"<{el[1]}" in el[0] and f"</{el[1]}" not in el[0]
  146. else f"<div>{el[0]}</div>"
  147. )
  148. def __enter__(self):
  149. raise RuntimeError(f"Can't use Context Manager for control type '{self._ELEMENT_NAME}'")
  150. def __exit__(self, exc_type, exc_value, traceback):
  151. raise RuntimeError(f"Can't use Context Manager for control type '{self._ELEMENT_NAME}'")