model.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. from __future__ import annotations
  2. import abc
  3. from dataclasses import dataclass
  4. from typing import Callable, Iterator, List, Literal, Optional, Union, overload
  5. from nicegui.dataclasses import KWONLY_SLOTS
  6. from nicegui.elements.markdown import remove_indentation
  7. from ..style import create_anchor_name
  8. from . import registry
  9. @dataclass(**KWONLY_SLOTS)
  10. class DocumentationPart:
  11. title: Optional[str] = None
  12. description: Optional[str] = None
  13. description_format: Literal['md', 'rst'] = 'md'
  14. link: Optional[str] = None
  15. ui: Optional[Callable] = None
  16. demo: Optional[Callable] = None
  17. @property
  18. def link_target(self) -> Optional[str]:
  19. """Return the link target for in-page navigation."""
  20. return create_anchor_name(self.title) if self.title else None
  21. class Documentation(abc.ABC):
  22. def __init__(self,
  23. route: str, *,
  24. title: str,
  25. subtitle: str,
  26. back_link: Optional[str] = None) -> None:
  27. self.route = route
  28. self.title = title
  29. self.subtitle = subtitle
  30. self.back_link = back_link
  31. self._content: List[DocumentationPart] = []
  32. self.content()
  33. registry.add(self)
  34. def __iter__(self) -> Iterator[DocumentationPart]:
  35. return iter(self._content)
  36. def __len__(self) -> int:
  37. return len(self._content)
  38. def text(self, title: str, description: str) -> None:
  39. """Add a text block to the documentation."""
  40. self._content.append(DocumentationPart(title=title, description=description))
  41. @overload
  42. def demo(self, title: str, description: str, /) -> Callable[[Callable], Callable]: ...
  43. @overload
  44. def demo(self, element: type, /) -> Callable[[Callable], Callable]: ...
  45. @overload
  46. def demo(self, function: Callable, /) -> Callable[[Callable], Callable]: ...
  47. def demo(self, *args) -> Callable[[Callable], Callable]:
  48. """Add a demo section to the documentation."""
  49. if len(args) == 2:
  50. title, description = args
  51. is_markdown = True
  52. else:
  53. doc = args[0].__init__.__doc__ if isinstance(args[0], type) else args[0].__doc__ # type: ignore
  54. title, description = doc.split('\n', 1)
  55. is_markdown = False
  56. description = remove_indentation(description)
  57. def decorator(function: Callable) -> Callable:
  58. self._content.append(DocumentationPart(
  59. title=title,
  60. description=description,
  61. description_format='md' if is_markdown else 'rst',
  62. demo=function,
  63. ))
  64. return function
  65. return decorator
  66. def ui(self, function: Callable) -> Callable:
  67. """Add arbitrary UI to the documentation."""
  68. self._content.append(DocumentationPart(ui=function))
  69. return function
  70. def intro(self, documentation: Documentation) -> None:
  71. """Add an element intro section to the documentation."""
  72. documentation.back_link = self.route
  73. part = documentation._content[0] # pylint: disable=protected-access
  74. part.link = documentation.route
  75. self._content.append(part)
  76. @abc.abstractmethod
  77. def content(self) -> None:
  78. """Add documentation content here."""
  79. class SectionDocumentation(Documentation):
  80. _title: str
  81. _route: str
  82. def __init_subclass__(cls, title: str, name_: str) -> None:
  83. cls._title = title
  84. cls._route = f'/documentation/section_{name_}'
  85. return super().__init_subclass__()
  86. def __init__(self) -> None:
  87. super().__init__(self._route, subtitle='Documentation', title=self._title, back_link='/documentation')
  88. class DetailDocumentation(Documentation):
  89. _title: str
  90. _route: str
  91. def __init_subclass__(cls, title: str, name_: str) -> None:
  92. cls._title = title
  93. cls._route = f'/documentation/{name_}'
  94. return super().__init_subclass__()
  95. def __init__(self) -> None:
  96. super().__init__(self._route, subtitle='Documentation', title=self._title)
  97. class UiElementDocumentation(Documentation):
  98. _element: Union[type, Callable]
  99. def __init_subclass__(cls, element: Union[type, Callable]) -> None:
  100. cls._element = element
  101. return super().__init_subclass__()
  102. def __init__(self) -> None:
  103. self.element = self._element
  104. name = self.element.__name__.lower()
  105. super().__init__(f'/documentation/{name}', subtitle='Documentation', title=f'ui.*{name}*')
  106. def main_demo(self) -> None:
  107. """Add a demo for the element here."""
  108. def more(self) -> None:
  109. """Add more demos for the element here."""
  110. def content(self) -> None:
  111. self.demo(self.element)(self.main_demo) # pylint: disable=not-callable
  112. self.more()