model.py 4.7 KB

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